@adia-ai/web-modules 0.8.12 → 0.8.14
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/CHANGELOG.md +26 -0
- package/dist/everything.min.js +73 -73
- package/dist/shell/admin-shell.min.css +1 -1
- package/dist/web-modules.min.css +1 -1
- package/dist/web-modules.sheet.js +1 -1
- package/package.json +1 -1
- package/shell/admin-shell/css/admin-shell.collapsed.css +46 -15
- package/shell/admin-shell/css/admin-shell.tokens.css +1 -1
- package/theme/theme-panel/theme-panel.a2ui.json +17 -10
- package/theme/theme-panel/theme-panel.css +23 -10
- package/theme/theme-panel/theme-panel.d.ts +28 -14
- package/theme/theme-panel/theme-panel.js +191 -104
- package/theme/theme-panel/theme-panel.test.js +203 -46
- package/theme/theme-panel/theme-panel.yaml +56 -24
|
@@ -57,13 +57,39 @@ describe('theme-panel', () => {
|
|
|
57
57
|
expect(customElements.get('theme-panel')).toBeDefined();
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it('
|
|
60
|
+
it('wraps its stamped content in <theme-provider scale="ui-md">, no [theme]', async () => {
|
|
61
61
|
const tp = mount('<theme-panel></theme-panel>');
|
|
62
62
|
await tick();
|
|
63
|
-
const
|
|
64
|
-
expect(
|
|
65
|
-
|
|
66
|
-
expect(
|
|
63
|
+
const provider = tp.querySelector(':scope > theme-provider');
|
|
64
|
+
expect(provider).toBeTruthy();
|
|
65
|
+
expect(provider.getAttribute('scale')).toBe('ui-md');
|
|
66
|
+
expect(provider.hasAttribute('theme')).toBe(false);
|
|
67
|
+
// Real content lives inside the provider, not as a direct theme-panel child.
|
|
68
|
+
expect(provider.querySelector('select-ui[part="themes"]')).toBeTruthy();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('renders a Theme select with the 12 default identities by default', async () => {
|
|
72
|
+
const tp = mount('<theme-panel></theme-panel>');
|
|
73
|
+
await tick();
|
|
74
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
75
|
+
expect(select).toBeTruthy();
|
|
76
|
+
const values = [...select.querySelectorAll('option')].map((o) => o.value);
|
|
77
|
+
expect(values).toEqual(['', 'atlas', 'signal', 'terrarium', 'ledger', 'arcade', 'meridian', 'solstice', 'glacier', 'ember', 'botanica', 'vector', 'velour']);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('[themes=""] suppresses the Theme row entirely', async () => {
|
|
81
|
+
const tp = mount('<theme-panel themes=""></theme-panel>');
|
|
82
|
+
await tick();
|
|
83
|
+
expect(tp.querySelector('[part="themes"]')).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('[themes="…"] renders a Theme select with exactly the given slugs plus Default', async () => {
|
|
87
|
+
const tp = mount('<theme-panel themes="terrarium vector"></theme-panel>');
|
|
88
|
+
await tick();
|
|
89
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
90
|
+
expect(select).toBeTruthy();
|
|
91
|
+
const values = [...select.querySelectorAll('option')].map((o) => o.value);
|
|
92
|
+
expect(values).toEqual(['', 'terrarium', 'vector']);
|
|
67
93
|
});
|
|
68
94
|
|
|
69
95
|
it('renders only minimum surface by default (no sliders, presets, scheme)', async () => {
|
|
@@ -96,37 +122,41 @@ describe('theme-panel', () => {
|
|
|
96
122
|
expect(tp.querySelector('[part="scheme"] segmented-ui')).toBeTruthy();
|
|
97
123
|
});
|
|
98
124
|
|
|
99
|
-
it('
|
|
100
|
-
const tp = mount('<theme-panel></theme-panel>');
|
|
125
|
+
it('selecting a theme sets [theme] on the target (defaults to <html>)', async () => {
|
|
126
|
+
const tp = mount('<theme-panel themes="atlas ledger terrarium"></theme-panel>');
|
|
101
127
|
await tick();
|
|
102
|
-
const
|
|
103
|
-
|
|
128
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
129
|
+
select.value = 'ledger';
|
|
130
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'ledger' } }));
|
|
104
131
|
await tick();
|
|
105
|
-
expect(document.documentElement.getAttribute('theme')).toBe('
|
|
106
|
-
expect(tp.getAttribute('active-theme')).toBe('
|
|
132
|
+
expect(document.documentElement.getAttribute('theme')).toBe('ledger');
|
|
133
|
+
expect(tp.getAttribute('active-theme')).toBe('ledger');
|
|
107
134
|
});
|
|
108
135
|
|
|
109
|
-
it('
|
|
110
|
-
document.documentElement.setAttribute('theme', '
|
|
111
|
-
const tp = mount('<theme-panel></theme-panel>');
|
|
136
|
+
it('selecting the Default option clears [theme]', async () => {
|
|
137
|
+
document.documentElement.setAttribute('theme', 'ledger');
|
|
138
|
+
const tp = mount('<theme-panel themes="atlas ledger"></theme-panel>');
|
|
112
139
|
await tick();
|
|
113
|
-
const
|
|
114
|
-
|
|
140
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
141
|
+
select.value = '';
|
|
142
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '' } }));
|
|
115
143
|
await tick();
|
|
116
144
|
expect(document.documentElement.hasAttribute('theme')).toBe(false);
|
|
117
145
|
expect(tp.getAttribute('active-theme')).toBe('');
|
|
118
146
|
});
|
|
119
147
|
|
|
120
|
-
it('fires theme-change event with source: "theme" on
|
|
121
|
-
const tp = mount('<theme-panel></theme-panel>');
|
|
148
|
+
it('fires theme-change event with source: "theme" on selection', async () => {
|
|
149
|
+
const tp = mount('<theme-panel themes="terrarium"></theme-panel>');
|
|
122
150
|
await tick();
|
|
123
151
|
const onChange = vi.fn();
|
|
124
152
|
tp.addEventListener('theme-change', onChange);
|
|
125
|
-
tp.querySelector('
|
|
153
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
154
|
+
select.value = 'terrarium';
|
|
155
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'terrarium' } }));
|
|
126
156
|
await tick();
|
|
127
157
|
expect(onChange).toHaveBeenCalled();
|
|
128
158
|
const detail = onChange.mock.calls[0][0].detail;
|
|
129
|
-
expect(detail.theme).toBe('
|
|
159
|
+
expect(detail.theme).toBe('terrarium');
|
|
130
160
|
expect(detail.source).toBe('theme');
|
|
131
161
|
});
|
|
132
162
|
|
|
@@ -166,34 +196,55 @@ describe('theme-panel', () => {
|
|
|
166
196
|
expect(document.documentElement.style.getPropertyValue('--a-radius-k')).toBe('1.25');
|
|
167
197
|
});
|
|
168
198
|
|
|
169
|
-
it('preset "default"
|
|
170
|
-
document.documentElement.setAttribute('theme', '
|
|
199
|
+
it('preset "default" only sets density/radius — it does NOT clear [theme]', async () => {
|
|
200
|
+
document.documentElement.setAttribute('theme', 'ledger');
|
|
171
201
|
const tp = mount('<theme-panel parametric presets></theme-panel>');
|
|
172
202
|
await tick();
|
|
173
|
-
const
|
|
174
|
-
|
|
203
|
+
const defaultBtn = tp.querySelector('button-ui[data-preset="default"]');
|
|
204
|
+
expect(defaultBtn.getAttribute('text')).toBe('Default');
|
|
205
|
+
defaultBtn.dispatchEvent(new Event('click', { bubbles: true }));
|
|
206
|
+
await tick();
|
|
207
|
+
expect(document.documentElement.getAttribute('theme')).toBe('ledger');
|
|
208
|
+
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('1');
|
|
209
|
+
expect(document.documentElement.style.getPropertyValue('--a-radius-k')).toBe('1');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('the Reset row is a dedicated bottom control, separate from the Preset row', async () => {
|
|
213
|
+
document.documentElement.setAttribute('theme', 'ledger');
|
|
214
|
+
document.documentElement.style.setProperty('--a-density', '1.2');
|
|
215
|
+
const tp = mount('<theme-panel presets></theme-panel>');
|
|
216
|
+
await tick();
|
|
217
|
+
const resetBtn = tp.querySelector('[part="reset-row"] button-ui[part="reset"]');
|
|
218
|
+
expect(resetBtn).toBeTruthy();
|
|
219
|
+
expect(resetBtn.getAttribute('text')).toBe('Reset');
|
|
220
|
+
resetBtn.dispatchEvent(new Event('click', { bubbles: true }));
|
|
175
221
|
await tick();
|
|
176
222
|
expect(document.documentElement.hasAttribute('theme')).toBe(false);
|
|
223
|
+
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('');
|
|
177
224
|
});
|
|
178
225
|
|
|
179
|
-
it('[persist] writes localStorage keys on theme
|
|
180
|
-
const tp = mount('<theme-panel persist></theme-panel>');
|
|
226
|
+
it('[persist] writes localStorage keys on theme selection', async () => {
|
|
227
|
+
const tp = mount('<theme-panel persist themes="atlas"></theme-panel>');
|
|
181
228
|
await tick();
|
|
182
|
-
tp.querySelector('
|
|
229
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
230
|
+
select.value = 'atlas';
|
|
231
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'atlas' } }));
|
|
183
232
|
await tick();
|
|
184
|
-
expect(localStorage.getItem('adia-theme-theme')).toBe('
|
|
233
|
+
expect(localStorage.getItem('adia-theme-theme')).toBe('atlas');
|
|
185
234
|
});
|
|
186
235
|
|
|
187
236
|
it('absence of [persist] does NOT write localStorage', async () => {
|
|
188
|
-
const tp = mount('<theme-panel></theme-panel>');
|
|
237
|
+
const tp = mount('<theme-panel themes="atlas"></theme-panel>');
|
|
189
238
|
await tick();
|
|
190
|
-
tp.querySelector('
|
|
239
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
240
|
+
select.value = 'atlas';
|
|
241
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'atlas' } }));
|
|
191
242
|
await tick();
|
|
192
243
|
expect(localStorage.getItem('adia-theme-theme')).toBeNull();
|
|
193
244
|
});
|
|
194
245
|
|
|
195
|
-
it('reset() clears all state and emits source: "reset"', async () => {
|
|
196
|
-
document.documentElement.setAttribute('theme', '
|
|
246
|
+
it('reset() clears all state, resets the Theme select to Default, and emits source: "reset"', async () => {
|
|
247
|
+
document.documentElement.setAttribute('theme', 'ledger');
|
|
197
248
|
document.documentElement.style.setProperty('--a-density', '1.2');
|
|
198
249
|
const tp = mount('<theme-panel parametric persist></theme-panel>');
|
|
199
250
|
await tick();
|
|
@@ -203,27 +254,31 @@ describe('theme-panel', () => {
|
|
|
203
254
|
await tick();
|
|
204
255
|
expect(document.documentElement.hasAttribute('theme')).toBe(false);
|
|
205
256
|
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('');
|
|
257
|
+
expect(tp.querySelector('select-ui[part="themes"]').value).toBe('');
|
|
206
258
|
expect(onChange).toHaveBeenCalled();
|
|
207
259
|
expect(onChange.mock.calls[0][0].detail.source).toBe('reset');
|
|
208
260
|
});
|
|
209
261
|
|
|
210
262
|
it('[storage-prefix] override changes LS key namespace', async () => {
|
|
211
|
-
const tp = mount('<theme-panel persist storage-prefix="my-app-"></theme-panel>');
|
|
263
|
+
const tp = mount('<theme-panel persist storage-prefix="my-app-" themes="atlas"></theme-panel>');
|
|
212
264
|
await tick();
|
|
213
|
-
tp.querySelector('
|
|
265
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
266
|
+
select.value = 'atlas';
|
|
267
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'atlas' } }));
|
|
214
268
|
await tick();
|
|
215
|
-
expect(localStorage.getItem('my-app-theme')).toBe('
|
|
269
|
+
expect(localStorage.getItem('my-app-theme')).toBe('atlas');
|
|
216
270
|
expect(localStorage.getItem('adia-theme-theme')).toBeNull();
|
|
217
271
|
});
|
|
218
272
|
|
|
219
|
-
it('apply({theme}) is equivalent to a theme
|
|
273
|
+
it('apply({theme}) is equivalent to a theme selection, syncs the select, and emits source: "programmatic"', async () => {
|
|
220
274
|
const tp = mount('<theme-panel></theme-panel>');
|
|
221
275
|
await tick();
|
|
222
276
|
const onChange = vi.fn();
|
|
223
277
|
tp.addEventListener('theme-change', onChange);
|
|
224
|
-
tp.apply({ theme: '
|
|
278
|
+
tp.apply({ theme: 'velour' });
|
|
225
279
|
await tick();
|
|
226
|
-
expect(document.documentElement.getAttribute('theme')).toBe('
|
|
280
|
+
expect(document.documentElement.getAttribute('theme')).toBe('velour');
|
|
281
|
+
expect(tp.querySelector('select-ui[part="themes"]').value).toBe('velour');
|
|
227
282
|
expect(onChange.mock.calls[0][0].detail.source).toBe('programmatic');
|
|
228
283
|
});
|
|
229
284
|
|
|
@@ -247,11 +302,13 @@ describe('theme-panel', () => {
|
|
|
247
302
|
const preview = document.createElement('div');
|
|
248
303
|
preview.id = 'preview';
|
|
249
304
|
document.body.appendChild(preview);
|
|
250
|
-
const tp = mount('<theme-panel target="#preview"></theme-panel>');
|
|
305
|
+
const tp = mount('<theme-panel target="#preview" themes="atlas"></theme-panel>');
|
|
251
306
|
await tick();
|
|
252
|
-
tp.querySelector('
|
|
307
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
308
|
+
select.value = 'atlas';
|
|
309
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'atlas' } }));
|
|
253
310
|
await tick();
|
|
254
|
-
expect(preview.getAttribute('theme')).toBe('
|
|
311
|
+
expect(preview.getAttribute('theme')).toBe('atlas');
|
|
255
312
|
expect(document.documentElement.getAttribute('theme')).toBeNull();
|
|
256
313
|
});
|
|
257
314
|
|
|
@@ -264,11 +321,111 @@ describe('theme-panel', () => {
|
|
|
264
321
|
expect(mqlListeners.length).toBe(0);
|
|
265
322
|
});
|
|
266
323
|
|
|
267
|
-
it('
|
|
268
|
-
const tp = mount('<theme-panel themes="
|
|
324
|
+
it('tolerates unknown slugs — renders an option and applies [theme] regardless', async () => {
|
|
325
|
+
const tp = mount('<theme-panel themes="brand-a"></theme-panel>');
|
|
326
|
+
await tick();
|
|
327
|
+
const select = tp.querySelector('select-ui[part="themes"]');
|
|
328
|
+
expect([...select.querySelectorAll('option')].map((o) => o.value)).toEqual(['', 'brand-a']);
|
|
329
|
+
select.value = 'brand-a';
|
|
330
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'brand-a' } }));
|
|
331
|
+
await tick();
|
|
332
|
+
expect(document.documentElement.getAttribute('theme')).toBe('brand-a');
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('[presets] labels its row "Preset", not "Density" — disambiguates from the Density slider row', async () => {
|
|
336
|
+
const tp = mount('<theme-panel parametric presets></theme-panel>');
|
|
337
|
+
await tick();
|
|
338
|
+
// The Density slider's label lives on slider-ui's own [label] attribute
|
|
339
|
+
// (its native header-row readout); the section labels (Parametric/
|
|
340
|
+
// Preset/Scale) are standalone text-ui rows.
|
|
341
|
+
expect(tp.querySelector('slider-ui[part="density"][label="Density"]')).toBeTruthy();
|
|
342
|
+
const sectionLabels = [...tp.querySelectorAll('text-ui[variant="label"]')].map((l) => l.textContent);
|
|
343
|
+
expect(sectionLabels).toContain('Preset');
|
|
344
|
+
expect(sectionLabels).not.toContain('Density');
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe('theme-panel — [register] Scale row (six [scale] tiers, supersedes verse/prose)', () => {
|
|
349
|
+
it('renders a select with the six canonical tiers plus a Default option', async () => {
|
|
350
|
+
const tp = mount('<theme-panel register></theme-panel>');
|
|
351
|
+
await tick();
|
|
352
|
+
const select = tp.querySelector('select-ui[part="scale"]');
|
|
353
|
+
expect(select).toBeTruthy();
|
|
354
|
+
const values = [...select.querySelectorAll('option')].map((o) => o.value);
|
|
355
|
+
expect(values).toEqual(['', 'ui-sm', 'ui-md', 'ui-lg', 'content-sm', 'content-md', 'content-lg']);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('does not render the Scale row without [register]', async () => {
|
|
359
|
+
const tp = mount('<theme-panel presets></theme-panel>');
|
|
360
|
+
await tick();
|
|
361
|
+
expect(tp.querySelector('select-ui[part="scale"]')).toBeNull();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('selecting a tier sets [scale="…"] on the target, never [verse]/[prose]', async () => {
|
|
365
|
+
const tp = mount('<theme-panel register></theme-panel>');
|
|
366
|
+
await tick();
|
|
367
|
+
const select = tp.querySelector('select-ui[part="scale"]');
|
|
368
|
+
select.value = 'content-md';
|
|
369
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'content-md' } }));
|
|
370
|
+
await tick();
|
|
371
|
+
expect(document.documentElement.getAttribute('scale')).toBe('content-md');
|
|
372
|
+
expect(document.documentElement.hasAttribute('verse')).toBe(false);
|
|
373
|
+
expect(document.documentElement.hasAttribute('prose')).toBe(false);
|
|
374
|
+
expect(tp.getAttribute('active-scale')).toBe('content-md');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('selecting the Default option clears [scale] on the target', async () => {
|
|
378
|
+
const tp = mount('<theme-panel register></theme-panel>');
|
|
379
|
+
await tick();
|
|
380
|
+
const select = tp.querySelector('select-ui[part="scale"]');
|
|
381
|
+
select.value = 'ui-lg';
|
|
382
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'ui-lg' } }));
|
|
383
|
+
await tick();
|
|
384
|
+
expect(document.documentElement.getAttribute('scale')).toBe('ui-lg');
|
|
385
|
+
|
|
386
|
+
select.value = '';
|
|
387
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '' } }));
|
|
388
|
+
await tick();
|
|
389
|
+
expect(document.documentElement.hasAttribute('scale')).toBe(false);
|
|
390
|
+
expect(tp.getAttribute('active-scale')).toBe('');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('apply({scale}) writes [scale] and emits source: "scale"', async () => {
|
|
394
|
+
const tp = mount('<theme-panel register></theme-panel>');
|
|
395
|
+
await tick();
|
|
396
|
+
const onChange = vi.fn();
|
|
397
|
+
tp.addEventListener('theme-change', onChange);
|
|
398
|
+
tp.apply({ scale: 'ui-sm' });
|
|
399
|
+
await tick();
|
|
400
|
+
expect(document.documentElement.getAttribute('scale')).toBe('ui-sm');
|
|
401
|
+
expect(onChange.mock.calls[0][0].detail.scale).toBe('ui-sm');
|
|
402
|
+
expect(onChange.mock.calls[0][0].detail.source).toBe('programmatic');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('[persist] round-trips the scale tier through localStorage on reconnect', async () => {
|
|
406
|
+
const first = mount('<theme-panel register persist></theme-panel>');
|
|
407
|
+
await tick();
|
|
408
|
+
const select = first.querySelector('select-ui[part="scale"]');
|
|
409
|
+
select.value = 'content-lg';
|
|
410
|
+
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'content-lg' } }));
|
|
411
|
+
await tick();
|
|
412
|
+
expect(localStorage.getItem('adia-theme-scale')).toBe('content-lg');
|
|
413
|
+
|
|
414
|
+
first.remove();
|
|
415
|
+
document.documentElement.removeAttribute('scale');
|
|
416
|
+
const second = mount('<theme-panel register persist></theme-panel>');
|
|
417
|
+
await tick();
|
|
418
|
+
expect(document.documentElement.getAttribute('scale')).toBe('content-lg');
|
|
419
|
+
expect(second.getAttribute('active-scale')).toBe('content-lg');
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('reset() clears [scale] and resets the select to Default', async () => {
|
|
423
|
+
document.documentElement.setAttribute('scale', 'ui-md');
|
|
424
|
+
const tp = mount('<theme-panel register></theme-panel>');
|
|
425
|
+
await tick();
|
|
426
|
+
tp.reset();
|
|
269
427
|
await tick();
|
|
270
|
-
|
|
271
|
-
expect(
|
|
272
|
-
expect(buttons.map((b) => b.getAttribute('data-theme-slug'))).toEqual(['default', 'ocean']);
|
|
428
|
+
expect(document.documentElement.hasAttribute('scale')).toBe(false);
|
|
429
|
+
expect(tp.querySelector('select-ui[part="scale"]').value).toBe('');
|
|
273
430
|
});
|
|
274
431
|
});
|
|
@@ -14,9 +14,12 @@ description: |
|
|
|
14
14
|
small attribute API.
|
|
15
15
|
|
|
16
16
|
Drops into any consumer's <popover-ui slot="content"> (the canonical
|
|
17
|
-
composition) or directly into a sidebar section.
|
|
18
|
-
ship by default
|
|
19
|
-
|
|
17
|
+
composition) or directly into a sidebar section. The 12 named identities
|
|
18
|
+
in styles/themes.css ship by default (a <select-ui> Theme row); pass
|
|
19
|
+
[themes="slug1 slug2"] to restrict, or "" to suppress the row entirely.
|
|
20
|
+
Section visibility flips on/off via boolean attributes ([parametric],
|
|
21
|
+
[presets], [register], [scheme-toggle]); a Reset row (own button, bottom
|
|
22
|
+
of the panel) always renders and clears everything via .reset().
|
|
20
23
|
|
|
21
24
|
Promoted from the duplicated <div id="theme-panel"> block in site/ and
|
|
22
25
|
playgrounds/admin-shell/ — see .claude/docs/specs/theme-panel-module.md.
|
|
@@ -26,20 +29,26 @@ description: |
|
|
|
26
29
|
composes:
|
|
27
30
|
- segmented-ui
|
|
28
31
|
- segment-ui
|
|
32
|
+
- select-ui
|
|
29
33
|
- text-ui
|
|
30
34
|
- button-ui
|
|
31
35
|
- divider-ui
|
|
32
|
-
- field-ui
|
|
33
36
|
- slider-ui
|
|
37
|
+
- theme-provider
|
|
34
38
|
props:
|
|
35
39
|
themes:
|
|
36
40
|
description: |
|
|
37
|
-
Space-separated list of theme slugs to render
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
Space-separated list of theme slugs to render in the Theme
|
|
42
|
+
<select-ui>. Defaults to the 12 named identities shipped in
|
|
43
|
+
styles/themes.css — atlas, signal, terrarium, ledger, arcade,
|
|
44
|
+
meridian, solstice, glacier, ember, botanica, vector, velour
|
|
45
|
+
(each a real recolor + real Google/system font pairing, not a
|
|
46
|
+
decorative hue nudge — see that file's header). Pass "" to
|
|
47
|
+
suppress the Theme row entirely. Tolerant — unknown slugs still
|
|
48
|
+
render as an option; selecting one applies [theme=slug] regardless
|
|
49
|
+
of whether themes.css has a matching block.
|
|
41
50
|
type: string
|
|
42
|
-
default: "
|
|
51
|
+
default: "atlas signal terrarium ledger arcade meridian solstice glacier ember botanica vector velour"
|
|
43
52
|
reflect: true
|
|
44
53
|
|
|
45
54
|
parametric:
|
|
@@ -52,19 +61,24 @@ props:
|
|
|
52
61
|
|
|
53
62
|
presets:
|
|
54
63
|
description: |
|
|
55
|
-
Renders the compact /
|
|
56
|
-
applies a (density, radius) pair
|
|
57
|
-
[
|
|
64
|
+
Renders the compact / default / spacious preset row. Each preset
|
|
65
|
+
applies a (density, radius) pair only — it does not touch [theme],
|
|
66
|
+
[scheme], or [scale]. Full state clearing lives in the panel's
|
|
67
|
+
always-rendered Reset row (bottom, own button, calls .reset()).
|
|
58
68
|
type: boolean
|
|
59
69
|
default: false
|
|
60
70
|
reflect: true
|
|
61
71
|
|
|
62
72
|
register:
|
|
63
73
|
description: |
|
|
64
|
-
Renders a "Scale
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
Renders a "Scale" row — a <select-ui> offering the six ancestor
|
|
75
|
+
[scale] tiers (ui-sm | ui-md | ui-lg | content-sm | content-md |
|
|
76
|
+
content-lg, plus a Default option that clears the attribute) —
|
|
77
|
+
writing [scale="<tier>"] directly on the target element, matching
|
|
78
|
+
theme-provider.class.js's own `scale` prop values exactly.
|
|
79
|
+
Supersedes the old verse/regular/prose register (deprecated
|
|
80
|
+
[verse]/[prose] aliases); this control never writes them.
|
|
81
|
+
Persisted like the other knobs when [persist] is set.
|
|
68
82
|
type: boolean
|
|
69
83
|
default: false
|
|
70
84
|
reflect: true
|
|
@@ -90,9 +104,9 @@ props:
|
|
|
90
104
|
|
|
91
105
|
storage-prefix:
|
|
92
106
|
description: |
|
|
93
|
-
Namespace for localStorage keys when [persist] is set.
|
|
107
|
+
Namespace for localStorage keys when [persist] is set. Five keys
|
|
94
108
|
are written: {prefix}theme, {prefix}scheme, {prefix}density,
|
|
95
|
-
{prefix}radius.
|
|
109
|
+
{prefix}radius, {prefix}scale.
|
|
96
110
|
type: string
|
|
97
111
|
default: "adia-theme-"
|
|
98
112
|
reflect: true
|
|
@@ -155,14 +169,14 @@ props:
|
|
|
155
169
|
reflect: true
|
|
156
170
|
attribute: active-radius
|
|
157
171
|
|
|
158
|
-
active-
|
|
172
|
+
active-scale:
|
|
159
173
|
description: |
|
|
160
|
-
Reflected — the active
|
|
161
|
-
|
|
174
|
+
Reflected — the active [scale] tier on the target element; empty
|
|
175
|
+
for the unscaled default (no [scale] attribute set).
|
|
162
176
|
type: string
|
|
163
177
|
default: ""
|
|
164
178
|
reflect: true
|
|
165
|
-
attribute: active-
|
|
179
|
+
attribute: active-scale
|
|
166
180
|
|
|
167
181
|
events:
|
|
168
182
|
theme-change:
|
|
@@ -175,6 +189,7 @@ events:
|
|
|
175
189
|
scheme: string
|
|
176
190
|
density: number
|
|
177
191
|
radius: number
|
|
192
|
+
scale: string
|
|
178
193
|
source: string
|
|
179
194
|
|
|
180
195
|
slots: {}
|
|
@@ -182,6 +197,21 @@ slots: {}
|
|
|
182
197
|
states:
|
|
183
198
|
- name: idle
|
|
184
199
|
description: Default — panel rendered, awaiting input.
|
|
200
|
+
- name: fixed-scale
|
|
201
|
+
description: |
|
|
202
|
+
The panel's stamped content is wrapped in an internal
|
|
203
|
+
<theme-provider scale="ui-md"> (display:contents, adds no box) —
|
|
204
|
+
pins the panel's OWN chrome to the base size/scale register,
|
|
205
|
+
independent of whatever [scale]/--a-density the Scale select or
|
|
206
|
+
Density slider is driving on the target. Target defaults to :root,
|
|
207
|
+
a DOM ancestor of theme-panel, so those inherited custom properties
|
|
208
|
+
would otherwise cascade into the panel's own controls too. A visual
|
|
209
|
+
no-op under the unscaled default (ui-md is byte-identical to it,
|
|
210
|
+
scale.css's own invariant; --a-density/--a-radius-k pinned to the
|
|
211
|
+
"compact" preset's values, declared on the provider in
|
|
212
|
+
theme-panel.css) — only matters once an ancestor sets a different
|
|
213
|
+
tier. Deliberately does not set [theme] on the provider — the
|
|
214
|
+
panel's own accent color tracks whatever theme is active.
|
|
185
215
|
- name: persisted
|
|
186
216
|
description: |
|
|
187
217
|
[persist] is set; selections write to localStorage under
|
|
@@ -207,8 +237,9 @@ a2ui:
|
|
|
207
237
|
[persist] for playgrounds and embedded demos so state stays
|
|
208
238
|
ephemeral. The default is ephemeral.
|
|
209
239
|
- >-
|
|
210
|
-
Add boolean attributes [parametric], [presets], [
|
|
211
|
-
to enable sections; the minimum panel is the theme
|
|
240
|
+
Add boolean attributes [parametric], [presets], [register],
|
|
241
|
+
[scheme-toggle] to enable sections; the minimum panel is the theme
|
|
242
|
+
button grid (empty until [themes] is supplied — no default list).
|
|
212
243
|
|
|
213
244
|
keywords:
|
|
214
245
|
- theme
|
|
@@ -216,6 +247,7 @@ keywords:
|
|
|
216
247
|
- palette
|
|
217
248
|
- density
|
|
218
249
|
- radius
|
|
250
|
+
- scale
|
|
219
251
|
- scheme
|
|
220
252
|
- dark-mode
|
|
221
253
|
- light-mode
|