diff --git a/LICENSE b/LICENSE index 8a11286..e71cd03 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 lib +Copyright (c) 2026 Buddy Sandidge Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 9bfc76d..34e3820 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,50 @@ # wc-mixin +A typed mixin factory that removes custom-element boilerplate. + +```ts +import { WebComponent, disable, enable, number, string, type PropChange } from 'wc-mixin'; + +const SPEC = { + shadow: 'open', // omit for light DOM + template: '', + css: ':host { display: block; }', + events: ['click'], + element: { input: 'input.name' }, // refs.input: HTMLInputElement | null + elements: { buttons: 'button' }, // refs.buttons: HTMLButtonElement[] + props: { + userId: number(), // user-id number | undefined + min: number({ default: 1 }), // min number + ready: enable({ attribute: 'is-ready' }), // is-ready boolean (default false) + fetch: disable(), // disable-fetch boolean (default true) + }, +} as const; + +class ExampleElement extends WebComponent(SPEC) { + override onConnect() { + window.addEventListener('resize', () => this.layout(), { signal: this.signal }); + } + override handleEvent(event: PointerEvent) {} + override onPropChange(change: PropChange) { + if (change.name === 'min') change.value; // number + } + layout() {} +} + +customElements.define('example-element', ExampleElement); + +ExampleElement.observedAttributes; // readonly ('user-id' | 'min' | 'is-ready' | 'disable-fetch')[] +``` + +- **Props** are stored as values and reflected to attributes; attribute changes decode back. `onPropChange` fires once per real change from either side. +- **Attribute names**: an explicit `attribute` is used verbatim; otherwise `kebab(key)`, with `disable-` for `disable()` and `data-` in front when `dataPrefix: true`. +- **Codecs**: `string`, `number`, `enable`, `disable`, `choice([...])`, `json()`, `prop({ default, decode, encode })`. +- **Shadow DOM**: `shadow` attaches a root and clones `template` into it; `css` is a shared adopted stylesheet. An existing declarative shadow root is used instead (and `shadow`/`template` are ignored). `root` is the shadow root (even when closed) or the element itself. +- **Light DOM**: `template` is appended on first connect; `css` is added once per element type, as a `