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.
69 lines
2.6 KiB
TypeScript
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();
|
|
});
|