wc-mixin/examples/main.ts
Buddy 8865b49f4e Add examples page with a context manager
DSD, open/closed template, and light-DOM components that talk through
<main-context>: children register on… callbacks and share updates as events.
Served by Vite via npm run examples; examples/ is linted, formatted and
typechecked.
2026-11-10 09:59:09 -08:00

69 lines
2.6 KiB
TypeScript

// The context first: elements that register with it on connect need it to be
// listening already when they upgrade.
import './main-context.ts';
import './color-swatch-picker.ts';
import './hero-banner.ts';
import './labeled-field.ts';
import './secret-note.ts';
import './star-rating.ts';
// Components talk only through <main-context>; this file just reports on them.
function $<K extends keyof HTMLElementTagNameMap>(tag: K, id: string): HTMLElementTagNameMap[K] {
const el = document.querySelector(`${tag}#${id}`);
if (!el) throw new Error(`missing <${tag} id="${id}">`);
// oxlint-disable-next-line no-unsafe-type-assertion -- the tag in the selector guarantees it
return el as HTMLElementTagNameMap[K];
}
const rating = $('star-rating', 'rating');
const note = $('secret-note', 'note');
// ---------------------------------------------------------------------------
// Closed vs open: what page code can see from outside.
const probe = $('output', 'probe');
function updateProbe(): void {
probe.textContent = [
`rating.shadowRoot → ${rating.shadowRoot ? 'ShadowRoot (open)' : 'null'}`,
`note.shadowRoot → ${note.shadowRoot ? 'ShadowRoot' : 'null (closed)'}`,
`note.revealed → ${note.revealed}`,
`note.querySelector('p') → ${note.querySelector('p') ? '<p>' : 'null'}`,
].join('\n');
}
updateProbe();
// ---------------------------------------------------------------------------
// Attribute log: every reflected prop shows up here as an attribute mutation.
const log = $('ol', 'log');
function show(value: string | null): string {
return value === null ? '∅' : JSON.stringify(value);
}
const observer = new MutationObserver((records) => {
for (const { target, attributeName, oldValue } of records) {
if (!(target instanceof Element) || attributeName === null) continue;
const element = document.createElement('code');
element.textContent = `<${target.localName}#${target.id}>`;
const name = document.createElement('b');
name.textContent = attributeName;
const change = `${show(oldValue)} → ${show(target.getAttribute(attributeName))}`;
const item = document.createElement('li');
item.append(element, ' ', name, ' ', change);
log.prepend(item);
while (log.children.length > 40) log.lastElementChild?.remove();
}
updateProbe();
});
for (const el of document.querySelectorAll('main-context [id]')) {
if (el.localName.includes('-')) {
observer.observe(el, { attributes: true, attributeOldValue: true });
}
}
$('button', 'clear-log').addEventListener('click', () => {
log.replaceChildren();
});