Web Components support including Custom Element registry, Shadow DOM, and slot functionality. Enables creation of reusable, encapsulated HTML elements.
Registry for custom element definitions.
/**
* Registry for custom element definitions
*/
class CustomElementRegistry {
/** Define custom element */
define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void;
/** Get custom element constructor */
get(name: string): CustomElementConstructor | undefined;
/** Wait for element definition */
whenDefined(name: string): Promise<CustomElementConstructor>;
/** Upgrade element */
upgrade(root: Node): void;
}
interface ElementDefinitionOptions {
extends?: string;
}
type CustomElementConstructor = new () => HTMLElement;Shadow DOM root for encapsulation.
/**
* Shadow DOM root for encapsulation
*/
class ShadowRoot extends DocumentFragment {
/** Shadow root mode */
readonly mode: ShadowRootMode;
/** Host element */
readonly host: Element;
/** Delegates focus flag */
readonly delegatesFocus: boolean;
/** Slot assignment mode */
readonly slotAssignment: SlotAssignmentMode;
/** Inner HTML */
innerHTML: string;
/** Active element */
readonly activeElement: Element | null;
/** Style sheets */
readonly styleSheets: StyleSheetList;
/** Get element by ID */
getElementById(elementId: string): Element | null;
/** Get elements by tag name */
getElementsByTagName(qualifiedName: string): HTMLCollection;
/** Get elements by class name */
getElementsByClassName(classNames: string): HTMLCollection;
}
type ShadowRootMode = 'open' | 'closed';
type SlotAssignmentMode = 'manual' | 'named';Slot element for content projection.
/**
* Slot element for content projection
*/
class HTMLSlotElement extends HTMLElement {
/** Slot name */
name: string;
/** Get assigned nodes */
assignedNodes(options?: AssignedNodesOptions): Node[];
/** Get assigned elements */
assignedElements(options?: AssignedNodesOptions): Element[];
/** Assign nodes (manual slot assignment) */
assign(...nodes: (Element | Text)[]): void;
}
interface AssignedNodesOptions {
flatten?: boolean;
}import { Window, HTMLElement } from "happy-dom";
const window = new Window();
const document = window.document;
// Define custom element class
class MyCustomElement extends HTMLElement {
constructor() {
super();
this.innerHTML = '<p>Hello from custom element!</p>';
}
connectedCallback() {
console.log('Custom element added to page');
this.addEventListener('click', this.handleClick);
}
disconnectedCallback() {
console.log('Custom element removed from page');
this.removeEventListener('click', this.handleClick);
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
}
static get observedAttributes() {
return ['data-value'];
}
private handleClick = () => {
console.log('Custom element clicked!');
}
}
// Register custom element
window.customElements.define('my-custom-element', MyCustomElement);
// Use custom element
const customElement = document.createElement('my-custom-element');
document.body.appendChild(customElement);
// Or use in HTML
document.body.innerHTML = '<my-custom-element data-value="test"></my-custom-element>';import { Window, HTMLElement } from "happy-dom";
const window = new Window();
const document = window.document;
// Custom element with shadow DOM
class CardElement extends HTMLElement {
constructor() {
super();
// Attach shadow root
const shadow = this.attachShadow({ mode: 'open' });
// Define shadow DOM structure
shadow.innerHTML = `
<style>
:host {
display: block;
border: 1px solid #ccc;
border-radius: 8px;
padding: 16px;
margin: 8px;
}
.header {
font-weight: bold;
margin-bottom: 8px;
}
.content {
color: #666;
}
</style>
<div class="header">
<slot name="title">Default Title</slot>
</div>
<div class="content">
<slot>Default content</slot>
</div>
`;
}
}
// Register card element
window.customElements.define('card-element', CardElement);
// Use card with slotted content
document.body.innerHTML = `
<card-element>
<span slot="title">My Card Title</span>
<p>This is the card content that goes in the default slot.</p>
</card-element>
`;
// Access shadow DOM
const card = document.querySelector('card-element');
if (card && card.shadowRoot) {
const titleSlot = card.shadowRoot.querySelector('slot[name="title"]');
const assignedNodes = titleSlot?.assignedNodes();
console.log('Assigned title nodes:', assignedNodes);
}import { Window, HTMLElement } from "happy-dom";
const window = new Window();
const document = window.document;
// Custom element with reactive properties
class CounterElement extends HTMLElement {
private _count = 0;
private _button!: HTMLButtonElement;
private _display!: HTMLSpanElement;
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
button {
padding: 8px 16px;
font-size: 16px;
cursor: pointer;
}
.count {
font-weight: bold;
margin-left: 8px;
}
</style>
<button id="increment">Increment</button>
<span class="count" id="display">0</span>
`;
this._button = shadow.querySelector('#increment')!;
this._display = shadow.querySelector('#display')!;
this._button.addEventListener('click', () => {
this.count++;
});
}
get count() {
return this._count;
}
set count(value: number) {
const oldValue = this._count;
this._count = value;
this._display.textContent = value.toString();
// Dispatch custom event
this.dispatchEvent(new CustomEvent('count-changed', {
detail: { oldValue, newValue: value },
bubbles: true
}));
}
static get observedAttributes() {
return ['count'];
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
if (name === 'count') {
this.count = parseInt(newValue) || 0;
}
}
}
// Register counter element
window.customElements.define('counter-element', CounterElement);
// Use counter element
const counter = document.createElement('counter-element');
counter.setAttribute('count', '5');
document.body.appendChild(counter);
// Listen for count changes
counter.addEventListener('count-changed', (event) => {
console.log('Count changed:', event.detail);
});