@adia-ai/web-components 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +63 -24
  2. package/USAGE.md +584 -0
  3. package/components/calendar-picker/calendar-picker.d.ts +27 -0
  4. package/components/check/check.d.ts +30 -0
  5. package/components/code/code.d.ts +39 -0
  6. package/components/color-picker/color-picker.d.ts +37 -0
  7. package/components/index.js +1 -0
  8. package/components/input/input.d.ts +61 -0
  9. package/components/option-card/option-card.d.ts +30 -0
  10. package/components/otp-input/otp-input.d.ts +25 -0
  11. package/components/pane/pane.css +10 -0
  12. package/components/pane/pane.js +28 -4
  13. package/components/radio/radio.d.ts +28 -0
  14. package/components/range/range.d.ts +31 -0
  15. package/components/rating/rating.d.ts +33 -0
  16. package/components/search/search.d.ts +35 -0
  17. package/components/segmented/segmented.d.ts +24 -0
  18. package/components/select/select.d.ts +57 -0
  19. package/components/slider/slider.d.ts +31 -0
  20. package/components/switch/switch.d.ts +30 -0
  21. package/components/textarea/textarea.d.ts +31 -0
  22. package/components/toggle-scheme/toggle-scheme.a2ui.json +197 -0
  23. package/components/toggle-scheme/toggle-scheme.css +20 -0
  24. package/components/toggle-scheme/toggle-scheme.js +277 -0
  25. package/components/toggle-scheme/toggle-scheme.yaml +173 -0
  26. package/components/upload/upload.d.ts +27 -0
  27. package/core/element.d.ts +174 -0
  28. package/core/form.d.ts +108 -0
  29. package/core/index.d.ts +11 -0
  30. package/core/index.js +1 -0
  31. package/core/register.d.ts +25 -0
  32. package/core/register.js +58 -0
  33. package/core/signals.d.ts +94 -0
  34. package/core/template.d.ts +70 -0
  35. package/index.d.ts +161 -0
  36. package/package.json +21 -6
  37. package/traits/CATEGORIES.md +1 -1
