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>
65 lines
1.8 KiB
TypeScript
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;
|
|
}
|
|
}
|