Happy DOM is a JavaScript implementation of a web browser without its graphical user interface including DOM, HTML, CSS, events, and fetch APIs
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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);
});