package/USAGE.md ADDED
@@ -0,0 +1,584 @@
1
+ # Using `@adia-ai/web-components` — Consumer Guide
2
+
3
+ The complete reference for engineers and agents **integrating** AdiaUI into an app. For authoring new primitives (contributing to the library), see [`README.md`](./README.md) § "Authoring a primitive".
4
+
5
+ > This document answers the five most-common questions consumers ask. If something here is wrong, surprising, or missing, that's a bug — open an issue with the surface it describes.
6
+
7
+ ---
8
+
9
+ ## Contents
10
+
11
+ 1. [Install](#install)
12
+ 2. [The mental model](#the-mental-model)
13
+ 3. [Property reactivity (signal-backed)](#property-reactivity-signal-backed)
14
+ 4. [Property binding patterns (templates / framework integration)](#property-binding-patterns)
15
+ 5. [Event contract — `CustomEvent` with `detail`](#event-contract)
16
+ 6. [Form participation — `UIFormElement` + `ElementInternals`](#form-participation)
17
+ 7. [Lifecycle — `connected` / `render` / `updated` / `disconnected`](#lifecycle)
18
+ 8. [Theming, density, size](#theming-density-size)
19
+ 9. [Registration — auto vs explicit](#registration--auto-vs-explicit)
20
+ 10. [TypeScript](#typescript)
21
+ 11. [Anti-patterns](#anti-patterns)
22
+
23
+ ---
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @adia-ai/web-components
29
+ ```
30
+
31
+ Pair with [`@adia-ai/web-modules`](../web-modules) for composite shells (admin / chat / editor / simple / theme):
32
+
33
+ ```bash
34
+ npm install @adia-ai/web-components @adia-ai/web-modules
35
+ ```
36
+
37
+ ### Importing
38
+
39
+ The package is ESM-only — works under any modern bundler (Vite, esbuild, webpack 5+, Rollup) or plain `<script type="module">`.
40
+
41
+ ```js
42
+ // Everything — registers every *-ui tag (95 primitives)
43
+ import '@adia-ai/web-components';
44
+ import '@adia-ai/web-components/css';
45
+
46
+ // Or per-component for tree-shaking-conscious bundles
47
+ import '@adia-ai/web-components/components/button';
48
+ import '@adia-ai/web-components/components/button.css';
49
+ ```
50
+
51
+ Subpath exports also available:
52
+
53
+ ```js
54
+ // Core utilities — signals, templating, base classes
55
+ import { signal, computed, effect, html, UIElement, UIFormElement } from '@adia-ai/web-components/core';
56
+
57
+ // Traits — composable behavior decorators
58
+ import '@adia-ai/web-components/traits';
59
+ ```
60
+
61
+ ---
62
+
63
+ ## The mental model
64
+
65
+ Every AdiaUI primitive is a **standard light-DOM custom element**. There's no shadow DOM, no compiler, no framework — just `customElements.define()` extending a small `UIElement` base class. The base class wires a signal-backed property system on top.
66
+
67
+ The contract every primitive obeys:
68
+
69
+ | Surface | What it is | How to use |
70
+ |---|---|---|
71
+ | **Properties** | Reactive — every prop in `static properties` is backed by a signal. Setting `el.prop = v` triggers a re-render. | `slider.value = 75` |
72
+ | **Attributes** | Mirror of properties (where `reflect: true`). Setting `[attr]` round-trips through the property setter. | `<slider-ui value="50">` |
73
+ | **Events** | `CustomEvent` with typed `detail` payload. Bubbling unless noted otherwise. | `slider.addEventListener('change', e => e.detail.value)` |
74
+ | **Slots** | Light-DOM children projected by attribute selectors (`:scope > [slot="icon"]`). No `<slot>` element. | `<card-ui><span slot="heading">Title</span></card-ui>` |
75
+
76
+ This is the entire surface. There are no magic helpers; no proprietary state-management; no compiler. If you know vanilla web components + standard DOM, you know AdiaUI.
77
+
78
+ ---
79
+
80
+ ## Property reactivity (signal-backed)
81
+
82
+ Every property declared in a primitive's `static properties` map is **signal-backed**. The setter mutates an internal `signal()`; the host's render effect subscribes to all signals and re-runs on every property change. So:
83
+
84
+ ```js
85
+ const slider = document.querySelector('slider-ui');
86
+
87
+ slider.value = 75; // thumb moves to 75%, readout updates, aria-valuenow reflects
88
+ slider.value = 10; // thumb moves to 10%
89
+ slider.disabled = true; // [disabled] reflects, click handlers no-op
90
+ ```
91
+
92
+ This works for **every primitive**. There's no "is this prop reactive?" question — they all are.
93
+
94
+ ### Attributes round-trip
95
+
96
+ Setting an attribute also propagates. `attributeChangedCallback` writes back into the property setter, which mutates the signal, which fires the render effect:
97
+
98
+ ```js
99
+ slider.setAttribute('value', '25'); // same effect as slider.value = 25
100
+ ```
101
+
102
+ This means you can drive a primitive from either side. Whatever your tooling sets — properties or attributes — works.
103
+
104
+ ### Why this matters
105
+
106
+ Consumers sometimes assume that property changes only update the DOM if explicitly subscribed to via `attributeChangedCallback`. **That's not the case here.** The signal-backed property system means every property is reactive by default, with zero per-component opt-in. If you set a property and the DOM doesn't update, it's a bug in the primitive, not a missing subscription on your side.
107
+
108
+ ---
109
+
110
+ ## Property binding patterns
111
+
112
+ When wiring a primitive's property to external state (your own signals, framework state, RxJS observables, etc.), the binding pattern matters. **An eagerly-evaluated value snapshot is one-shot**, not reactive.
113
+
114
+ ### Using the built-in `html` template
115
+
116
+ The package ships its own lightweight template tag at `@adia-ai/web-components/core`. It detects signals and functions as bound values and wraps them in effects:
117
+
118
+ ```js
119
+ import { html, signal, stamp } from '@adia-ai/web-components/core';
120
+
121
+ const minL = signal(0.5);
122
+
123
+ // ✅ Reactive — signal passed directly. Re-fires on every minL.value mutation.
124
+ const tpl1 = html`<slider-ui .value=${minL} min="0" max="1" step="0.01"></slider-ui>`;
125
+
126
+ // ✅ Reactive with transform — function-as-value. Function re-runs on dependency change.
127
+ const tpl2 = html`<slider-ui .value=${() => minL.value * 100} min="0" max="100"></slider-ui>`;
128
+
129
+ // ❌ NON-REACTIVE — eagerly evaluated. Snapshots at render time only.
130
+ const tpl3 = html`<slider-ui .value=${minL.value * 100} min="0" max="100"></slider-ui>`;
131
+ // ^^^^^^^^^^^^^^^^^^^^^^^^
132
+ // evaluates to a number now; nothing to subscribe to
133
+ ```
134
+
135
+ The `.prop=` syntax sets the JS property (not the attribute). The `attr=` syntax sets an attribute. Both round-trip through the signal-backed setter, so either works for reactive updates from your side.
136
+
137
+ ### Using React, Lit, Vue, Svelte, etc.
138
+
139
+ When the binding system isn't AdiaUI's `html` tag, the rule generalizes:
140
+
141
+ **Set the primitive's property inside whatever effect/watcher hook your framework provides.** The property setter mutates the signal, the host re-renders. Don't try to "bind" the value via attributes alone — properties are the reactive path.
142
+
143
+ ```jsx
144
+ // React example
145
+ function MinLSlider({ minL, onMinLChange }) {
146
+ const ref = useRef(null);
147
+ useEffect(() => {
148
+ if (ref.current) ref.current.value = minL * 100;
149
+ }, [minL]);
150
+ return <slider-ui ref={ref} min="0" max="100" onChange={e => onMinLChange(e.detail.value / 100)} />;
151
+ }
152
+ ```
153
+
154
+ ```html
155
+ <!-- Lit example -->
156
+ <slider-ui .value=${this.minL * 100} @change=${e => this.minL = e.detail.value / 100}></slider-ui>
157
+ ```
158
+
159
+ ```html
160
+ <!-- Vue example -->
161
+ <slider-ui :value="minL * 100" @change="onMinLChange($event.detail.value / 100)"></slider-ui>
162
+ ```
163
+
164
+ ```svelte
165
+ <!-- Svelte example -->
166
+ <slider-ui value={minL * 100} on:change={e => minL = e.detail.value / 100}></slider-ui>
167
+ ```
168
+
169
+ ### The eager-evaluation trap
170
+
171
+ Reported real-world bug: a consumer wrote `.value=${signal.value * 100}` and reported "the slider doesn't move on undo." The slider IS reactive; the binding pattern wasn't. `signal.value * 100` evaluates eagerly to a number; the template system never sees the signal, so it can't subscribe.
172
+
173
+ **Fix:** pass `${signal}` (signal direct) or `${() => signal.value * 100}` (function as value). Both get effect-wrapped.
174
+
175
+ ---
176
+
177
+ ## Event contract
178
+
179
+ Every interactive primitive emits **`CustomEvent` with a `detail` payload**. Since v0.4.5, this is uniform across all 17 form-bearing primitives.
180
+
181
+ | Event | Fires on | Detail shape |
182
+ |---|---|---|
183
+ | `change` | Value commit (blur, drag end, keystroke commit) | `{ value }` or `{ value, checked }` for boolean primitives |
184
+ | `input` | Every value tick (typing, dragging) | `{ value }` |
185
+ | `submit` | Enter without shift in `<textarea-ui>` | (none — `event.preventDefault()` to suppress form submission) |
186
+ | Component-specific | See per-component yaml | Per-component detail |
187
+
188
+ ### Listening
189
+
190
+ ```js
191
+ // Typed payload (canonical)
192
+ slider.addEventListener('change', (e) => {
193
+ console.log(e.detail.value); // canonical reading path
194
+ });
195
+
196
+ // Untyped (backwards-compatible — `this.value = v` runs before dispatch)
197
+ slider.addEventListener('change', (e) => {
198
+ console.log(e.target.value); // also valid
199
+ });
200
+ ```
201
+
202
+ Both paths return the same value. Pick whichever your tooling makes ergonomic.
203
+
204
+ ### Value-semantic vs boolean-semantic
205
+
206
+ | Class | Components | Detail |
207
+ |---|---|---|
208
+ | Value-semantic | input, select, slider, textarea, range, rating, search, otp-input, calendar-picker | `{ value }` |
209
+ | Boolean-semantic | switch, check, radio, option-card | `{ value, checked }` |
210
+ | Special | upload | `{ value, files }` |
211
+
212
+ For boolean primitives, `value` is the form-submission string ("on" by default; the form value attribute if set), and `checked` is the actual checked state.
213
+
214
+ ### Component-specific events
215
+
216
+ Some primitives emit named events beyond `change`/`input`:
217
+
218
+ - `<select-ui>` — emits `action` for option-callbacks with `detail.action`
219
+ - `<search-ui>` — emits `search` after debounce with `detail.value`
220
+ - `<chat-composer>` — emits `composer-submit` on Enter with `detail.value`
221
+ - `<color-picker-ui>` — emits multiple typed events (`input`, `change`, `format-change`) with structured detail
222
+
223
+ Each primitive's yaml lists its complete event surface. Live demos at `https://ui-kit.exe.xyz/site/components/<name>`.
224
+
225
+ ### Bubbling
226
+
227
+ All standard events bubble. Internal-routing collisions (e.g. a trait listening for an event then re-emitting one with the same name) are avoided by name discipline — see the `event-name-collision` memory entry for the lesson.
228
+
229
+ ---
230
+
231
+ ## Form participation
232
+
233
+ 15 primitives extend `UIFormElement` and participate in standard forms via `ElementInternals`:
234
+
235
+ ```
236
+ input, select, slider, switch, segmented, check, radio, textarea,
237
+ range, color-picker, rating, option-card, search, otp-input, code
238
+ ```
239
+
240
+ They behave like native form controls:
241
+
242
+ ```html
243
+ <form id="settings">
244
+ <input-ui name="username" type="text" value="alice"></input-ui>
245
+ <select-ui name="theme" value="ocean">…</select-ui>
246
+ <slider-ui name="density" value="50" min="0" max="100"></slider-ui>
247
+ <switch-ui name="notifications" checked></switch-ui>
248
+ <button-ui type="submit" text="Save"></button-ui>
249
+ </form>
250
+ ```
251
+
252
+ ```js
253
+ const form = document.getElementById('settings');
254
+ const data = new FormData(form);
255
+ // → { username: "alice", theme: "ocean", density: "50", notifications: "on" }
256
+ ```
257
+
258
+ ### Validation
259
+
260
+ The full validation surface from native form controls works:
261
+
262
+ ```js
263
+ input.checkValidity(); // boolean
264
+ input.reportValidity(); // boolean + shows error UI
265
+ input.setInvalid('Custom'); // mark invalid with custom message
266
+ input.setValid(); // clear error state
267
+ input.validity; // ValidityState — { valid, valueMissing, patternMismatch, ... }
268
+ input.validationMessage; // localized error message
269
+ input.willValidate; // boolean
270
+ ```
271
+
272
+ Constraint attributes (`required`, `pattern`, `minlength`, `maxlength`) work natively. CSS pseudo-classes (`:valid`, `:invalid`, `:required`, `:disabled`) work in your own CSS without any opt-in.
273
+
274
+ ### Custom validation timing
275
+
276
+ `UIFormElement` validates **on blur if dirty** and **on form reset**. Override the timing via:
277
+
278
+ ```js
279
+ const input = document.querySelector('input-ui');
280
+ input.addEventListener('blur', () => {
281
+ if (!input.value.includes('@')) {
282
+ input.setInvalid('Must be an email');
283
+ } else {
284
+ input.setValid();
285
+ }
286
+ });
287
+ ```
288
+
289
+ ### Form events
290
+
291
+ Submit events bubble normally:
292
+
293
+ ```js
294
+ form.addEventListener('submit', (e) => {
295
+ e.preventDefault();
296
+ const data = new FormData(form);
297
+ fetch('/api/save', { method: 'POST', body: data });
298
+ });
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Lifecycle
304
+
305
+ `UIElement` exposes four overridable lifecycle methods. Defaults are no-ops. Override what you need:
306
+
307
+ ```js
308
+ import { UIElement } from '@adia-ai/web-components/core';
309
+
310
+ class MyWidget extends UIElement {
311
+ static properties = {
312
+ label: { type: String, default: '' },
313
+ };
314
+
315
+ #onKeydown = (e) => { // stable arrow field — required for remove
316
+ if (e.key === 'Escape') this.label = '';
317
+ };
318
+
319
+ connected() {
320
+ // Called once per insertion.
321
+ // Set up listeners on document / external targets here.
322
+ document.addEventListener('keydown', this.#onKeydown);
323
+ }
324
+
325
+ render() {
326
+ // Called every time any property signal mutates.
327
+ // Update internal DOM here. Reading `this.foo` subscribes the host's
328
+ // render effect to that signal.
329
+ this.textContent = this.label || 'Empty';
330
+ }
331
+
332
+ updated(changed) {
333
+ // Called after `render()`.
334
+ // `changed` is a Map<propName, oldValue> for this tick.
335
+ if (changed.has('label')) {
336
+ this.dispatchEvent(new CustomEvent('label-change', {
337
+ bubbles: true, detail: { value: this.label }
338
+ }));
339
+ }
340
+ }
341
+
342
+ disconnected() {
343
+ // Called once per removal.
344
+ // Clean up listeners + intervals + observers here.
345
+ document.removeEventListener('keydown', this.#onKeydown);
346
+ }
347
+ }
348
+
349
+ customElements.define('my-widget', MyWidget);
350
+ ```
351
+
352
+ ### Why the `#field` arrow pattern
353
+
354
+ `addEventListener('event', fn)` and `removeEventListener('event', fn)` only match when `fn` is the same reference. Inline arrows allocate a fresh closure every call, so the remove never fires:
355
+
356
+ ```js
357
+ // ❌ Wrong — different reference each call
358
+ connected() { document.addEventListener('click', (e) => this.foo()); }
359
+ disconnected() { document.removeEventListener('click', (e) => this.foo()); } // doesn't remove
360
+
361
+ // ✅ Correct — stable reference
362
+ #onClick = (e) => this.foo();
363
+ connected() { document.addEventListener('click', this.#onClick); }
364
+ disconnected() { document.removeEventListener('click', this.#onClick); }
365
+ ```
366
+
367
+ The same applies to `bind()` — call it once in the constructor or use a field, not at the listener site.
368
+
369
+ ### Order of operations
370
+
371
+ 1. `constructor()` — `installProps()` defines reactive getters/setters from `static properties`
372
+ 2. Element inserted into DOM → `connectedCallback()` fires
373
+ 3. `connected()` (the overridable hook) runs in `untracked()` so reads don't subscribe outer effects
374
+ 4. Host's main render effect installs: subscribes to all property signals, calls `template()` + `render()`
375
+ 5. On any property change: effect re-runs, re-stamps template, calls `render()` then `updated(changed)`
376
+ 6. Element removed → `disconnectedCallback()` fires → `disconnected()` (overridable)
377
+
378
+ ---
379
+
380
+ ## Theming, density, size
381
+
382
+ AdiaUI is parametric. Three attributes drive global appearance:
383
+
384
+ ```html
385
+ <div data-theme="ocean" density="compact" size="sm">
386
+ <!-- all descendants re-theme / re-densify / re-scale -->
387
+ </div>
388
+ ```
389
+
390
+ | Attribute | Values | Effect |
391
+ |---|---|---|
392
+ | `[data-theme]` | `default`, `ocean`, `forest`, `sunset`, `lavender`, `rose`, `slate`, `midnight` | Swaps the OKLCH color ramp |
393
+ | `[density]` | `compact` (0.85×), `spacious` (1.15×) | Multiplies `--a-density` token |
394
+ | `[size]` | `sm`, `md`, `lg` | Shifts typescale + component dimensions |
395
+ | `[radius]` | `sharp` (0), `rounded` (1), `round` (2) | Multiplies `--a-radius-k` token |
396
+
397
+ Each is a CSS-variable override; no class toggles, no re-imports. Set on any ancestor → applies to that subtree.
398
+
399
+ For a control surface to let users change these at runtime, use [`<theme-panel>`](https://ui-kit.exe.xyz/site/components/theme-panel) from `@adia-ai/web-modules/theme`.
400
+
401
+ ---
402
+
403
+ ## Registration — auto vs explicit
404
+
405
+ ### Auto-register (default)
406
+
407
+ Importing a primitive's `.js` file immediately runs `customElements.define('button-ui', UIButton)`. This is the canonical path — once imported, the tag works everywhere:
408
+
409
+ ```js
410
+ import '@adia-ai/web-components/components/button';
411
+ // <button-ui> is now usable in any markup
412
+ ```
413
+
414
+ The `sideEffects` field in `package.json` keeps this path tree-shake-safe.
415
+
416
+ ### Conflict-safe registration (`defineIfFree`)
417
+
418
+ Multiple packages may try to register the same tag (e.g. when a host page imports the AdiaUI bundle AND a downstream consumer imports per-component subpaths). Native `customElements.define` throws `NotSupportedError` on duplicate registration. The package ships a no-op-on-duplicate helper:
419
+
420
+ ```js
421
+ import { defineIfFree, isRegistered, register } from '@adia-ai/web-components/core';
422
+
423
+ class MyWidget extends HTMLElement { /* … */ }
424
+
425
+ // Registers if free; returns false (no throw) if already taken
426
+ const ok = defineIfFree('my-widget', MyWidget);
427
+
428
+ // Check before registering
429
+ if (!isRegistered('my-widget')) {
430
+ register('my-widget', MyWidget);
431
+ }
432
+ ```
433
+
434
+ When subclassing an AdiaUI primitive under a different tag name:
435
+
436
+ ```js
437
+ import { UISlider } from '@adia-ai/web-components/components/slider';
438
+ import { register } from '@adia-ai/web-components/core';
439
+
440
+ class MySlider extends UISlider {
441
+ static properties = { ...UISlider.properties, custom: { type: String } };
442
+ }
443
+ register('my-slider', MySlider);
444
+ ```
445
+
446
+ The `slider-ui` import auto-registers, but that doesn't interfere — `my-slider` is a distinct tag that hosts your subclass.
447
+
448
+ ### Roadmap — non-side-effect class imports
449
+
450
+ A future v0.4.x release will ship a non-side-effect `class` subpath per component:
451
+
452
+ ```js
453
+ // FUTURE — not available yet (v0.4.7+)
454
+ import { UISlider } from '@adia-ai/web-components/components/slider/class';
455
+ // imports the class without running customElements.define
456
+ ```
457
+
458
+ This will enable test isolation (register the class under a different tag in unit tests without clobbering the global) + true tree-shaking elimination of un-used primitives. Until then, importing the auto-register subpath is the canonical path, and `defineIfFree` covers conflict avoidance.
459
+
460
+ ---
461
+
462
+ ## TypeScript
463
+
464
+ Since v0.4.6, the package ships hand-written `.d.ts` declarations. Adding the package to your dependencies automatically picks up:
465
+
466
+ - Typed properties on every primitive (e.g. `el.value` is `number` on `<slider-ui>`, `boolean` on `<switch-ui>`)
467
+ - Typed events — `addEventListener('change', e => e.detail.value)` infers the detail shape
468
+ - `HTMLElementTagNameMap` augmentation — `document.querySelector('slider-ui')` returns `UISliderElement`
469
+ - Public types for `UIElement`, `UIFormElement`, signal, computed, effect, html, stamp
470
+
471
+ ```ts
472
+ import type { UISliderElement, SliderChangeEvent } from '@adia-ai/web-components';
473
+
474
+ const slider: UISliderElement = document.querySelector('slider-ui')!;
475
+ slider.value = 75; // typed Number — string would error
476
+
477
+ slider.addEventListener('change', (e: SliderChangeEvent) => {
478
+ console.log(e.detail.value); // number, inferred
479
+ });
480
+
481
+ slider.addEventListener('change', (e) => { // also inferred
482
+ e.detail.value.toFixed(2); // ok — Number.prototype is in scope
483
+ });
484
+ ```
485
+
486
+ For older bundlers without `exports` field support, add to `tsconfig.json`:
487
+
488
+ ```json
489
+ {
490
+ "compilerOptions": {
491
+ "moduleResolution": "bundler"
492
+ }
493
+ }
494
+ ```
495
+
496
+ ---
497
+
498
+ ## Anti-patterns
499
+
500
+ Things to avoid, with why:
501
+
502
+ ### ❌ Don't bind eagerly-evaluated values to reactive props
503
+
504
+ ```js
505
+ // BREAKS — value is a number, not a signal
506
+ <slider-ui .value=${signal.value * 100}></slider-ui>
507
+
508
+ // WORKS — pass the signal or a function
509
+ <slider-ui .value=${signal}></slider-ui>
510
+ <slider-ui .value=${() => signal.value * 100}></slider-ui>
511
+ ```
512
+
513
+ ### ❌ Don't subscribe via `attributeChangedCallback` to a primitive's attributes
514
+
515
+ The base class already handles this. Use the property setter instead:
516
+
517
+ ```js
518
+ // BREAKS — fragile, duplicates the base class's mirror
519
+ const observer = new MutationObserver(...);
520
+ observer.observe(slider, { attributes: ['value'] });
521
+
522
+ // WORKS — set the property; signals propagate
523
+ slider.value = 75;
524
+ ```
525
+
526
+ ### ❌ Don't use shadow DOM patterns
527
+
528
+ AdiaUI is light-DOM. `::part()`, `::slotted()`, `<slot>` elements don't exist:
529
+
530
+ ```js
531
+ // BREAKS — no shadow tree
532
+ slider.shadowRoot.querySelector('.thumb');
533
+
534
+ // WORKS — children are real DOM
535
+ slider.querySelector('[slot="thumb"]');
536
+ ```
537
+
538
+ ### ❌ Don't add inline event handlers via `onchange=`
539
+
540
+ The HTML `on*` attributes don't pick up the typed `detail`:
541
+
542
+ ```html
543
+ <!-- BREAKS — fires but no typed handler signature -->
544
+ <slider-ui onchange="console.log(this.value)"></slider-ui>
545
+
546
+ <!-- WORKS — addEventListener gets the CustomEvent -->
547
+ <slider-ui id="s"></slider-ui>
548
+ <script>
549
+ document.getElementById('s').addEventListener('change', e => console.log(e.detail.value));
550
+ </script>
551
+ ```
552
+
553
+ ### ❌ Don't try to register a tag name twice
554
+
555
+ `customElements.define()` throws on re-registration. The auto-register pattern handles this for you. If you import a primitive twice (e.g. via two different paths), the bundler de-dupes; if it doesn't, the first define wins and the second throws.
556
+
557
+ ### ❌ Don't write `static properties` with un-typed defaults
558
+
559
+ ```js
560
+ // BREAKS — undefined defaults cause attribute parsing surprises
561
+ static properties = { value: {} };
562
+
563
+ // WORKS — declare type + default
564
+ static properties = {
565
+ value: { type: Number, default: 0, reflect: true },
566
+ };
567
+ ```
568
+
569
+ ### ❌ Don't read property values from inside `constructor()`
570
+
571
+ Properties are installed by `installProps()` in `super()`, but the host's render effect doesn't install until `connectedCallback()`. Reading properties in the constructor returns defaults; mutating them is fine, but if you depend on the rendered DOM existing, do it in `connected()` or later.
572
+
573
+ ---
574
+
575
+ ## More
576
+
577
+ - [`README.md`](./README.md) — package overview + authoring guide
578
+ - [Component yaml contracts](./components/) — every primitive's complete API (props, events, slots, examples)
579
+ - [Live demos](https://ui-kit.exe.xyz/site/components/) — every primitive in action
580
+ - [`docs/specs/component-token-contract.md`](../../docs/specs/component-token-contract.md) — full authoring contract
581
+ - [`docs/specs/component-implementation-patterns.md`](../../docs/specs/component-implementation-patterns.md) — render strategies
582
+ - [`docs/specs/traits.md`](../../docs/specs/traits.md) — composable behaviors
583
+
584
+ For consumer questions not covered here, file an issue against [adiahealth/gen-ui-kit](https://github.com/adiahealth/gen-ui-kit/issues). This document is the canonical answer — if it doesn't have your answer, it's a doc gap worth fixing.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `<calendar-picker-ui>` — Date picker with popover-anchored calendar grid.
3
+ *
4
+ * @see https://ui-kit.exe.xyz/site/components/calendar-picker
5
+ */
6
+
7
+ import { UIFormElement } from '../../core/form.js';
8
+
9
+ export interface CalendarPickerChangeEventDetail {
10
+ /** Selected date in ISO-8601 form (e.g. `"2026-05-12"`). */
11
+ value: string;
12
+ }
13
+ export type CalendarPickerChangeEvent = CustomEvent<CalendarPickerChangeEventDetail>;
14
+
15
+ export class UICalendarPicker extends UIFormElement {
16
+ /** ISO-date string of the selected day. */
17
+ value: string;
18
+ /** Open/closed reflected state. */
19
+ open: boolean;
20
+
21
+ addEventListener<K extends keyof HTMLElementEventMap>(
22
+ type: K,
23
+ listener: (this: UICalendarPicker, ev: HTMLElementEventMap[K]) => unknown,
24
+ options?: boolean | AddEventListenerOptions,
25
+ ): void;
26
+ addEventListener(type: 'change', listener: (ev: CalendarPickerChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
27
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `<check-ui>` — Checkbox (boolean-semantic form control).
3
+ *
4
+ * @see https://ui-kit.exe.xyz/site/components/check
5
+ */
6
+
7
+ import { UIFormElement } from '../../core/form.js';
8
+
9
+ export interface CheckChangeEventDetail {
10
+ /** Submitted value (defaults to `"on"` when checked). */
11
+ value: string;
12
+ /** Current checked state. */
13
+ checked: boolean;
14
+ }
15
+ export type CheckChangeEvent = CustomEvent<CheckChangeEventDetail>;
16
+
17
+ export class UICheck extends UIFormElement {
18
+ /** Checked state — reflected, toggles on click/Space/Enter. */
19
+ checked: boolean;
20
+ /** Indeterminate (mixed) state — visual only; cleared on click. */
21
+ indeterminate: boolean;
22
+ label: string;
23
+
24
+ addEventListener<K extends keyof HTMLElementEventMap>(
25
+ type: K,
26
+ listener: (this: UICheck, ev: HTMLElementEventMap[K]) => unknown,
27
+ options?: boolean | AddEventListenerOptions,
28
+ ): void;
29
+ addEventListener(type: 'change', listener: (ev: CheckChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
30
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `<code-ui>` — Syntax-highlighted code block (CodeMirror 6 under the hood).
3
+ *
4
+ * @see https://ui-kit.exe.xyz/site/components/code
5
+ */
6
+
7
+ import { UIFormElement } from '../../core/form.js';
8
+
9
+ export interface CodeChangeEventDetail {
10
+ value: string;
11
+ }
12
+ export type CodeChangeEvent = CustomEvent<CodeChangeEventDetail>;
13
+ export type CodeInputEvent = CustomEvent<CodeChangeEventDetail>;
14
+
15
+ export class UICode extends UIFormElement {
16
+ /** CodeMirror language slug — `javascript` / `typescript` / `html` / `css` / `json` / `markdown` / `python` etc. */
17
+ language: string;
18
+ /** Inline mode — drops chrome, renders as a single-line code fragment. */
19
+ inline: boolean;
20
+ /** Text content of the code block (alternative to slotted children). */
21
+ text: string;
22
+ /** Alias for `text` — set via `el.textContent` round-trips. */
23
+ textContent: string;
24
+ /** Show line numbers in the gutter. */
25
+ lineNumbers: boolean;
26
+ /** Editable mode — user can type. Form-associated only when this is true. */
27
+ editable: boolean;
28
+ /** Drop chrome (border, background) for inline composition. */
29
+ bare: boolean;
30
+ placeholder: string;
31
+
32
+ addEventListener<K extends keyof HTMLElementEventMap>(
33
+ type: K,
34
+ listener: (this: UICode, ev: HTMLElementEventMap[K]) => unknown,
35
+ options?: boolean | AddEventListenerOptions,
36
+ ): void;
37
+ addEventListener(type: 'change', listener: (ev: CodeChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
38
+ addEventListener(type: 'input', listener: (ev: CodeInputEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
39
+ }