A simple base class for creating fast, lightweight web components
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
TypeScript decorators for enhanced development experience with declarative property and element configuration. Decorators provide a clean, declarative syntax for configuring reactive properties and DOM queries.
Registers a custom element with the browser's custom element registry.
/**
* Class decorator to register a custom element
* @param tagName The tag name for the custom element (must contain a hyphen)
* @returns Class decorator function
*/
function customElement(tagName: string): ClassDecorator;Usage Examples:
import { LitElement, html, customElement } from "lit-element";
@customElement("my-button")
class MyButton extends LitElement {
render() {
return html`
<button>
<slot></slot>
</button>
`;
}
}
@customElement("user-card")
class UserCard extends LitElement {
render() {
return html`
<div class="card">
<slot name="avatar"></slot>
<slot name="name"></slot>
<slot name="bio"></slot>
</div>
`;
}
}
// Elements are automatically registered and can be used in HTML
// <my-button>Click me</my-button>
// <user-card>
// <img slot="avatar" src="..." />
// <h2 slot="name">John Doe</h2>
// <p slot="bio">Software developer</p>
// </user-card>Declares a reactive property that triggers updates when changed.
/**
* Property decorator for reactive properties
* @param options Configuration options for the property
* @returns Property decorator function
*/
function property(options?: PropertyDeclaration): PropertyDecorator;Usage Examples:
import { LitElement, html, customElement, property } from "lit-element";
@customElement("my-element")
class MyElement extends LitElement {
@property({ type: String })
name = "World";
@property({ type: Number })
count = 0;
@property({ type: Boolean })
disabled = false;
@property({ type: Array })
items: string[] = [];
@property({ type: Object })
config: any = {};
render() {
return html`
<div>
<h1>Hello, ${this.name}!</h1>
<p>Count: ${this.count}</p>
<button ?disabled=${this.disabled}>
${this.disabled ? 'Disabled' : 'Enabled'}
</button>
<ul>
${this.items.map(item => html`<li>${item}</li>`)}
</ul>
</div>
`;
}
}Declares internal reactive state that doesn't reflect to attributes.
/**
* Property decorator for internal reactive state
* @param options Configuration options for the state property
* @returns Property decorator function
*/
function state(options?: StateDeclaration): PropertyDecorator;Usage Examples:
import { LitElement, html, customElement, property, state } from "lit-element";
@customElement("toggle-element")
class ToggleElement extends LitElement {
@property({ type: String })
label = "Toggle";
@state()
private _expanded = false;
@state()
private _loading = false;
@state()
private _data: any[] = [];
private async _toggle() {
this._loading = true;
if (!this._expanded) {
// Simulate loading data
await new Promise(resolve => setTimeout(resolve, 1000));
this._data = ['Item 1', 'Item 2', 'Item 3'];
}
this._expanded = !this._expanded;
this._loading = false;
}
render() {
return html`
<div>
<button @click=${this._toggle} ?disabled=${this._loading}>
${this._loading ? 'Loading...' : this.label}
</button>
${this._expanded ? html`
<div class="content">
<ul>
${this._data.map(item => html`<li>${item}</li>`)}
</ul>
</div>
` : ''}
</div>
`;
}
}Queries the render root for an element matching a CSS selector.
/**
* Property decorator for DOM queries
* @param selector CSS selector to query for
* @param cache Whether to cache the query result
* @returns Property decorator function
*/
function query(selector: string, cache?: boolean): PropertyDecorator;Usage Examples:
import { LitElement, html, customElement, query } from "lit-element";
@customElement("form-element")
class FormElement extends LitElement {
@query('#username')
usernameInput!: HTMLInputElement;
@query('button[type="submit"]')
submitButton!: HTMLButtonElement;
@query('.error-message')
errorMessage?: HTMLElement;
@query('form', true) // cached query
form!: HTMLFormElement;
private _handleSubmit(e: Event) {
e.preventDefault();
const username = this.usernameInput.value;
if (!username.trim()) {
if (this.errorMessage) {
this.errorMessage.textContent = 'Username is required';
this.errorMessage.style.display = 'block';
}
return;
}
// Hide error message
if (this.errorMessage) {
this.errorMessage.style.display = 'none';
}
// Disable submit button
this.submitButton.disabled = true;
console.log('Submitting:', username);
}
render() {
return html`
<form @submit=${this._handleSubmit}>
<div>
<label for="username">Username:</label>
<input id="username" type="text" required />
</div>
<div class="error-message" style="display: none; color: red;"></div>
<button type="submit">Submit</button>
</form>
`;
}
}Queries the render root for all elements matching a CSS selector.
/**
* Property decorator for DOM queries returning NodeList
* @param selector CSS selector to query for
* @returns Property decorator function
*/
function queryAll(selector: string): PropertyDecorator;Usage Examples:
import { LitElement, html, customElement, queryAll } from "lit-element";
@customElement("list-element")
class ListElement extends LitElement {
@queryAll('.item')
items!: NodeListOf<HTMLElement>;
@queryAll('input[type="checkbox"]')
checkboxes!: NodeListOf<HTMLInputElement>;
@queryAll('.draggable')
draggableElements!: NodeListOf<HTMLElement>;
private _selectAll() {
this.checkboxes.forEach(checkbox => {
checkbox.checked = true;
});
}
private _clearAll() {
this.checkboxes.forEach(checkbox => {
checkbox.checked = false;
});
}
private _highlightAll() {
this.items.forEach(item => {
item.classList.add('highlighted');
});
}
render() {
return html`
<div>
<div class="controls">
<button @click=${this._selectAll}>Select All</button>
<button @click=${this._clearAll}>Clear All</button>
<button @click=${this._highlightAll}>Highlight All</button>
</div>
<div class="item">
<input type="checkbox" /> Item 1
</div>
<div class="item">
<input type="checkbox" /> Item 2
</div>
<div class="item">
<input type="checkbox" /> Item 3
</div>
</div>
`;
}
}Performs an async query that resolves when the element is found.
/**
* Property decorator for async DOM queries
* @param selector CSS selector to query for
* @returns Property decorator function returning Promise<Element>
*/
function queryAsync(selector: string): PropertyDecorator;Usage Examples:
import { LitElement, html, customElement, queryAsync } from "lit-element";
@customElement("async-element")
class AsyncElement extends LitElement {
@queryAsync('#dynamic-content')
dynamicContent!: Promise<HTMLElement>;
@queryAsync('.lazy-loaded')
lazyElement!: Promise<HTMLElement>;
private async _handleDynamicContent() {
try {
const element = await this.dynamicContent;
element.textContent = 'Content loaded!';
element.style.color = 'green';
} catch (error) {
console.error('Failed to find dynamic content:', error);
}
}
private async _loadContent() {
// Trigger re-render to add the dynamic element
this.requestUpdate();
// Wait for the element to be available
await this._handleDynamicContent();
}
render() {
return html`
<div>
<button @click=${this._loadContent}>Load Content</button>
<div id="dynamic-content">Loading...</div>
</div>
`;
}
}Queries for elements assigned to a slot.
/**
* Property decorator for querying slot assigned elements
* @param options Query options including slot name and selector
* @returns Property decorator function
*/
function queryAssignedElements(options?: QueryAssignedElementsOptions): PropertyDecorator;
interface QueryAssignedElementsOptions {
slot?: string;
selector?: string;
flatten?: boolean;
}Usage Examples:
import { LitElement, html, customElement, queryAssignedElements } from "lit-element";
@customElement("tab-container")
class TabContainer extends LitElement {
@queryAssignedElements({ slot: 'tab' })
tabs!: HTMLElement[];
@queryAssignedElements({ slot: 'panel' })
panels!: HTMLElement[];
@queryAssignedElements({ selector: '.special' })
specialElements!: HTMLElement[];
private _activeTab = 0;
private _selectTab(index: number) {
this._activeTab = index;
// Update tab states
this.tabs.forEach((tab, i) => {
tab.classList.toggle('active', i === index);
});
// Update panel visibility
this.panels.forEach((panel, i) => {
panel.style.display = i === index ? 'block' : 'none';
});
}
render() {
return html`
<div class="tab-header">
<slot name="tab" @slotchange=${this._handleTabsChange}></slot>
</div>
<div class="tab-content">
<slot name="panel" @slotchange=${this._handlePanelsChange}></slot>
</div>
<div class="special-content">
<slot @slotchange=${this._handleSpecialChange}></slot>
</div>
`;
}
private _handleTabsChange() {
this.tabs.forEach((tab, index) => {
tab.addEventListener('click', () => this._selectTab(index));
});
}
private _handlePanelsChange() {
this._selectTab(this._activeTab);
}
private _handleSpecialChange() {
console.log('Special elements:', this.specialElements);
}
}
// Usage:
// <tab-container>
// <button slot="tab">Tab 1</button>
// <button slot="tab">Tab 2</button>
// <div slot="panel">Panel 1 content</div>
// <div slot="panel">Panel 2 content</div>
// <div class="special">Special content</div>
// </tab-container>Queries for nodes (including text nodes) assigned to a slot.
/**
* Property decorator for querying slot assigned nodes
* @param options Query options including slot name and flatten
* @returns Property decorator function
*/
function queryAssignedNodes(options?: QueryAssignedNodesOptions): PropertyDecorator;
interface QueryAssignedNodesOptions {
slot?: string;
flatten?: boolean;
}Usage Examples:
import { LitElement, html, customElement, queryAssignedNodes } from "lit-element";
@customElement("content-analyzer")
class ContentAnalyzer extends LitElement {
@queryAssignedNodes()
allNodes!: Node[];
@queryAssignedNodes({ slot: 'header' })
headerNodes!: Node[];
@queryAssignedNodes({ flatten: true })
flattenedNodes!: Node[];
private _analyzeContent() {
console.log('All nodes:', this.allNodes);
console.log('Text nodes:', this.allNodes.filter(node => node.nodeType === Node.TEXT_NODE));
console.log('Element nodes:', this.allNodes.filter(node => node.nodeType === Node.ELEMENT_NODE));
console.log('Header nodes:', this.headerNodes);
}
render() {
return html`
<div>
<button @click=${this._analyzeContent}>Analyze Content</button>
<div class="header-section">
<slot name="header" @slotchange=${this._handleSlotChange}></slot>
</div>
<div class="main-content">
<slot @slotchange=${this._handleSlotChange}></slot>
</div>
</div>
`;
}
private _handleSlotChange() {
this._analyzeContent();
}
}Configures event listener options for methods.
/**
* Method decorator for configuring event listener options
* @param options Event listener options
* @returns Method decorator function
*/
function eventOptions(options: AddEventListenerOptions): MethodDecorator;
interface AddEventListenerOptions {
capture?: boolean;
once?: boolean;
passive?: boolean;
signal?: AbortSignal;
}Usage Examples:
import { LitElement, html, customElement, eventOptions } from "lit-element";
@customElement("event-element")
class EventElement extends LitElement {
@eventOptions({ passive: true })
private _handleScroll(e: Event) {
// Passive scroll handling for better performance
console.log('Scroll event (passive)');
}
@eventOptions({ once: true })
private _handleFirstClick(e: Event) {
// This handler will only run once
console.log('First click only');
}
@eventOptions({ capture: true })
private _handleCaptureClick(e: Event) {
// Handle in capture phase
console.log('Capture phase click');
}
render() {
return html`
<div
@scroll=${this._handleScroll}
@click=${this._handleFirstClick}
@click=${this._handleCaptureClick}
style="height: 200px; overflow-y: scroll; border: 1px solid #ccc;"
>
<div style="height: 500px; padding: 16px;">
<p>Scrollable content with event handling</p>
<button>Click me (once only + capture)</button>
</div>
</div>
`;
}
}