@adia-ai/web-components 0.4.4 → 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.
- package/README.md +63 -24
- package/USAGE.md +584 -0
- package/components/calendar-picker/calendar-picker.d.ts +27 -0
- package/components/calendar-picker/calendar-picker.js +1 -1
- package/components/check/check.d.ts +30 -0
- package/components/check/check.js +1 -1
- package/components/code/code.d.ts +39 -0
- package/components/color-picker/color-picker.d.ts +37 -0
- package/components/index.js +1 -0
- package/components/input/input.d.ts +61 -0
- package/components/input/input.js +9 -9
- package/components/option-card/option-card.d.ts +30 -0
- package/components/option-card/option-card.js +1 -1
- package/components/otp-input/otp-input.d.ts +25 -0
- package/components/otp-input/otp-input.js +3 -3
- package/components/pane/pane.css +10 -0
- package/components/pane/pane.js +28 -4
- package/components/radio/radio.d.ts +28 -0
- package/components/radio/radio.js +1 -1
- package/components/range/range.d.ts +31 -0
- package/components/range/range.js +3 -3
- package/components/rating/rating.d.ts +33 -0
- package/components/rating/rating.js +1 -1
- package/components/search/search.d.ts +35 -0
- package/components/search/search.js +2 -2
- package/components/segmented/segmented.d.ts +24 -0
- package/components/select/select.d.ts +57 -0
- package/components/select/select.js +2 -2
- package/components/slider/slider.d.ts +31 -0
- package/components/slider/slider.js +4 -4
- package/components/slider/slider.test.js +105 -0
- package/components/switch/switch.d.ts +30 -0
- package/components/switch/switch.js +1 -1
- package/components/textarea/textarea.d.ts +31 -0
- package/components/textarea/textarea.js +2 -2
- package/components/toggle-scheme/toggle-scheme.a2ui.json +197 -0
- package/components/toggle-scheme/toggle-scheme.css +20 -0
- package/components/toggle-scheme/toggle-scheme.js +277 -0
- package/components/toggle-scheme/toggle-scheme.yaml +173 -0
- package/components/upload/upload.d.ts +27 -0
- package/components/upload/upload.js +1 -1
- package/core/element.d.ts +174 -0
- package/core/form.d.ts +108 -0
- package/core/index.d.ts +11 -0
- package/core/index.js +1 -0
- package/core/register.d.ts +25 -0
- package/core/register.js +58 -0
- package/core/signals.d.ts +94 -0
- package/core/template.d.ts +70 -0
- package/index.d.ts +161 -0
- package/package.json +22 -6
- package/styles/design-tokens-export.js +554 -0
- package/traits/CATEGORIES.md +1 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <toggle-scheme-ui>
|
|
3
|
+
*
|
|
4
|
+
* Icon-button primitive that toggles `color-scheme` between light and dark
|
|
5
|
+
* on a target element (default `:root`). Replaces the hand-wired
|
|
6
|
+
* `<button-ui id="theme-toggle"> + applyScheme()` boilerplate previously
|
|
7
|
+
* duplicated across apps/genui, apps/construct-canvas, playgrounds/chat.
|
|
8
|
+
*
|
|
9
|
+
* Composition: light-DOM host, internally stamps a single <button-ui>.
|
|
10
|
+
*
|
|
11
|
+
* Authoring API:
|
|
12
|
+
* [scheme="auto"|"light"|"dark"] initial scheme (default "auto")
|
|
13
|
+
* [target=":root"] CSS selector for target element
|
|
14
|
+
* [persist] write selection to localStorage
|
|
15
|
+
* [storage-prefix="adia-theme-"] LS key namespace (matches theme-panel)
|
|
16
|
+
* [icon-light="sun"] icon shown when active-scheme is light
|
|
17
|
+
* [icon-dark="moon"] icon shown when active-scheme is dark
|
|
18
|
+
* [label-light] aria-label / title in light mode
|
|
19
|
+
* [label-dark] aria-label / title in dark mode
|
|
20
|
+
* [size="md"] forwarded to internal <button-ui>
|
|
21
|
+
* [variant="ghost"] forwarded to internal <button-ui>
|
|
22
|
+
* [color=""] forwarded to internal <button-ui>
|
|
23
|
+
* [disabled] forwarded to internal <button-ui>
|
|
24
|
+
*
|
|
25
|
+
* Reflected state (read-only externally; use .setScheme() to mutate):
|
|
26
|
+
* [active-scheme] resolved scheme ("light" | "dark")
|
|
27
|
+
*
|
|
28
|
+
* Events:
|
|
29
|
+
* scheme-change — bubbles. detail: { scheme, source }
|
|
30
|
+
* source ∈ "press" | "media" | "programmatic"
|
|
31
|
+
*
|
|
32
|
+
* Public methods:
|
|
33
|
+
* .toggle() flip between light and dark (defeats auto)
|
|
34
|
+
* .setScheme(s) "light" | "dark" | "auto"; programmatic write
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { UIElement } from '../../core/element.js';
|
|
38
|
+
import '../button/button.js';
|
|
39
|
+
|
|
40
|
+
const LIGHT = 'light';
|
|
41
|
+
const DARK = 'dark';
|
|
42
|
+
const AUTO = 'auto';
|
|
43
|
+
|
|
44
|
+
class UIToggleScheme extends UIElement {
|
|
45
|
+
static properties = {
|
|
46
|
+
scheme: { type: String, default: AUTO, reflect: true },
|
|
47
|
+
target: { type: String, default: ':root', reflect: true },
|
|
48
|
+
persist: { type: Boolean, default: false, reflect: true },
|
|
49
|
+
storagePrefix: { type: String, default: 'adia-theme-', reflect: true, attribute: 'storage-prefix' },
|
|
50
|
+
iconLight: { type: String, default: 'sun', reflect: true, attribute: 'icon-light' },
|
|
51
|
+
iconDark: { type: String, default: 'moon', reflect: true, attribute: 'icon-dark' },
|
|
52
|
+
labelLight: { type: String, default: 'Switch to dark mode', reflect: true, attribute: 'label-light' },
|
|
53
|
+
labelDark: { type: String, default: 'Switch to light mode', reflect: true, attribute: 'label-dark' },
|
|
54
|
+
size: { type: String, default: 'md', reflect: true },
|
|
55
|
+
variant: { type: String, default: 'ghost', reflect: true },
|
|
56
|
+
color: { type: String, default: '', reflect: true },
|
|
57
|
+
disabled: { type: Boolean, default: false, reflect: true },
|
|
58
|
+
activeScheme: { type: String, default: '', reflect: true, attribute: 'active-scheme' },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
static template = () => null;
|
|
62
|
+
|
|
63
|
+
#button = null;
|
|
64
|
+
#mql = null;
|
|
65
|
+
#mqlHandler = null;
|
|
66
|
+
#onPress = null;
|
|
67
|
+
#stamped = false;
|
|
68
|
+
|
|
69
|
+
connected() {
|
|
70
|
+
if (!this.#stamped) {
|
|
71
|
+
this.#stamp();
|
|
72
|
+
this.#stamped = true;
|
|
73
|
+
}
|
|
74
|
+
this.#initState();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
disconnected() {
|
|
78
|
+
if (this.#button && this.#onPress) {
|
|
79
|
+
this.#button.removeEventListener('press', this.#onPress);
|
|
80
|
+
}
|
|
81
|
+
this.#detachPcm();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Public API ──────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/** Flip between light and dark — defeats auto. */
|
|
87
|
+
toggle() {
|
|
88
|
+
const next = this.activeScheme === DARK ? LIGHT : DARK;
|
|
89
|
+
this.#apply(next, 'programmatic');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {"light"|"dark"|"auto"} s
|
|
94
|
+
*/
|
|
95
|
+
setScheme(s) {
|
|
96
|
+
if (s === AUTO) {
|
|
97
|
+
this.#clearTargetOverride();
|
|
98
|
+
const resolved = this.#resolvePrefersScheme();
|
|
99
|
+
this.activeScheme = resolved;
|
|
100
|
+
this.#syncButton();
|
|
101
|
+
this.#attachPcm();
|
|
102
|
+
this.#emit('programmatic');
|
|
103
|
+
} else if (s === LIGHT || s === DARK) {
|
|
104
|
+
this.#apply(s, 'programmatic');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Internal — stamping ─────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
#stamp() {
|
|
111
|
+
const btn = document.createElement('button-ui');
|
|
112
|
+
btn.setAttribute('part', 'button');
|
|
113
|
+
btn.setAttribute('variant', this.variant || 'ghost');
|
|
114
|
+
btn.setAttribute('size', this.size || 'md');
|
|
115
|
+
if (this.color) btn.setAttribute('color', this.color);
|
|
116
|
+
if (this.disabled) btn.setAttribute('disabled', '');
|
|
117
|
+
this.appendChild(btn);
|
|
118
|
+
|
|
119
|
+
this.#button = btn;
|
|
120
|
+
this.#onPress = (e) => {
|
|
121
|
+
// Internal button bubbles 'press'; we re-emit our own 'scheme-change'
|
|
122
|
+
// so consumers see one semantic event, not the inner button's.
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
if (this.disabled) return;
|
|
125
|
+
const next = this.activeScheme === DARK ? LIGHT : DARK;
|
|
126
|
+
this.#apply(next, 'press');
|
|
127
|
+
};
|
|
128
|
+
btn.addEventListener('press', this.#onPress);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Internal — initial state ────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
#initState() {
|
|
134
|
+
// Priority: persisted > [scheme] attr > auto
|
|
135
|
+
let initial = null;
|
|
136
|
+
if (this.persist) {
|
|
137
|
+
try {
|
|
138
|
+
const v = localStorage.getItem(`${this.storagePrefix}scheme`);
|
|
139
|
+
if (v === LIGHT || v === DARK) initial = v;
|
|
140
|
+
} catch {}
|
|
141
|
+
}
|
|
142
|
+
if (initial === null) {
|
|
143
|
+
const attr = (this.scheme || AUTO);
|
|
144
|
+
if (attr === LIGHT || attr === DARK) initial = attr;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (initial === LIGHT || initial === DARK) {
|
|
148
|
+
// Explicit choice — write to target, no MQL listening
|
|
149
|
+
this.#writeTarget(initial);
|
|
150
|
+
this.activeScheme = initial;
|
|
151
|
+
} else {
|
|
152
|
+
// Auto — follow prefers-color-scheme, attach listener
|
|
153
|
+
this.activeScheme = this.#resolvePrefersScheme();
|
|
154
|
+
this.#attachPcm();
|
|
155
|
+
}
|
|
156
|
+
this.#syncButton();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── Internal — mutation (single path) ───────────────────────────────
|
|
160
|
+
|
|
161
|
+
#apply(next, source) {
|
|
162
|
+
if (next !== LIGHT && next !== DARK) return;
|
|
163
|
+
this.#writeTarget(next);
|
|
164
|
+
this.activeScheme = next;
|
|
165
|
+
if (this.persist) {
|
|
166
|
+
try { localStorage.setItem(`${this.storagePrefix}scheme`, next); } catch {}
|
|
167
|
+
}
|
|
168
|
+
// User picked explicitly — stop tracking OS preference
|
|
169
|
+
this.#detachPcm();
|
|
170
|
+
this.#syncButton();
|
|
171
|
+
this.#emit(source);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#syncButton() {
|
|
175
|
+
if (!this.#button) return;
|
|
176
|
+
const dark = this.activeScheme === DARK;
|
|
177
|
+
const icon = dark ? this.iconDark : this.iconLight;
|
|
178
|
+
const label = dark ? this.labelDark : this.labelLight;
|
|
179
|
+
if (this.#button.getAttribute('icon') !== icon) {
|
|
180
|
+
this.#button.setAttribute('icon', icon);
|
|
181
|
+
}
|
|
182
|
+
this.#button.setAttribute('aria-label', label);
|
|
183
|
+
this.#button.setAttribute('title', label);
|
|
184
|
+
// Forward style passthrough so authors can change variant/size/color
|
|
185
|
+
// at runtime and the internal button reflects it.
|
|
186
|
+
if (this.#button.getAttribute('variant') !== this.variant) {
|
|
187
|
+
this.#button.setAttribute('variant', this.variant || 'ghost');
|
|
188
|
+
}
|
|
189
|
+
if (this.#button.getAttribute('size') !== this.size) {
|
|
190
|
+
this.#button.setAttribute('size', this.size || 'md');
|
|
191
|
+
}
|
|
192
|
+
if (this.color) {
|
|
193
|
+
this.#button.setAttribute('color', this.color);
|
|
194
|
+
} else {
|
|
195
|
+
this.#button.removeAttribute('color');
|
|
196
|
+
}
|
|
197
|
+
if (this.disabled) {
|
|
198
|
+
this.#button.setAttribute('disabled', '');
|
|
199
|
+
} else {
|
|
200
|
+
this.#button.removeAttribute('disabled');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
render() {
|
|
205
|
+
// Property writes after connect (e.g. el.size = 'sm') route through here.
|
|
206
|
+
this.#syncButton();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#emit(source) {
|
|
210
|
+
this.dispatchEvent(new CustomEvent('scheme-change', {
|
|
211
|
+
bubbles: true,
|
|
212
|
+
detail: { scheme: this.activeScheme, source },
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── Internal — target resolution + write ────────────────────────────
|
|
217
|
+
|
|
218
|
+
#resolveTarget() {
|
|
219
|
+
const sel = this.target || ':root';
|
|
220
|
+
if (sel === ':root') return document.documentElement;
|
|
221
|
+
try {
|
|
222
|
+
return document.querySelector(sel) ?? document.documentElement;
|
|
223
|
+
} catch {
|
|
224
|
+
return document.documentElement;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#writeTarget(scheme) {
|
|
229
|
+
const t = this.#resolveTarget();
|
|
230
|
+
t.style.setProperty('color-scheme', scheme);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#clearTargetOverride() {
|
|
234
|
+
const t = this.#resolveTarget();
|
|
235
|
+
t.style.removeProperty('color-scheme');
|
|
236
|
+
if (this.persist) {
|
|
237
|
+
try { localStorage.removeItem(`${this.storagePrefix}scheme`); } catch {}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── Internal — prefers-color-scheme ─────────────────────────────────
|
|
242
|
+
|
|
243
|
+
#resolvePrefersScheme() {
|
|
244
|
+
try {
|
|
245
|
+
if (typeof matchMedia === 'function' &&
|
|
246
|
+
matchMedia('(prefers-color-scheme: dark)').matches) return DARK;
|
|
247
|
+
} catch {}
|
|
248
|
+
return LIGHT;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
#attachPcm() {
|
|
252
|
+
if (this.#mql || typeof matchMedia !== 'function') return;
|
|
253
|
+
try {
|
|
254
|
+
this.#mql = matchMedia('(prefers-color-scheme: dark)');
|
|
255
|
+
this.#mqlHandler = () => {
|
|
256
|
+
const resolved = this.#mql.matches ? DARK : LIGHT;
|
|
257
|
+
if (resolved === this.activeScheme) return;
|
|
258
|
+
this.activeScheme = resolved;
|
|
259
|
+
this.#syncButton();
|
|
260
|
+
this.#emit('media');
|
|
261
|
+
};
|
|
262
|
+
this.#mql.addEventListener('change', this.#mqlHandler);
|
|
263
|
+
} catch {}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#detachPcm() {
|
|
267
|
+
if (this.#mql && this.#mqlHandler) {
|
|
268
|
+
try { this.#mql.removeEventListener('change', this.#mqlHandler); } catch {}
|
|
269
|
+
}
|
|
270
|
+
this.#mql = null;
|
|
271
|
+
this.#mqlHandler = null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
customElements.define('toggle-scheme-ui', UIToggleScheme);
|
|
276
|
+
|
|
277
|
+
export { UIToggleScheme };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Edit this file; run `node scripts/build/components.mjs` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: UIToggleScheme
|
|
4
|
+
tag: toggle-scheme-ui
|
|
5
|
+
component: ToggleScheme
|
|
6
|
+
category: control
|
|
7
|
+
version: 1
|
|
8
|
+
description: >-
|
|
9
|
+
Icon-button primitive that toggles `color-scheme` between light and dark on
|
|
10
|
+
a target element (default `:root`). Encapsulates the prefers-color-scheme
|
|
11
|
+
initial-resolution, the inline-style write to the target, optional
|
|
12
|
+
localStorage persistence (sharing the `adia-theme-` namespace with
|
|
13
|
+
<theme-panel>), and the moon/sun icon swap. Replaces the hand-wired
|
|
14
|
+
`<button-ui id="theme-toggle">` + `applyScheme()` pattern previously
|
|
15
|
+
duplicated across apps/genui, apps/construct-canvas, playgrounds/chat.
|
|
16
|
+
props:
|
|
17
|
+
scheme:
|
|
18
|
+
description: Initial scheme — "auto" follows prefers-color-scheme; "light" / "dark" force.
|
|
19
|
+
type: string
|
|
20
|
+
default: auto
|
|
21
|
+
enum:
|
|
22
|
+
- auto
|
|
23
|
+
- light
|
|
24
|
+
- dark
|
|
25
|
+
reflect: true
|
|
26
|
+
target:
|
|
27
|
+
description: CSS selector for the element receiving `color-scheme`. ":root" → <html>.
|
|
28
|
+
type: string
|
|
29
|
+
default: ":root"
|
|
30
|
+
reflect: true
|
|
31
|
+
persist:
|
|
32
|
+
description: Persist the user's choice to localStorage under `${storage-prefix}scheme`.
|
|
33
|
+
type: boolean
|
|
34
|
+
default: false
|
|
35
|
+
reflect: true
|
|
36
|
+
storagePrefix:
|
|
37
|
+
description: LocalStorage key prefix; default matches <theme-panel> for cross-element interop.
|
|
38
|
+
type: string
|
|
39
|
+
default: "adia-theme-"
|
|
40
|
+
attribute: storage-prefix
|
|
41
|
+
reflect: true
|
|
42
|
+
iconLight:
|
|
43
|
+
description: Phosphor icon shown when active-scheme is "light" (clicking switches to dark).
|
|
44
|
+
type: string
|
|
45
|
+
default: sun
|
|
46
|
+
attribute: icon-light
|
|
47
|
+
reflect: true
|
|
48
|
+
iconDark:
|
|
49
|
+
description: Phosphor icon shown when active-scheme is "dark" (clicking switches to light).
|
|
50
|
+
type: string
|
|
51
|
+
default: moon
|
|
52
|
+
attribute: icon-dark
|
|
53
|
+
reflect: true
|
|
54
|
+
labelLight:
|
|
55
|
+
description: aria-label / title applied to the internal button when active-scheme is "light".
|
|
56
|
+
type: string
|
|
57
|
+
default: "Switch to dark mode"
|
|
58
|
+
attribute: label-light
|
|
59
|
+
reflect: true
|
|
60
|
+
labelDark:
|
|
61
|
+
description: aria-label / title applied to the internal button when active-scheme is "dark".
|
|
62
|
+
type: string
|
|
63
|
+
default: "Switch to light mode"
|
|
64
|
+
attribute: label-dark
|
|
65
|
+
reflect: true
|
|
66
|
+
size:
|
|
67
|
+
description: Sizing scale forwarded to the internal <button-ui>.
|
|
68
|
+
type: string
|
|
69
|
+
default: md
|
|
70
|
+
enum:
|
|
71
|
+
- xs
|
|
72
|
+
- sm
|
|
73
|
+
- md
|
|
74
|
+
- lg
|
|
75
|
+
- xl
|
|
76
|
+
reflect: true
|
|
77
|
+
variant:
|
|
78
|
+
description: Visual style forwarded to the internal <button-ui>. Defaults to "ghost" (matches app practice).
|
|
79
|
+
type: string
|
|
80
|
+
default: ghost
|
|
81
|
+
enum:
|
|
82
|
+
- default
|
|
83
|
+
- solid
|
|
84
|
+
- outline
|
|
85
|
+
- ghost
|
|
86
|
+
- primary
|
|
87
|
+
- secondary
|
|
88
|
+
- soft
|
|
89
|
+
reflect: true
|
|
90
|
+
color:
|
|
91
|
+
description: Semantic intent forwarded to the internal <button-ui>.
|
|
92
|
+
type: string
|
|
93
|
+
default: ""
|
|
94
|
+
enum:
|
|
95
|
+
- ""
|
|
96
|
+
- default
|
|
97
|
+
- accent
|
|
98
|
+
- info
|
|
99
|
+
- success
|
|
100
|
+
- warning
|
|
101
|
+
- danger
|
|
102
|
+
reflect: true
|
|
103
|
+
disabled:
|
|
104
|
+
description: Disables the internal button.
|
|
105
|
+
type: boolean
|
|
106
|
+
default: false
|
|
107
|
+
reflect: true
|
|
108
|
+
activeScheme:
|
|
109
|
+
description: Resolved scheme ("light" | "dark"). Read-only; mutate via .setScheme() or .toggle().
|
|
110
|
+
type: string
|
|
111
|
+
default: ""
|
|
112
|
+
enum:
|
|
113
|
+
- ""
|
|
114
|
+
- light
|
|
115
|
+
- dark
|
|
116
|
+
attribute: active-scheme
|
|
117
|
+
reflect: true
|
|
118
|
+
events:
|
|
119
|
+
scheme-change:
|
|
120
|
+
description: >-
|
|
121
|
+
Fired when the active scheme changes. detail contains
|
|
122
|
+
{ scheme: "light" | "dark", source: "press" | "media" | "programmatic" }.
|
|
123
|
+
slots: {}
|
|
124
|
+
states:
|
|
125
|
+
- name: idle
|
|
126
|
+
description: Default, ready for interaction.
|
|
127
|
+
- name: light
|
|
128
|
+
description: Active scheme is light.
|
|
129
|
+
attribute: active-scheme
|
|
130
|
+
- name: dark
|
|
131
|
+
description: Active scheme is dark.
|
|
132
|
+
attribute: active-scheme
|
|
133
|
+
- name: disabled
|
|
134
|
+
description: Non-interactive; the internal button is disabled.
|
|
135
|
+
attribute: disabled
|
|
136
|
+
traits: []
|
|
137
|
+
tokens:
|
|
138
|
+
--toggle-scheme-icon-transition:
|
|
139
|
+
description: Duration + easing for icon-color transition when scheme flips.
|
|
140
|
+
a2ui:
|
|
141
|
+
rules: []
|
|
142
|
+
anti_patterns: []
|
|
143
|
+
examples:
|
|
144
|
+
- name: header-action
|
|
145
|
+
description: Drop into a toolbar / app header as a single icon-button affordance.
|
|
146
|
+
a2ui: >-
|
|
147
|
+
[
|
|
148
|
+
{
|
|
149
|
+
"id": "root",
|
|
150
|
+
"component": "ToggleScheme",
|
|
151
|
+
"persist": true
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
keywords:
|
|
155
|
+
- scheme
|
|
156
|
+
- color-scheme
|
|
157
|
+
- dark-mode
|
|
158
|
+
- light-mode
|
|
159
|
+
- theme
|
|
160
|
+
- toggle
|
|
161
|
+
- moon
|
|
162
|
+
- sun
|
|
163
|
+
synonyms:
|
|
164
|
+
toggle:
|
|
165
|
+
- toggle-scheme
|
|
166
|
+
- switch
|
|
167
|
+
dark-mode:
|
|
168
|
+
- toggle-scheme
|
|
169
|
+
theme-toggle:
|
|
170
|
+
- toggle-scheme
|
|
171
|
+
related:
|
|
172
|
+
- Button
|
|
173
|
+
- Switch
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<upload-ui>` — File upload dropzone + picker.
|
|
3
|
+
*
|
|
4
|
+
* @see https://ui-kit.exe.xyz/site/components/upload
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { UIFormElement } from '../../core/form.js';
|
|
8
|
+
|
|
9
|
+
export interface UploadChangeEventDetail {
|
|
10
|
+
/** Form value — comma-joined filenames (FormData carries the actual files). */
|
|
11
|
+
value: string;
|
|
12
|
+
/** Selected files as a FileList. */
|
|
13
|
+
files: FileList | File[];
|
|
14
|
+
}
|
|
15
|
+
export type UploadChangeEvent = CustomEvent<UploadChangeEventDetail>;
|
|
16
|
+
|
|
17
|
+
export class UIUpload extends UIFormElement {
|
|
18
|
+
/** Files currently selected. */
|
|
19
|
+
readonly files: FileList | File[];
|
|
20
|
+
|
|
21
|
+
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
22
|
+
type: K,
|
|
23
|
+
listener: (this: UIUpload, ev: HTMLElementEventMap[K]) => unknown,
|
|
24
|
+
options?: boolean | AddEventListenerOptions,
|
|
25
|
+
): void;
|
|
26
|
+
addEventListener(type: 'change', listener: (ev: UploadChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
27
|
+
}
|
|
@@ -168,7 +168,7 @@ class UIUpload extends UIFormElement {
|
|
|
168
168
|
}
|
|
169
169
|
this.internals.setFormValue(fd);
|
|
170
170
|
|
|
171
|
-
this.dispatchEvent(new
|
|
171
|
+
this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value, files: this.files } }));
|
|
172
172
|
this.render();
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UIElement — light-DOM reactive base class.
|
|
3
|
+
*
|
|
4
|
+
* Every primitive in `@adia-ai/web-components` extends this (or `UIFormElement`
|
|
5
|
+
* for form-bearing primitives). Provides signal-backed property reactivity,
|
|
6
|
+
* lifecycle hooks (`connected`/`render`/`updated`/`disconnected`), and
|
|
7
|
+
* tagged-template rendering.
|
|
8
|
+
*
|
|
9
|
+
* @see ../USAGE.md#lifecycle
|
|
10
|
+
* @see ../USAGE.md#property-reactivity-signal-backed
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ReadonlySignal, Signal } from './signals.js';
|
|
14
|
+
import type { TemplateResult } from './template.js';
|
|
15
|
+
|
|
16
|
+
/** Property declaration in a component's `static properties = { … }` map. */
|
|
17
|
+
export interface PropertyConfig<T = unknown> {
|
|
18
|
+
/** Coercion target — `Boolean`, `Number`, `String`, or custom. */
|
|
19
|
+
type?: BooleanConstructor | NumberConstructor | StringConstructor | (new (...args: unknown[]) => T);
|
|
20
|
+
/** Initial value. Defaults to `undefined`. */
|
|
21
|
+
default?: T;
|
|
22
|
+
/** Mirror property changes to an HTML attribute. */
|
|
23
|
+
reflect?: boolean;
|
|
24
|
+
/** Custom attribute name. Default: kebab-case of property key. */
|
|
25
|
+
attribute?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Map of property name → config. The shape consumed by `static properties`. */
|
|
29
|
+
export type PropertiesMap = Record<string, PropertyConfig>;
|
|
30
|
+
|
|
31
|
+
/** Trait name + config tuple passed via `static traits`. */
|
|
32
|
+
export type TraitDeclaration = string | { name: string; [opt: string]: unknown };
|
|
33
|
+
|
|
34
|
+
/** Map of slot name → markup string. Consumed by `static parts`. */
|
|
35
|
+
export type PartsMap = Record<string, string>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Light-DOM reactive base class. Subclass to author a primitive.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* class MyWidget extends UIElement {
|
|
42
|
+
* static properties = { label: { type: String, default: '' } };
|
|
43
|
+
* render() { this.textContent = this.label; }
|
|
44
|
+
* }
|
|
45
|
+
* customElements.define('my-widget', MyWidget);
|
|
46
|
+
*/
|
|
47
|
+
export class UIElement extends HTMLElement {
|
|
48
|
+
/**
|
|
49
|
+
* Property declarations — signal-backed getters/setters wired by the
|
|
50
|
+
* constructor. Override in subclasses.
|
|
51
|
+
*/
|
|
52
|
+
static properties: PropertiesMap;
|
|
53
|
+
|
|
54
|
+
/** Trait declarations — applied during `connectedCallback`. */
|
|
55
|
+
static traits: readonly TraitDeclaration[];
|
|
56
|
+
|
|
57
|
+
/** Slot blueprints used by `ensure(slotName)`. */
|
|
58
|
+
static parts?: PartsMap;
|
|
59
|
+
|
|
60
|
+
/** Stylesheets to adopt onto the document on first render. */
|
|
61
|
+
static styles?: CSSStyleSheet | readonly CSSStyleSheet[];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Pure-function template — called inside the host's render effect. Reading
|
|
65
|
+
* `this.foo` inside subscribes the effect to the `foo` signal.
|
|
66
|
+
*
|
|
67
|
+
* Override OR return `null` and update DOM imperatively in `render()`.
|
|
68
|
+
*/
|
|
69
|
+
static template: <T extends UIElement>(host: T) => TemplateResult | null;
|
|
70
|
+
|
|
71
|
+
/** Observed attribute names — derived from `static properties`. */
|
|
72
|
+
static readonly observedAttributes: readonly string[];
|
|
73
|
+
|
|
74
|
+
/** ElementInternals — attached in the constructor; carries form state etc. */
|
|
75
|
+
readonly internals: ElementInternals;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Setup hook — called after construction + insertion, before the first render
|
|
79
|
+
* effect installs. Set up listeners on document / external targets here.
|
|
80
|
+
*
|
|
81
|
+
* Override in subclasses; default is a no-op. Runs inside `untracked()` so
|
|
82
|
+
* reads don't leak subscriptions to outer effects.
|
|
83
|
+
*/
|
|
84
|
+
connected(): void;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Imperative update hook — called every time any property signal mutates.
|
|
88
|
+
* Reading `this.foo` here subscribes the effect to `foo`.
|
|
89
|
+
*
|
|
90
|
+
* Override in subclasses; default is a no-op.
|
|
91
|
+
*/
|
|
92
|
+
render(): void;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Post-render hook — called after `render()`. `changed` is a map of every
|
|
96
|
+
* property that mutated this tick, keyed by property name with old value.
|
|
97
|
+
*
|
|
98
|
+
* Override in subclasses; default is a no-op.
|
|
99
|
+
*/
|
|
100
|
+
updated(changed: ReadonlyMap<string, unknown>): void;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Teardown hook — called on element removal. Clean up listeners + intervals
|
|
104
|
+
* + observers here.
|
|
105
|
+
*
|
|
106
|
+
* Override in subclasses; default is a no-op.
|
|
107
|
+
*/
|
|
108
|
+
disconnected(): void;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Error hook — called when the host's render effect throws. Receives the
|
|
112
|
+
* thrown value. Default behavior: rethrow.
|
|
113
|
+
*/
|
|
114
|
+
onError?(err: unknown): void;
|
|
115
|
+
|
|
116
|
+
// ── Standard custom-element callbacks (overridable but rarely needed) ──
|
|
117
|
+
|
|
118
|
+
connectedCallback(): void;
|
|
119
|
+
disconnectedCallback(): void;
|
|
120
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
121
|
+
|
|
122
|
+
// ── Convenience helpers ──
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create a local signal bound to this element's lifetime. Disposed on
|
|
126
|
+
* `disconnectedCallback`.
|
|
127
|
+
*/
|
|
128
|
+
signal<T>(initial: T): Signal<T>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get or stamp a slot child by name. If a `[slot="name"]` child exists,
|
|
132
|
+
* returns it; otherwise stamps the blueprint from `static parts[name]`,
|
|
133
|
+
* appends it, and returns the new node.
|
|
134
|
+
*/
|
|
135
|
+
ensure(slotName: string): Element | null;
|
|
136
|
+
|
|
137
|
+
/** Remove the `[slot="name"]` child if present. */
|
|
138
|
+
drop(slotName: string): void;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Keyed-reconcile a parent's children against `items`. Used internally by
|
|
142
|
+
* the `repeat` directive; consumers rarely call directly.
|
|
143
|
+
*/
|
|
144
|
+
reconcile<T>(
|
|
145
|
+
parent: Element,
|
|
146
|
+
items: readonly T[],
|
|
147
|
+
keyFn: (item: T, i: number) => string | number,
|
|
148
|
+
stampFn: (item: T, i: number, existing: Element | null) => Element,
|
|
149
|
+
): void;
|
|
150
|
+
|
|
151
|
+
// ── Controller pattern (optional advanced) ──
|
|
152
|
+
|
|
153
|
+
/** Set the controller; calls connect/disconnect on it through lifecycle. */
|
|
154
|
+
set controller(c: UIController | null);
|
|
155
|
+
get controller(): UIController | null;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Factory — create an instance of this element with the given props applied.
|
|
159
|
+
* Tag must already be registered.
|
|
160
|
+
*/
|
|
161
|
+
static create<T extends typeof UIElement>(
|
|
162
|
+
this: T,
|
|
163
|
+
props?: Partial<InstanceType<T>>,
|
|
164
|
+
): InstanceType<T>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Controller — optional pluggable behavior attached to a UIElement via
|
|
169
|
+
* `element.controller = …`. Lifecycle mirrors the host.
|
|
170
|
+
*/
|
|
171
|
+
export interface UIController {
|
|
172
|
+
connect?(host: UIElement): void;
|
|
173
|
+
disconnect?(host: UIElement): void;
|
|
174
|
+
}
|