@ponchia/ui 0.4.0 → 0.4.1
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 +128 -10
- package/README.md +20 -13
- package/behaviors/index.d.ts +3 -1
- package/behaviors/index.js +179 -106
- package/classes/index.d.ts +40 -0
- package/classes/index.js +41 -0
- package/classes/vscode.css-custom-data.json +18 -14
- package/css/disclosure.css +29 -0
- package/css/dots.css +2 -0
- package/css/feedback.css +53 -0
- package/css/motion.css +88 -0
- package/css/overlay.css +52 -1
- package/css/report.css +382 -0
- package/css/tokens.css +56 -26
- package/dist/bronto.css +1 -1
- package/dist/css/disclosure.css +1 -1
- package/dist/css/dots.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/motion.css +1 -1
- package/dist/css/overlay.css +1 -1
- package/dist/css/report.css +1 -0
- package/dist/css/tokens.css +1 -1
- package/docs/adr/0001-color-system.md +26 -27
- package/docs/adr/0002-scope-and-2026-baseline.md +104 -0
- package/docs/adr/0003-theme-model.md +94 -0
- package/docs/contrast.md +42 -42
- package/docs/reference.md +112 -15
- package/docs/reporting.md +270 -0
- package/docs/stability.md +37 -0
- package/docs/theming.md +18 -6
- package/docs/usage.md +21 -3
- package/llms.txt +61 -4
- package/package.json +28 -3
- package/qwik/index.d.ts +55 -0
- package/qwik/index.js +129 -0
- package/react/index.d.ts +35 -14
- package/react/index.js +22 -5
- package/solid/index.d.ts +35 -14
- package/solid/index.js +21 -3
- package/tokens/index.d.ts +3 -3
- package/tokens/index.js +16 -14
- package/tokens/index.json +32 -28
- package/tokens/resolved.json +34 -32
- package/tokens/tokens.dtcg.json +22 -14
package/behaviors/index.js
CHANGED
|
@@ -54,6 +54,24 @@ function bindOnce(target, key, add) {
|
|
|
54
54
|
return cleanup;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function byIdInHost(host, id) {
|
|
58
|
+
if (!id) return null;
|
|
59
|
+
if (host === document) return document.getElementById(id);
|
|
60
|
+
if (host.id === id) return host;
|
|
61
|
+
return (
|
|
62
|
+
Array.from(host.querySelectorAll?.('[id]') || []).find((el) => el.id === id) ||
|
|
63
|
+
document.getElementById(id)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function closestSafe(el, selector) {
|
|
68
|
+
try {
|
|
69
|
+
return el.closest(selector);
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
/**
|
|
58
76
|
* Apply the persisted theme to <html data-theme>. Call as early as
|
|
59
77
|
* possible (an inline module in <head>) to avoid a flash before the
|
|
@@ -145,7 +163,7 @@ export function dismissible({ root } = {}) {
|
|
|
145
163
|
const btn = e.target.closest('[data-bronto-dismiss]');
|
|
146
164
|
if (!btn || !host.contains(btn)) return;
|
|
147
165
|
const sel = btn.getAttribute('data-bronto-dismiss');
|
|
148
|
-
const target = sel ? btn
|
|
166
|
+
const target = sel ? closestSafe(btn, sel) : btn.closest('[data-bronto-dismissible]');
|
|
149
167
|
if (!target) return;
|
|
150
168
|
const ev = new CustomEvent('bronto:dismiss', { bubbles: true, cancelable: true });
|
|
151
169
|
if (target.dispatchEvent(ev)) target.remove();
|
|
@@ -264,81 +282,73 @@ export function initTabs({ root } = {}) {
|
|
|
264
282
|
* `close` event, so keyboard/SR users are never dropped at `<body>`.
|
|
265
283
|
* SSR-safe and idempotent; returns cleanup.
|
|
266
284
|
*
|
|
267
|
-
* `root` scopes
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* (same model as `initThemeToggle`, where `root` scopes controls but the
|
|
271
|
-
* theme applies to <html>).
|
|
285
|
+
* `root` scopes delegated triggers (default `document`). Controlled targets are
|
|
286
|
+
* resolved root-first, then document-wide, so scoped islands win duplicate-id
|
|
287
|
+
* conflicts without breaking body/portal-mounted overlays.
|
|
272
288
|
*/
|
|
273
289
|
export function initDialog({ root } = {}) {
|
|
274
290
|
if (!hasDom()) return noop;
|
|
275
291
|
const host = root || document;
|
|
292
|
+
const managedDialogs = new WeakSet();
|
|
293
|
+
const canManageDialog = (dlg, origin) => host.contains(origin) || managedDialogs.has(dlg);
|
|
294
|
+
|
|
295
|
+
const openFrom = (opener) => {
|
|
296
|
+
const dlg = byIdInHost(host, opener.getAttribute('data-bronto-open'));
|
|
297
|
+
if (!dlg || typeof dlg.showModal !== 'function' || dlg.open) return;
|
|
298
|
+
managedDialogs.add(dlg);
|
|
299
|
+
dlg.addEventListener(
|
|
300
|
+
'close',
|
|
301
|
+
() => {
|
|
302
|
+
if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
|
|
303
|
+
},
|
|
304
|
+
{ once: true },
|
|
305
|
+
);
|
|
306
|
+
dlg.showModal();
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const closeFrom = (closer) => {
|
|
310
|
+
const dlg = closer.closest('dialog');
|
|
311
|
+
if (dlg && dlg.open && canManageDialog(dlg, closer)) dlg.close();
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const lightDismiss = (dlg) => {
|
|
315
|
+
if (
|
|
316
|
+
dlg.tagName === 'DIALOG' &&
|
|
317
|
+
dlg.open &&
|
|
318
|
+
dlg.hasAttribute('data-bronto-dialog-light') &&
|
|
319
|
+
canManageDialog(dlg, dlg)
|
|
320
|
+
) {
|
|
321
|
+
dlg.close();
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
276
325
|
const onClick = (e) => {
|
|
277
326
|
const opener = e.target.closest('[data-bronto-open]');
|
|
278
327
|
if (opener && host.contains(opener)) {
|
|
279
|
-
|
|
280
|
-
if (dlg && typeof dlg.showModal === 'function' && !dlg.open) {
|
|
281
|
-
dlg.addEventListener(
|
|
282
|
-
'close',
|
|
283
|
-
() => {
|
|
284
|
-
if (opener.isConnected && typeof opener.focus === 'function') opener.focus();
|
|
285
|
-
},
|
|
286
|
-
{ once: true },
|
|
287
|
-
);
|
|
288
|
-
dlg.showModal();
|
|
289
|
-
}
|
|
328
|
+
openFrom(opener);
|
|
290
329
|
return;
|
|
291
330
|
}
|
|
292
331
|
const closer = e.target.closest('[data-bronto-close]');
|
|
293
|
-
if (closer
|
|
294
|
-
|
|
295
|
-
if (dlg && dlg.open) dlg.close();
|
|
332
|
+
if (closer) {
|
|
333
|
+
closeFrom(closer);
|
|
296
334
|
return;
|
|
297
335
|
}
|
|
298
336
|
// Light-dismiss: a click whose target is the <dialog> itself is the
|
|
299
337
|
// backdrop (content sits in child elements).
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
dlg.tagName === 'DIALOG' &&
|
|
303
|
-
dlg.open &&
|
|
304
|
-
dlg.hasAttribute('data-bronto-dialog-light') &&
|
|
305
|
-
host.contains(dlg)
|
|
306
|
-
) {
|
|
307
|
-
dlg.close();
|
|
308
|
-
}
|
|
338
|
+
lightDismiss(e.target);
|
|
309
339
|
};
|
|
310
340
|
return bindOnce(host, 'dialog', () => {
|
|
311
|
-
|
|
312
|
-
return () =>
|
|
341
|
+
document.addEventListener('click', onClick);
|
|
342
|
+
return () => document.removeEventListener('click', onClick);
|
|
313
343
|
});
|
|
314
344
|
}
|
|
315
345
|
|
|
316
|
-
|
|
317
|
-
* Push a transient toast into a shared, screen-anchored stack. The stack
|
|
318
|
-
* is the `aria-live="polite"` region: it is created once, appended to
|
|
319
|
-
* <body>, and **kept resident even when empty** so the live region is
|
|
320
|
-
* always present before content is inserted (a freshly created region
|
|
321
|
-
* that receives its first child in the same tick is not reliably
|
|
322
|
-
* announced by VoiceOver/NVDA). On first creation the empty region is
|
|
323
|
-
* inserted and the toast is appended on the next frame for the same
|
|
324
|
-
* reason. `tone` is accent/success/warning/danger/info; `title` is an
|
|
325
|
-
* optional uppercase label; `duration` ms before auto-dismiss (0 keeps
|
|
326
|
-
* it until dismissed). Returns a function that dismisses the toast
|
|
327
|
-
* early. SSR-safe (no-op).
|
|
328
|
-
*/
|
|
329
|
-
export function toast(message, { tone, title, duration = 4000, assertive, closable } = {}) {
|
|
330
|
-
if (!hasDom()) return noop;
|
|
331
|
-
// Errors must interrupt: danger toasts (or an explicit `assertive`)
|
|
332
|
-
// go to a SEPARATE assertive region so they announce immediately,
|
|
333
|
-
// while status toasts stay polite. Two regions — not a per-item
|
|
334
|
-
// role=alert nested in a polite parent — avoids the double
|
|
335
|
-
// announcement that nesting causes in some screen readers.
|
|
336
|
-
const isAssertive = assertive ?? tone === 'danger';
|
|
346
|
+
function toastStack(isAssertive) {
|
|
337
347
|
const stackSel = isAssertive
|
|
338
348
|
? '.ui-toast-stack--assertive'
|
|
339
349
|
: '.ui-toast-stack:not(.ui-toast-stack--assertive)';
|
|
340
350
|
let stack = document.querySelector(stackSel);
|
|
341
|
-
const
|
|
351
|
+
const fresh = !stack;
|
|
342
352
|
if (!stack) {
|
|
343
353
|
stack = document.createElement('div');
|
|
344
354
|
stack.className = isAssertive ? 'ui-toast-stack ui-toast-stack--assertive' : 'ui-toast-stack';
|
|
@@ -346,6 +356,26 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
346
356
|
if (isAssertive) stack.setAttribute('role', 'alert');
|
|
347
357
|
document.body.appendChild(stack);
|
|
348
358
|
}
|
|
359
|
+
return { stack, fresh };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function enqueueToast(place, freshStack) {
|
|
363
|
+
const canDefer = typeof requestAnimationFrame === 'function';
|
|
364
|
+
if (freshStack && canDefer) {
|
|
365
|
+
toastQueue.push(place);
|
|
366
|
+
toastFlushScheduled = true;
|
|
367
|
+
requestAnimationFrame(() => {
|
|
368
|
+
toastFlushScheduled = false;
|
|
369
|
+
for (const fn of toastQueue.splice(0)) fn();
|
|
370
|
+
});
|
|
371
|
+
} else if (toastFlushScheduled) {
|
|
372
|
+
toastQueue.push(place);
|
|
373
|
+
} else {
|
|
374
|
+
place();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function toastElement(message, { tone, title }) {
|
|
349
379
|
const el = document.createElement('div');
|
|
350
380
|
el.className = tone ? `ui-toast ui-toast--${tone}` : 'ui-toast';
|
|
351
381
|
// No per-item role: the stack itself is the live region; a nested
|
|
@@ -359,6 +389,72 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
359
389
|
const body = document.createElement('div');
|
|
360
390
|
body.textContent = message;
|
|
361
391
|
el.appendChild(body);
|
|
392
|
+
return el;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Remove a toast, animating its exit when — and only when — a transition
|
|
396
|
+
// is actually in effect. Detached nodes, reduced-motion, and the no-CSS
|
|
397
|
+
// test/SSR env all resolve to instant removal, so the dismiss contract
|
|
398
|
+
// (toast gone now, the aria-live stack stays resident) is unchanged there;
|
|
399
|
+
// a real browser with motion gets the CSS `.is-leaving` fade-out, with a
|
|
400
|
+
// timeout fallback so an interrupted/never-firing transitionend can't strand
|
|
401
|
+
// a toast in the live region.
|
|
402
|
+
function removeToast(el) {
|
|
403
|
+
const reduce =
|
|
404
|
+
typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
405
|
+
const cs =
|
|
406
|
+
!reduce && el.isConnected && typeof getComputedStyle === 'function'
|
|
407
|
+
? getComputedStyle(el)
|
|
408
|
+
: null;
|
|
409
|
+
const dur = cs ? parseFloat(cs.transitionDuration) || 0 : 0;
|
|
410
|
+
if (dur <= 0) {
|
|
411
|
+
el.remove();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
el.classList.add('is-leaving');
|
|
415
|
+
let done = false;
|
|
416
|
+
const finish = () => {
|
|
417
|
+
if (done) return;
|
|
418
|
+
done = true;
|
|
419
|
+
el.remove();
|
|
420
|
+
};
|
|
421
|
+
el.addEventListener('transitionend', finish, { once: true });
|
|
422
|
+
const timer = setTimeout(finish, dur * 1000 + 120);
|
|
423
|
+
timer?.unref?.(); // don't keep a Node test process alive
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function addToastClose(el, dismiss) {
|
|
427
|
+
const close = document.createElement('button');
|
|
428
|
+
close.type = 'button';
|
|
429
|
+
close.className = 'ui-toast__close';
|
|
430
|
+
close.setAttribute('aria-label', 'Dismiss');
|
|
431
|
+
close.addEventListener('click', dismiss);
|
|
432
|
+
el.appendChild(close);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Push a transient toast into a shared, screen-anchored stack. The stack
|
|
437
|
+
* is the `aria-live="polite"` region: it is created once, appended to
|
|
438
|
+
* <body>, and **kept resident even when empty** so the live region is
|
|
439
|
+
* always present before content is inserted (a freshly created region
|
|
440
|
+
* that receives its first child in the same tick is not reliably
|
|
441
|
+
* announced by VoiceOver/NVDA). On first creation the empty region is
|
|
442
|
+
* inserted and the toast is appended on the next frame for the same
|
|
443
|
+
* reason. `tone` is accent/success/warning/danger/info; `title` is an
|
|
444
|
+
* optional uppercase label; `duration` ms before auto-dismiss (0 keeps
|
|
445
|
+
* it until dismissed). Returns a function that dismisses the toast
|
|
446
|
+
* early. SSR-safe (no-op).
|
|
447
|
+
*/
|
|
448
|
+
export function toast(message, { tone, title, duration = 4000, assertive, closable } = {}) {
|
|
449
|
+
if (!hasDom()) return noop;
|
|
450
|
+
// Errors must interrupt: danger toasts (or an explicit `assertive`)
|
|
451
|
+
// go to a SEPARATE assertive region so they announce immediately,
|
|
452
|
+
// while status toasts stay polite. Two regions — not a per-item
|
|
453
|
+
// role=alert nested in a polite parent — avoids the double
|
|
454
|
+
// announcement that nesting causes in some screen readers.
|
|
455
|
+
const isAssertive = assertive ?? tone === 'danger';
|
|
456
|
+
const { stack, fresh: freshStack } = toastStack(isAssertive);
|
|
457
|
+
const el = toastElement(message, { tone, title });
|
|
362
458
|
// Append after a frame the *first* time so the empty live region is
|
|
363
459
|
// observed by AT before its first child arrives; once the region has
|
|
364
460
|
// been observed, later toasts append synchronously.
|
|
@@ -369,28 +465,14 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
369
465
|
const place = () => {
|
|
370
466
|
if (!dismissed) stack.appendChild(el);
|
|
371
467
|
};
|
|
372
|
-
|
|
373
|
-
if (freshStack && canDefer) {
|
|
374
|
-
toastQueue.push(place);
|
|
375
|
-
toastFlushScheduled = true;
|
|
376
|
-
requestAnimationFrame(() => {
|
|
377
|
-
toastFlushScheduled = false;
|
|
378
|
-
for (const fn of toastQueue.splice(0)) fn();
|
|
379
|
-
});
|
|
380
|
-
} else if (toastFlushScheduled) {
|
|
381
|
-
// A first-frame deferral is in flight — queue behind it so FIFO
|
|
382
|
-
// order holds and the region still isn't populated synchronously.
|
|
383
|
-
toastQueue.push(place);
|
|
384
|
-
} else {
|
|
385
|
-
place();
|
|
386
|
-
}
|
|
468
|
+
enqueueToast(place, freshStack);
|
|
387
469
|
|
|
388
470
|
let timer;
|
|
389
471
|
const dismiss = () => {
|
|
390
472
|
if (dismissed) return;
|
|
391
473
|
dismissed = true;
|
|
392
474
|
if (timer) clearTimeout(timer);
|
|
393
|
-
el
|
|
475
|
+
removeToast(el);
|
|
394
476
|
// The stack is a persistent live region — never removed on drain, so
|
|
395
477
|
// the next toast does not recreate (and thus mis-announce) it.
|
|
396
478
|
};
|
|
@@ -398,14 +480,7 @@ export function toast(message, { tone, title, duration = 4000, assertive, closab
|
|
|
398
480
|
// it gets a dismiss affordance by default; any toast can opt in via
|
|
399
481
|
// `closable`. The button carries no text node (glyph is a CSS
|
|
400
482
|
// ::before) so the toast's announced/textContent stays the message.
|
|
401
|
-
if (closable ?? duration === 0)
|
|
402
|
-
const close = document.createElement('button');
|
|
403
|
-
close.type = 'button';
|
|
404
|
-
close.className = 'ui-toast__close';
|
|
405
|
-
close.setAttribute('aria-label', 'Dismiss');
|
|
406
|
-
close.addEventListener('click', dismiss);
|
|
407
|
-
el.appendChild(close);
|
|
408
|
-
}
|
|
483
|
+
if (closable ?? duration === 0) addToastClose(el, dismiss);
|
|
409
484
|
if (duration > 0) timer = setTimeout(dismiss, duration);
|
|
410
485
|
return dismiss;
|
|
411
486
|
}
|
|
@@ -422,7 +497,7 @@ export function initDisclosure({ root } = {}) {
|
|
|
422
497
|
const trigger = e.target.closest('[data-bronto-disclosure]');
|
|
423
498
|
if (!trigger || !host.contains(trigger)) return;
|
|
424
499
|
const id = trigger.getAttribute('aria-controls');
|
|
425
|
-
const panel =
|
|
500
|
+
const panel = byIdInHost(host, id);
|
|
426
501
|
if (!panel) return;
|
|
427
502
|
const open = trigger.getAttribute('aria-expanded') === 'true';
|
|
428
503
|
trigger.setAttribute('aria-expanded', String(!open));
|
|
@@ -755,6 +830,24 @@ export function initCombobox({ root } = {}) {
|
|
|
755
830
|
active = options.indexOf(vis[next]);
|
|
756
831
|
setActive(options[active]);
|
|
757
832
|
};
|
|
833
|
+
const activateEdge = (which) => {
|
|
834
|
+
if (list.hidden) return false;
|
|
835
|
+
const v = visible();
|
|
836
|
+
if (!v.length) return true;
|
|
837
|
+
active = options.indexOf(which === 'first' ? v[0] : v[v.length - 1]);
|
|
838
|
+
setActive(options[active]);
|
|
839
|
+
return true;
|
|
840
|
+
};
|
|
841
|
+
const selectActive = () => {
|
|
842
|
+
if (list.hidden || active < 0 || options[active].hidden) return false;
|
|
843
|
+
select(options[active]);
|
|
844
|
+
return true;
|
|
845
|
+
};
|
|
846
|
+
const closeIfOpen = () => {
|
|
847
|
+
if (list.hidden) return false;
|
|
848
|
+
close();
|
|
849
|
+
return true;
|
|
850
|
+
};
|
|
758
851
|
|
|
759
852
|
const onInput = () => filter();
|
|
760
853
|
const onKey = (e) => {
|
|
@@ -768,36 +861,16 @@ export function initCombobox({ root } = {}) {
|
|
|
768
861
|
move(-1);
|
|
769
862
|
break;
|
|
770
863
|
case 'Home':
|
|
771
|
-
if (
|
|
772
|
-
e.preventDefault();
|
|
773
|
-
const v = visible();
|
|
774
|
-
if (v.length) {
|
|
775
|
-
active = options.indexOf(v[0]);
|
|
776
|
-
setActive(options[active]);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
864
|
+
if (activateEdge('first')) e.preventDefault();
|
|
779
865
|
break;
|
|
780
866
|
case 'End':
|
|
781
|
-
if (
|
|
782
|
-
e.preventDefault();
|
|
783
|
-
const v = visible();
|
|
784
|
-
if (v.length) {
|
|
785
|
-
active = options.indexOf(v[v.length - 1]);
|
|
786
|
-
setActive(options[active]);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
867
|
+
if (activateEdge('last')) e.preventDefault();
|
|
789
868
|
break;
|
|
790
869
|
case 'Enter':
|
|
791
|
-
if (
|
|
792
|
-
e.preventDefault();
|
|
793
|
-
select(options[active]);
|
|
794
|
-
}
|
|
870
|
+
if (selectActive()) e.preventDefault();
|
|
795
871
|
break;
|
|
796
872
|
case 'Escape':
|
|
797
|
-
if (
|
|
798
|
-
e.preventDefault();
|
|
799
|
-
close();
|
|
800
|
-
}
|
|
873
|
+
if (closeIfOpen()) e.preventDefault();
|
|
801
874
|
break;
|
|
802
875
|
case 'Tab':
|
|
803
876
|
close();
|
|
@@ -904,8 +977,8 @@ export function initPopover({ root } = {}) {
|
|
|
904
977
|
|
|
905
978
|
const onClick = (e) => {
|
|
906
979
|
const trigger = e.target.closest?.('[data-bronto-popover]');
|
|
907
|
-
if (trigger) {
|
|
908
|
-
const panel =
|
|
980
|
+
if (trigger && host.contains(trigger)) {
|
|
981
|
+
const panel = byIdInHost(host, trigger.getAttribute('data-bronto-popover'));
|
|
909
982
|
if (!panel) return;
|
|
910
983
|
e.preventDefault();
|
|
911
984
|
if (openPanel === panel) close();
|
|
@@ -926,12 +999,12 @@ export function initPopover({ root } = {}) {
|
|
|
926
999
|
};
|
|
927
1000
|
|
|
928
1001
|
return bindOnce(host, 'popover', () => {
|
|
929
|
-
|
|
1002
|
+
document.addEventListener('click', onClick);
|
|
930
1003
|
document.addEventListener('keydown', onKey);
|
|
931
1004
|
view?.addEventListener('scroll', onReflow, true);
|
|
932
1005
|
view?.addEventListener('resize', onReflow);
|
|
933
1006
|
return () => {
|
|
934
|
-
|
|
1007
|
+
document.removeEventListener('click', onClick);
|
|
935
1008
|
document.removeEventListener('keydown', onKey);
|
|
936
1009
|
view?.removeEventListener('scroll', onReflow, true);
|
|
937
1010
|
view?.removeEventListener('resize', onReflow);
|
package/classes/index.d.ts
CHANGED
|
@@ -232,6 +232,43 @@ export declare const cls: {
|
|
|
232
232
|
readonly timeline: 'ui-timeline';
|
|
233
233
|
readonly timelineItem: 'ui-timeline__item';
|
|
234
234
|
readonly timelineTime: 'ui-timeline__time';
|
|
235
|
+
readonly report: 'ui-report';
|
|
236
|
+
readonly reportCompact: 'ui-report--compact';
|
|
237
|
+
readonly reportNumbered: 'ui-report--numbered';
|
|
238
|
+
readonly reportCover: 'ui-report__cover';
|
|
239
|
+
readonly reportCoverCompact: 'ui-report__cover--compact';
|
|
240
|
+
readonly reportHeader: 'ui-report__header';
|
|
241
|
+
readonly reportTitle: 'ui-report__title';
|
|
242
|
+
readonly reportSubtitle: 'ui-report__subtitle';
|
|
243
|
+
readonly reportMeta: 'ui-report__meta';
|
|
244
|
+
readonly reportToc: 'ui-report__toc';
|
|
245
|
+
readonly reportSummary: 'ui-report__summary';
|
|
246
|
+
readonly reportSection: 'ui-report__section';
|
|
247
|
+
readonly reportSectionUnnumbered: 'ui-report__section--unnumbered';
|
|
248
|
+
readonly reportSectionHead: 'ui-report__section-head';
|
|
249
|
+
readonly reportFinding: 'ui-report__finding';
|
|
250
|
+
readonly reportEvidence: 'ui-report__evidence';
|
|
251
|
+
readonly reportFigure: 'ui-report__figure';
|
|
252
|
+
readonly reportCaption: 'ui-report__caption';
|
|
253
|
+
readonly reportSources: 'ui-report__sources';
|
|
254
|
+
readonly reportAppendix: 'ui-report__appendix';
|
|
255
|
+
readonly reportFootnotes: 'ui-report__footnotes';
|
|
256
|
+
readonly chart: 'ui-chart';
|
|
257
|
+
readonly chartLegend: 'ui-chart__legend';
|
|
258
|
+
readonly chartSwatch: 'ui-chart__swatch';
|
|
259
|
+
readonly chartCaption: 'ui-chart__caption';
|
|
260
|
+
readonly chartPlot: 'ui-chart__plot';
|
|
261
|
+
readonly chartBar: 'ui-chart__bar';
|
|
262
|
+
readonly chartLabel: 'ui-chart__label';
|
|
263
|
+
readonly chartTrack: 'ui-chart__track';
|
|
264
|
+
readonly chartFill: 'ui-chart__fill';
|
|
265
|
+
readonly chartFallback: 'ui-chart__fallback';
|
|
266
|
+
readonly printOnly: 'ui-print-only';
|
|
267
|
+
readonly screenOnly: 'ui-screen-only';
|
|
268
|
+
readonly breakBefore: 'ui-break-before';
|
|
269
|
+
readonly breakAfter: 'ui-break-after';
|
|
270
|
+
readonly keep: 'ui-keep';
|
|
271
|
+
readonly printExact: 'ui-print-exact';
|
|
235
272
|
readonly kbd: 'ui-kbd';
|
|
236
273
|
readonly display: 'ui-display';
|
|
237
274
|
readonly mono: 'ui-mono';
|
|
@@ -248,6 +285,9 @@ export declare const cls: {
|
|
|
248
285
|
readonly animateFade: 'ui-animate-fade';
|
|
249
286
|
readonly animateDot: 'ui-animate-dot';
|
|
250
287
|
readonly animateMatrix: 'ui-animate-matrix';
|
|
288
|
+
readonly scrollProgress: 'ui-scroll-progress';
|
|
289
|
+
readonly scrollReveal: 'ui-scroll-reveal';
|
|
290
|
+
readonly vt: 'ui-vt';
|
|
251
291
|
readonly appShell: 'ui-app-shell';
|
|
252
292
|
readonly appShellFull: 'ui-app-shell--full';
|
|
253
293
|
readonly appRail: 'ui-app-rail';
|
package/classes/index.js
CHANGED
|
@@ -245,6 +245,44 @@ export const cls = Object.freeze({
|
|
|
245
245
|
timeline: 'ui-timeline',
|
|
246
246
|
timelineItem: 'ui-timeline__item',
|
|
247
247
|
timelineTime: 'ui-timeline__time',
|
|
248
|
+
// report kit (opt-in css/report.css)
|
|
249
|
+
report: 'ui-report',
|
|
250
|
+
reportCompact: 'ui-report--compact',
|
|
251
|
+
reportNumbered: 'ui-report--numbered',
|
|
252
|
+
reportCover: 'ui-report__cover',
|
|
253
|
+
reportCoverCompact: 'ui-report__cover--compact',
|
|
254
|
+
reportHeader: 'ui-report__header',
|
|
255
|
+
reportTitle: 'ui-report__title',
|
|
256
|
+
reportSubtitle: 'ui-report__subtitle',
|
|
257
|
+
reportMeta: 'ui-report__meta',
|
|
258
|
+
reportToc: 'ui-report__toc',
|
|
259
|
+
reportSummary: 'ui-report__summary',
|
|
260
|
+
reportSection: 'ui-report__section',
|
|
261
|
+
reportSectionUnnumbered: 'ui-report__section--unnumbered',
|
|
262
|
+
reportSectionHead: 'ui-report__section-head',
|
|
263
|
+
reportFinding: 'ui-report__finding',
|
|
264
|
+
reportEvidence: 'ui-report__evidence',
|
|
265
|
+
reportFigure: 'ui-report__figure',
|
|
266
|
+
reportCaption: 'ui-report__caption',
|
|
267
|
+
reportSources: 'ui-report__sources',
|
|
268
|
+
reportAppendix: 'ui-report__appendix',
|
|
269
|
+
reportFootnotes: 'ui-report__footnotes',
|
|
270
|
+
chart: 'ui-chart',
|
|
271
|
+
chartLegend: 'ui-chart__legend',
|
|
272
|
+
chartSwatch: 'ui-chart__swatch',
|
|
273
|
+
chartCaption: 'ui-chart__caption',
|
|
274
|
+
chartPlot: 'ui-chart__plot',
|
|
275
|
+
chartBar: 'ui-chart__bar',
|
|
276
|
+
chartLabel: 'ui-chart__label',
|
|
277
|
+
chartTrack: 'ui-chart__track',
|
|
278
|
+
chartFill: 'ui-chart__fill',
|
|
279
|
+
chartFallback: 'ui-chart__fallback',
|
|
280
|
+
printOnly: 'ui-print-only',
|
|
281
|
+
screenOnly: 'ui-screen-only',
|
|
282
|
+
breakBefore: 'ui-break-before',
|
|
283
|
+
breakAfter: 'ui-break-after',
|
|
284
|
+
keep: 'ui-keep',
|
|
285
|
+
printExact: 'ui-print-exact',
|
|
248
286
|
kbd: 'ui-kbd',
|
|
249
287
|
display: 'ui-display',
|
|
250
288
|
mono: 'ui-mono',
|
|
@@ -262,6 +300,9 @@ export const cls = Object.freeze({
|
|
|
262
300
|
animateFade: 'ui-animate-fade',
|
|
263
301
|
animateDot: 'ui-animate-dot',
|
|
264
302
|
animateMatrix: 'ui-animate-matrix',
|
|
303
|
+
scrollProgress: 'ui-scroll-progress',
|
|
304
|
+
scrollReveal: 'ui-scroll-reveal',
|
|
305
|
+
vt: 'ui-vt',
|
|
265
306
|
// admin shell (was the legacy .app-* vocabulary; promoted in 0.3.0)
|
|
266
307
|
appShell: 'ui-app-shell',
|
|
267
308
|
appShellFull: 'ui-app-shell--full',
|
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"name": "--accent-1",
|
|
10
|
-
"description": "Global scale token. Value: `color-mix(in oklch, var(--accent) 8%, var(--
|
|
10
|
+
"description": "Global scale token. Value: `color-mix(in oklch, var(--accent) 8%, var(--accent-ramp-end))`"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"name": "--accent-2",
|
|
14
|
-
"description": "Global scale token. Value: `color-mix(in oklch, var(--accent) 16%, var(--
|
|
14
|
+
"description": "Global scale token. Value: `color-mix(in oklch, var(--accent) 16%, var(--accent-ramp-end))`"
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
"name": "--accent-3",
|
|
18
|
-
"description": "Global scale token. Value: `color-mix(in oklch, var(--accent) 32%, var(--
|
|
18
|
+
"description": "Global scale token. Value: `color-mix(in oklch, var(--accent) 32%, var(--accent-ramp-end))`"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"name": "--accent-4",
|
|
22
|
-
"description": "Global scale token. Value: `color-mix(in oklch, var(--accent) 60%, var(--
|
|
22
|
+
"description": "Global scale token. Value: `color-mix(in oklch, var(--accent) 60%, var(--accent-ramp-end))`"
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"name": "--accent-5",
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"name": "--accent-6",
|
|
30
30
|
"description": "Global scale token. Value: `var(--accent-strong)`"
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
"name": "--accent-ramp-end",
|
|
34
|
+
"description": "Theme token. Light: `#ffffff` · Dark: `#000000`"
|
|
35
|
+
},
|
|
32
36
|
{
|
|
33
37
|
"name": "--accent-soft",
|
|
34
38
|
"description": "Theme token. Light: `color-mix(in srgb, var(--accent) 10%, transparent)` · Dark: `color-mix(in srgb, var(--accent) 14%, transparent)`"
|
|
@@ -43,7 +47,7 @@
|
|
|
43
47
|
},
|
|
44
48
|
{
|
|
45
49
|
"name": "--bg",
|
|
46
|
-
"description": "Theme token. Light: `#f4f4f2` · Dark: `#
|
|
50
|
+
"description": "Theme token. Light: `#f4f4f2` · Dark: `#121212`"
|
|
47
51
|
},
|
|
48
52
|
{
|
|
49
53
|
"name": "--bg-accent",
|
|
@@ -51,7 +55,7 @@
|
|
|
51
55
|
},
|
|
52
56
|
{
|
|
53
57
|
"name": "--bg-elevated",
|
|
54
|
-
"description": "Theme token. Light: `#fbfbfa` · Dark: `#
|
|
58
|
+
"description": "Theme token. Light: `#fbfbfa` · Dark: `#181818`"
|
|
55
59
|
},
|
|
56
60
|
{
|
|
57
61
|
"name": "--border",
|
|
@@ -199,11 +203,11 @@
|
|
|
199
203
|
},
|
|
200
204
|
{
|
|
201
205
|
"name": "--line",
|
|
202
|
-
"description": "Theme token. Light: `#d8d8d4` · Dark: `#
|
|
206
|
+
"description": "Theme token. Light: `#d8d8d4` · Dark: `#383838`"
|
|
203
207
|
},
|
|
204
208
|
{
|
|
205
209
|
"name": "--line-strong",
|
|
206
|
-
"description": "Theme token. Light: `#a8a8a2` · Dark: `#
|
|
210
|
+
"description": "Theme token. Light: `#a8a8a2` · Dark: `#555555`"
|
|
207
211
|
},
|
|
208
212
|
{
|
|
209
213
|
"name": "--mono",
|
|
@@ -211,15 +215,15 @@
|
|
|
211
215
|
},
|
|
212
216
|
{
|
|
213
217
|
"name": "--panel",
|
|
214
|
-
"description": "Theme token. Light: `#ffffff` · Dark: `#
|
|
218
|
+
"description": "Theme token. Light: `#ffffff` · Dark: `#1c1c1c`"
|
|
215
219
|
},
|
|
216
220
|
{
|
|
217
221
|
"name": "--panel-soft",
|
|
218
|
-
"description": "Theme token. Light: `#ececea` · Dark: `#
|
|
222
|
+
"description": "Theme token. Light: `#ececea` · Dark: `#242424`"
|
|
219
223
|
},
|
|
220
224
|
{
|
|
221
225
|
"name": "--panel-strong",
|
|
222
|
-
"description": "Theme token. Light: `#ffffff` · Dark: `#
|
|
226
|
+
"description": "Theme token. Light: `#ffffff` · Dark: `#222222`"
|
|
223
227
|
},
|
|
224
228
|
{
|
|
225
229
|
"name": "--radius-lg",
|
|
@@ -327,7 +331,7 @@
|
|
|
327
331
|
},
|
|
328
332
|
{
|
|
329
333
|
"name": "--text",
|
|
330
|
-
"description": "Theme token. Light: `#0a0a0a` · Dark: `#
|
|
334
|
+
"description": "Theme token. Light: `#0a0a0a` · Dark: `#e6e6e6`"
|
|
331
335
|
},
|
|
332
336
|
{
|
|
333
337
|
"name": "--text-2xs",
|
|
@@ -339,7 +343,7 @@
|
|
|
339
343
|
},
|
|
340
344
|
{
|
|
341
345
|
"name": "--text-dim",
|
|
342
|
-
"description": "Theme token. Light: `#686863` · Dark: `#
|
|
346
|
+
"description": "Theme token. Light: `#686863` · Dark: `#a0a0a0`"
|
|
343
347
|
},
|
|
344
348
|
{
|
|
345
349
|
"name": "--text-lg",
|
|
@@ -351,7 +355,7 @@
|
|
|
351
355
|
},
|
|
352
356
|
{
|
|
353
357
|
"name": "--text-soft",
|
|
354
|
-
"description": "Theme token. Light: `#353533` · Dark: `#
|
|
358
|
+
"description": "Theme token. Light: `#353533` · Dark: `#c8c8c8`"
|
|
355
359
|
},
|
|
356
360
|
{
|
|
357
361
|
"name": "--text-xl",
|
package/css/disclosure.css
CHANGED
|
@@ -128,6 +128,35 @@
|
|
|
128
128
|
margin-block-start: 0;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
/* Auto-height open/close for the native <details> accordion: animate the
|
|
132
|
+
content box from 0 to its intrinsic height. `interpolate-size:
|
|
133
|
+
allow-keywords` makes `auto` interpolable; `::details-content` exposes
|
|
134
|
+
the otherwise-anonymous content box; `content-visibility ... allow-discrete`
|
|
135
|
+
keeps the content laid out (and the dot marker's own rotation in sync)
|
|
136
|
+
through the close. Strict progressive enhancement — gated on
|
|
137
|
+
`@supports selector(::details-content)` (Chrome 131+/Safari 18.4+; Firefox
|
|
138
|
+
not yet) so engines without it just snap open/closed exactly as before —
|
|
139
|
+
and on `prefers-reduced-motion: no-preference`. */
|
|
140
|
+
@supports selector(::details-content) {
|
|
141
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
142
|
+
.ui-accordion {
|
|
143
|
+
interpolate-size: allow-keywords;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ui-accordion__item::details-content {
|
|
147
|
+
block-size: 0;
|
|
148
|
+
overflow: hidden;
|
|
149
|
+
transition:
|
|
150
|
+
block-size var(--duration-base) var(--ease-out),
|
|
151
|
+
content-visibility var(--duration-base) allow-discrete;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.ui-accordion__item[open]::details-content {
|
|
155
|
+
block-size: auto;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
131
160
|
/* --- Segmented control — CSS-only radio group --- */
|
|
132
161
|
|
|
133
162
|
.ui-segmented {
|
package/css/dots.css
CHANGED
|
@@ -129,6 +129,8 @@
|
|
|
129
129
|
block-size: var(--icon-size, 1em);
|
|
130
130
|
background: currentcolor;
|
|
131
131
|
vertical-align: -0.125em;
|
|
132
|
+
/* stylelint-disable-next-line property-no-vendor-prefix -- Safari still needs the prefixed mask property. */
|
|
133
|
+
-webkit-mask: var(--icon-mask) center / contain no-repeat;
|
|
132
134
|
mask: var(--icon-mask) center / contain no-repeat;
|
|
133
135
|
}
|
|
134
136
|
|