wc-mixin/examples/secret-note.ts
Buddy 76d78e1925 Accept a CSSStyleSheet as css; move example CSS and templates into files
css is now string | CSSStyleSheet. A sheet is adopted as-is: into its own
shadow root, an enclosing shadow root, or document.adoptedStyleSheets for
light DOM at the document level. Strings behave as before.

The examples import their styles with `with { type: 'css' }` and their
static templates with `with { type: 'text' }`. Safari doesn't support those
yet, so vite.config.ts rewrites them into modules Vite builds: a constructed
CSSStyleSheet, or the file's text. npm run examples now just runs vite,
with root set in the config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 11:44:27 -08:00

65 lines
1.8 KiB
TypeScript

import { WebComponent, enable, string, type PropChange } from '../src/index.ts';
import { register } from './main-context.ts';
import css from './secret-note.css' with { type: 'css' };
import template from './secret-note.html' with { type: 'text' };
/**
* Closed shadow root built from `template`: `el.shadowRoot` is `null` from the
* outside, but the component still reaches its own DOM through `this.root` and
* `this.refs`. Closed hides the internals, not the data; `message` is still a
* reflected attribute.
*/
const SPEC = {
shadow: 'closed',
template,
css,
element: {
toggle: 'button',
hint: '.hint',
lock: '.lock',
message: 'p.message',
},
props: {
message: string({ default: '' }),
revealed: enable(),
},
} as const;
export class SecretNote extends WebComponent(SPEC) {
override onConnect(): void {
// Not `events: { click: true }`: a listener on the host can't see into a closed
// root (`composedPath()` stops at the host), so listen on the button itself.
this.refs.toggle?.addEventListener('click', this, { signal: this.signal });
this.#render();
register(this, {
onSecret: (secret) => {
this.message = secret;
},
});
}
override onPropChange(_change: PropChange<typeof SPEC>): void {
this.#render();
}
override handleEvent(): void {
this.revealed = !this.revealed;
}
#render(): void {
const { toggle, hint, lock, message } = this.refs;
if (message) message.textContent = this.message;
if (hint) hint.textContent = this.revealed ? 'Hide' : 'Click to reveal';
if (lock) lock.textContent = this.revealed ? '🔓' : '🔒';
toggle?.setAttribute('aria-expanded', String(this.revealed));
}
}
customElements.define('secret-note', SecretNote);
declare global {
interface HTMLElementTagNameMap {
'secret-note': SecretNote;
}
}