@adia-ai/web-components 0.8.33 → 0.8.35
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 +33 -0
- package/components/agent-questions/agent-questions.class.js +0 -1
- package/components/agent-reasoning/agent-reasoning.a2ui.json +1 -1
- package/components/agent-reasoning/agent-reasoning.yaml +3 -3
- package/components/badge/badge.css +33 -7
- package/components/badge/badge.examples.md +4 -4
- package/components/button/button.css +12 -0
- package/components/card/card.css +44 -1
- package/components/check/check.a2ui.json +25 -11
- package/components/check/check.class.js +5 -0
- package/components/check/check.css +21 -0
- package/components/check/check.d.ts +2 -0
- package/components/check/check.examples.md +4 -4
- package/components/check/check.yaml +26 -7
- package/components/combobox/combobox.a2ui.json +1 -1
- package/components/combobox/combobox.yaml +3 -0
- package/components/command/command.a2ui.json +4 -0
- package/components/command/command.class.js +54 -4
- package/components/command/command.d.ts +2 -0
- package/components/command/command.yaml +25 -3
- package/components/date-range-picker/date-range-picker.a2ui.json +1 -1
- package/components/date-range-picker/date-range-picker.class.js +42 -43
- package/components/date-range-picker/date-range-picker.css +11 -0
- package/components/date-range-picker/date-range-picker.yaml +6 -3
- package/components/demo-toggle/demo-toggle.css +4 -0
- package/components/demo-toggle/demo-toggle.examples.md +4 -4
- package/components/description-list/description-list.css +16 -4
- package/components/feed/feed-item.a2ui.json +3 -2
- package/components/feed/feed-item.yaml +7 -1
- package/components/feed/feed.css +28 -5
- package/components/field/field.examples.md +4 -4
- package/components/image/image.class.js +8 -0
- package/components/modal/modal.class.js +43 -2
- package/components/modal/modal.css +23 -0
- package/components/nav-group/nav-group.css +27 -5
- package/components/nav-item/nav-item.css +11 -3
- package/components/option-card/option-card.a2ui.json +10 -10
- package/components/option-card/option-card.css +32 -16
- package/components/option-card/option-card.yaml +19 -15
- package/components/radio/radio.examples.md +4 -4
- package/components/range/range.css +8 -4
- package/components/richtext/richtext.css +5 -1
- package/components/segment/segment.css +10 -0
- package/components/slider/slider.a2ui.json +6 -3
- package/components/slider/slider.css +28 -12
- package/components/slider/slider.yaml +11 -3
- package/components/stat/stat.examples.md +4 -4
- package/components/step-progress/step-progress.css +16 -3
- package/components/swatch/swatch.css +14 -6
- package/components/switch/switch.examples.md +4 -4
- package/components/table/cell-types.js +8 -0
- package/components/table/table.a2ui.json +1 -1
- package/components/table/table.class.js +11 -0
- package/components/table/table.css +9 -0
- package/components/table/table.d.ts +1 -1
- package/components/table/table.yaml +1 -1
- package/components/table-toolbar/table-toolbar.css +10 -1
- package/components/tabs/tabs.css +13 -5
- package/components/tag/tag.class.js +14 -1
- package/components/tag/tag.css +70 -9
- package/components/text/text.examples.md +4 -4
- package/components/timeline/timeline-item.a2ui.json +2 -2
- package/components/timeline/timeline-item.yaml +2 -2
- package/components/timeline/timeline.css +33 -5
- package/components/timeline/timeline.d.ts +2 -2
- package/components/toast/toast.css +28 -5
- package/core/element.js +31 -0
- package/core/signals.js +3 -1
- package/custom-elements.json +469 -46
- package/dist/theme-provider.min.js +2 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +41 -41
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/patterns/access-requests/access-requests.examples.html +3 -3
- package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +3 -3
- package/patterns/admin-shell/admin-shell.examples.html +3 -4
- package/patterns/agent-cost/agent-cost.examples.html +3 -3
- package/patterns/agent-memory/agent-memory.examples.html +3 -3
- package/patterns/agent-prompt-library/agent-prompt-library.examples.html +3 -3
- package/patterns/agent-tool-call/agent-tool-call.examples.html +3 -3
- package/patterns/alert-dialog/alert-dialog.examples.html +3 -3
- package/patterns/app-nav/app-nav.examples.html +4 -4
- package/patterns/approvals/approvals.examples.html +3 -3
- package/patterns/audit-log/audit-log.examples.html +3 -3
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +19 -9
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +2 -2
- package/patterns/changelog-feed/changelog-feed.examples.html +3 -3
- package/patterns/chart-in-card/chart-in-card.examples.html +37 -3
- package/patterns/chart-in-card/chart-in-card.examples.js +12 -0
- package/patterns/chart-in-card/chart-in-card.html +6 -0
- package/patterns/chat-shell/chat-shell.examples.html +3 -4
- package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +3 -3
- package/patterns/conversion-funnel/conversion-funnel.examples.html +3 -3
- package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +3 -3
- package/patterns/diff-review/diff-review.examples.html +3 -3
- package/patterns/editor-shell/editor-shell.examples.html +3 -4
- package/patterns/export-flow/export-flow.examples.html +3 -3
- package/patterns/filter-bar/filter-bar.examples.html +3 -3
- package/patterns/form-drawer/form-drawer.examples.html +3 -3
- package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +3 -3
- package/patterns/inline-dialog/inline-dialog.examples.html +3 -3
- package/patterns/kanban-board/kanban-board.examples.html +3 -3
- package/patterns/layout/layout.examples.html +4 -7
- package/patterns/marketing-engagement/marketing-engagement.examples.html +3 -3
- package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +3 -3
- package/patterns/permissions-matrix/permissions-matrix.examples.html +3 -3
- package/patterns/permissions-role-picker/permissions-role-picker.examples.html +3 -3
- package/patterns/permissions-sharing/permissions-sharing.examples.html +3 -3
- package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +3 -3
- package/patterns/record-detail-drawer/record-detail-drawer.examples.html +3 -3
- package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.examples.html +135 -147
- package/patterns/retention-cohort/retention-cohort.examples.html +3 -3
- package/patterns/search-discovery/search-discovery.examples.html +3 -3
- package/patterns/system-banners/system-banners.examples.html +3 -3
- package/patterns/timezone-selector/timezone-selector.examples.html +3 -3
- package/patterns/wizard-drawer/wizard-drawer.examples.html +3 -3
- package/styles/colors/semantics/core.css +22 -2
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
* <date-range-picker-ui>
|
|
16
16
|
*
|
|
17
17
|
* Compound form primitive for selecting a start + end date pair with
|
|
18
|
-
* optional preset shortcuts. Composes <calendar-
|
|
19
|
-
*
|
|
20
|
-
* trigger and preset rail.
|
|
18
|
+
* optional preset shortcuts. Composes <calendar-grid-ui> (two panes
|
|
19
|
+
* showing adjacent months) + <popover-ui> + <button-ui> for the
|
|
20
|
+
* trigger and preset rail. Selection is click-start-then-click-end
|
|
21
|
+
* across BOTH panes as one surface (gh#948) — the panes differ only
|
|
22
|
+
* in which month they display, never in which half of the range a
|
|
23
|
+
* click writes.
|
|
21
24
|
*
|
|
22
25
|
* Form participation:
|
|
23
26
|
* ElementInternals serializes the selected range as JSON:
|
|
@@ -345,8 +348,8 @@ export class UIDateRangePicker extends UIFormElement {
|
|
|
345
348
|
this.#triggerRef.addEventListener('keydown', this.#onTriggerKey, true);
|
|
346
349
|
this.#popoverRef.addEventListener('click', this.#onPopoverClick);
|
|
347
350
|
this.#popoverRef.addEventListener('keydown', this.#onPopoverKey);
|
|
348
|
-
this.#calFromRef.addEventListener('change', this.#
|
|
349
|
-
this.#calToRef.addEventListener('change', this.#
|
|
351
|
+
this.#calFromRef.addEventListener('change', this.#onDaySelect);
|
|
352
|
+
this.#calToRef.addEventListener('change', this.#onDaySelect);
|
|
350
353
|
}
|
|
351
354
|
// Wire form value on first connect.
|
|
352
355
|
this.syncValue();
|
|
@@ -364,8 +367,8 @@ export class UIDateRangePicker extends UIFormElement {
|
|
|
364
367
|
this.#popoverRef.removeEventListener('keydown', this.#onPopoverKey);
|
|
365
368
|
this.#popoverRef.hidePopover?.();
|
|
366
369
|
}
|
|
367
|
-
if (this.#calFromRef) this.#calFromRef.removeEventListener('change', this.#
|
|
368
|
-
if (this.#calToRef) this.#calToRef.removeEventListener('change', this.#
|
|
370
|
+
if (this.#calFromRef) this.#calFromRef.removeEventListener('change', this.#onDaySelect);
|
|
371
|
+
if (this.#calToRef) this.#calToRef.removeEventListener('change', this.#onDaySelect);
|
|
369
372
|
document.removeEventListener('pointerdown', this.#onOutside);
|
|
370
373
|
document.removeEventListener('keydown', this.#onDocKey);
|
|
371
374
|
this.#anchorCleanup?.();
|
|
@@ -449,14 +452,16 @@ export class UIDateRangePicker extends UIFormElement {
|
|
|
449
452
|
// Cascade min/max + selection state into the calendar panes.
|
|
450
453
|
const range = parseRange(this.value);
|
|
451
454
|
const pending = this.#pending;
|
|
452
|
-
// Effective endpoints —
|
|
453
|
-
// value
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
|
|
459
|
-
|
|
455
|
+
// Effective endpoints — an in-flight pending selection REPLACES the
|
|
456
|
+
// committed value wholesale (no per-half fallback: mixing the new
|
|
457
|
+
// start with the old committed end would light a range the user
|
|
458
|
+
// never picked, gh#948). Push the SAME from/to onto BOTH grids so
|
|
459
|
+
// each independently lights up its in-range cells via
|
|
460
|
+
// `[data-in-range]`. Without this, only the two endpoint cells got
|
|
461
|
+
// `[data-selected]` and the days between them rendered as plain
|
|
462
|
+
// background — the "fill the cells between start and end dates" trap.
|
|
463
|
+
const effFrom = pending ? pending.from : (range?.from || '');
|
|
464
|
+
const effTo = pending ? pending.to : (range?.to || '');
|
|
460
465
|
const setRangeAttrs = (cal) => {
|
|
461
466
|
if (effFrom) cal.setAttribute('range-start', effFrom);
|
|
462
467
|
else cal.removeAttribute('range-start');
|
|
@@ -466,13 +471,13 @@ export class UIDateRangePicker extends UIFormElement {
|
|
|
466
471
|
if (this.#calFromRef) {
|
|
467
472
|
if (this.min) this.#calFromRef.setAttribute('min', this.min);
|
|
468
473
|
if (this.max) this.#calFromRef.setAttribute('max', this.max);
|
|
469
|
-
this.#calFromRef.value =
|
|
474
|
+
this.#calFromRef.value = effFrom;
|
|
470
475
|
setRangeAttrs(this.#calFromRef);
|
|
471
476
|
}
|
|
472
477
|
if (this.#calToRef) {
|
|
473
478
|
if (this.min) this.#calToRef.setAttribute('min', this.min);
|
|
474
479
|
if (this.max) this.#calToRef.setAttribute('max', this.max);
|
|
475
|
-
this.#calToRef.value =
|
|
480
|
+
this.#calToRef.value = effTo;
|
|
476
481
|
setRangeAttrs(this.#calToRef);
|
|
477
482
|
}
|
|
478
483
|
|
|
@@ -680,37 +685,31 @@ export class UIDateRangePicker extends UIFormElement {
|
|
|
680
685
|
}
|
|
681
686
|
};
|
|
682
687
|
|
|
683
|
-
|
|
688
|
+
// Unified day selection across BOTH grids — first click = start,
|
|
689
|
+
// second click = end, whichever pane the click lands in (gh#948).
|
|
690
|
+
// The panes' [data-cal-from]/[data-cal-to] roles are display-only
|
|
691
|
+
// (left month / next month); they no longer lock which half of the
|
|
692
|
+
// range a click writes. A second click EARLIER than the pending start
|
|
693
|
+
// restarts the selection at that day (the industry-standard dual-pane
|
|
694
|
+
// model) instead of emitting a reversed-range `invalid`. A click
|
|
695
|
+
// while a range is already committed starts a fresh selection.
|
|
696
|
+
#onDaySelect = (e) => {
|
|
684
697
|
if (this.readonly) return;
|
|
685
698
|
e.stopPropagation();
|
|
686
|
-
const iso = e.detail?.value ||
|
|
699
|
+
const iso = e.detail?.value || '';
|
|
687
700
|
if (!iso) return;
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if (toCandidate) {
|
|
697
|
-
this.#commitRange({ from: iso, to: toCandidate });
|
|
701
|
+
const startFresh =
|
|
702
|
+
!this.#pending?.from || // nothing pending (incl. committed-value state)
|
|
703
|
+
this.#pending.to || // previous selection fully picked
|
|
704
|
+
iso < this.#pending.from; // earlier than pending start → restart there
|
|
705
|
+
if (startFresh) {
|
|
706
|
+
this.#pending = { from: iso, to: '' };
|
|
707
|
+
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: { from: iso, to: null } } }));
|
|
708
|
+
return;
|
|
698
709
|
}
|
|
699
|
-
};
|
|
700
|
-
|
|
701
|
-
#onCalToChange = (e) => {
|
|
702
|
-
if (this.readonly) return;
|
|
703
|
-
e.stopPropagation();
|
|
704
|
-
const iso = e.detail?.value || this.#calToRef?.value || '';
|
|
705
|
-
if (!iso) return;
|
|
706
|
-
const existing = parseRange(this.value);
|
|
707
|
-
if (!this.#pending) this.#pending = { from: '', to: '' };
|
|
708
710
|
this.#pending.to = iso;
|
|
709
|
-
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: { from: this.#pending.from
|
|
710
|
-
|
|
711
|
-
if (fromCandidate) {
|
|
712
|
-
this.#commitRange({ from: fromCandidate, to: iso });
|
|
713
|
-
}
|
|
711
|
+
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: { from: this.#pending.from, to: iso } } }));
|
|
712
|
+
this.#commitRange({ from: this.#pending.from, to: iso });
|
|
714
713
|
};
|
|
715
714
|
|
|
716
715
|
#onOutside = (e) => {
|
|
@@ -158,6 +158,17 @@ date-range-picker-ui [slot="popover"] {
|
|
|
158
158
|
/* Opt into a wider anchor.js max-width cap so both month panes fit side-by-side
|
|
159
159
|
(the 32rem default clipped the second month). Viewport safety still applies. */
|
|
160
160
|
--popover-max-width: var(--date-range-picker-popover-max-width);
|
|
161
|
+
/* Content sizing lives in the STYLESHEET, not only in anchor.js inline
|
|
162
|
+
styles (gh#948): only the native-anchor path applies width/max-width
|
|
163
|
+
hints inline — the JS fallback (Safari/Firefox) and any placement
|
|
164
|
+
where anchoring fails otherwise leave the popover's size to whatever
|
|
165
|
+
the surrounding cascade produces, which is how the viewport-huge
|
|
166
|
+
mostly-empty panel renders. These caps make the popover content-sized
|
|
167
|
+
in every path; anchor.js inline styles still win where they apply. */
|
|
168
|
+
width: max-content;
|
|
169
|
+
max-width: min(calc(100vw - 1rem), var(--date-range-picker-popover-max-width));
|
|
170
|
+
max-height: calc(100vh - 2rem);
|
|
171
|
+
overflow: auto;
|
|
161
172
|
padding: var(--date-range-picker-py) var(--date-range-picker-px);
|
|
162
173
|
border: 1px solid var(--date-range-picker-popover-border);
|
|
163
174
|
border-radius: var(--date-range-picker-popover-radius);
|
|
@@ -10,9 +10,12 @@ description: >-
|
|
|
10
10
|
optional named preset shortcuts ("Today", "Last 7 days", "This month",
|
|
11
11
|
...). Default presentation is a trigger button showing the formatted
|
|
12
12
|
range; on activation a popover opens with two synchronized calendar
|
|
13
|
-
panes + a preset side rail. Composes <calendar-
|
|
14
|
-
emits {from, to} ISO 8601 dates.
|
|
15
|
-
|
|
13
|
+
panes + a preset side rail. Composes <calendar-grid-ui> twice;
|
|
14
|
+
emits {from, to} ISO 8601 dates. Both panes are ONE selection
|
|
15
|
+
surface: first click = start, second click = end, in either grid;
|
|
16
|
+
a second click earlier than the start restarts the range there.
|
|
17
|
+
Optional comparison-range mode emits a secondary range for
|
|
18
|
+
analytics surfaces.
|
|
16
19
|
# Per ADR-0027 — primitives that programmatically create other primitives
|
|
17
20
|
# do NOT auto-import them. Consumer (or demo shell) must explicitly import.
|
|
18
21
|
composes:
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
|
|
40
40
|
/* -- Header bar -- */
|
|
41
41
|
[slot="bar"] {
|
|
42
|
+
/* ensure('bar') APPENDS the bar after the authored slot content —
|
|
43
|
+
flex order puts it visually first (gh#1075: it rendered at the
|
|
44
|
+
bottom of the card, under the stage, without this). */
|
|
45
|
+
order: -1;
|
|
42
46
|
width: 100%;
|
|
43
47
|
padding: var(--demo-toggle-bar-py) var(--demo-toggle-bar-px);
|
|
44
48
|
background: var(--demo-toggle-bar-bg);
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
```html
|
|
6
6
|
<demo-toggle-ui label-on="With trait" label-off="Without trait" initial="off">
|
|
7
|
-
<div slot="off" style="
|
|
8
|
-
<div slot="on" style="
|
|
7
|
+
<div slot="off" style="text-align:center;color:var(--a-fg-muted);">Bare surface — no trait attached.</div>
|
|
8
|
+
<div slot="on" style="text-align:center;color:var(--md-sys-color-neutral-on-surface);">Same surface, with trait attached.</div>
|
|
9
9
|
</demo-toggle-ui>
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
```html
|
|
15
15
|
<demo-toggle-ui label-on="With shimmer-loading" label-off="Without shimmer-loading">
|
|
16
|
-
<div slot="off"
|
|
17
|
-
<div slot="on"
|
|
16
|
+
<div slot="off">Placeholder content (no trait)</div>
|
|
17
|
+
<div slot="on">Placeholder content (shimmer active)</div>
|
|
18
18
|
</demo-toggle-ui>
|
|
19
19
|
```
|
|
@@ -6,10 +6,21 @@
|
|
|
6
6
|
--description-list-term-min-width: 8rem;
|
|
7
7
|
|
|
8
8
|
/* ── Typography ── */
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
/* Term reads as a kicker/data-label (operator ruling 2026-08-11,
|
|
10
|
+
matching the docs site's [data-label] convention): code family at
|
|
11
|
+
label size, normal weight — a quiet technical eyebrow the medium-
|
|
12
|
+
weight desc answers. */
|
|
13
|
+
--description-list-term-font-family: var(--a-font-family-code);
|
|
14
|
+
--description-list-term-font-size: var(--a-label-size);
|
|
15
|
+
--description-list-term-weight: var(--a-weight-normal);
|
|
16
|
+
/* 1em, not a fixed step — the desc inherits the surrounding body/
|
|
17
|
+
context size (operator ruling 2026-08-11), so a dl inside prose
|
|
18
|
+
matches the prose and inside a compact card matches the card. */
|
|
19
|
+
--description-list-desc-font-size: 1em;
|
|
20
|
+
/* Semibold — the desc (value) carries the pair's content and reads
|
|
21
|
+
clearly bolder than the muted normal-weight kicker term (operator
|
|
22
|
+
rulings 2026-08-11: first normal → medium, then medium → semibold). */
|
|
23
|
+
--description-list-desc-weight: var(--a-weight-semibold);
|
|
13
24
|
|
|
14
25
|
/* ── Colors ── */
|
|
15
26
|
--description-list-term-fg: var(--a-fg-muted);
|
|
@@ -29,6 +40,7 @@
|
|
|
29
40
|
[data-dl-term],
|
|
30
41
|
dt {
|
|
31
42
|
margin: 0;
|
|
43
|
+
font-family: var(--description-list-term-font-family);
|
|
32
44
|
font-size: var(--description-list-term-font-size);
|
|
33
45
|
font-weight: var(--description-list-term-weight);
|
|
34
46
|
color: var(--description-list-term-fg);
|
|
@@ -47,14 +47,15 @@
|
|
|
47
47
|
"default": ""
|
|
48
48
|
},
|
|
49
49
|
"variant": {
|
|
50
|
-
"description": "Semantic variant",
|
|
50
|
+
"description": "Semantic variant — `default | info | success | warning | danger | primary`. `primary` is the brand-emphasis fill (accent-filled bg + on-accent text), ported from toast.css's `primary` variant (gh#1104) now that feed-item-ui is the real paint site for posted toasts.",
|
|
51
51
|
"type": "string",
|
|
52
52
|
"enum": [
|
|
53
53
|
"default",
|
|
54
54
|
"info",
|
|
55
55
|
"success",
|
|
56
56
|
"warning",
|
|
57
|
-
"danger"
|
|
57
|
+
"danger",
|
|
58
|
+
"primary"
|
|
58
59
|
],
|
|
59
60
|
"default": "default"
|
|
60
61
|
}
|
|
@@ -25,7 +25,12 @@ props:
|
|
|
25
25
|
type: string
|
|
26
26
|
default: ""
|
|
27
27
|
variant:
|
|
28
|
-
description:
|
|
28
|
+
description: >-
|
|
29
|
+
Semantic variant — `default | info | success | warning | danger |
|
|
30
|
+
primary`. `primary` is the brand-emphasis fill (accent-filled bg +
|
|
31
|
+
on-accent text), ported from toast.css's `primary` variant
|
|
32
|
+
(gh#1104) now that feed-item-ui is the real paint site for
|
|
33
|
+
posted toasts.
|
|
29
34
|
type: string
|
|
30
35
|
default: default
|
|
31
36
|
enum:
|
|
@@ -34,6 +39,7 @@ props:
|
|
|
34
39
|
- success
|
|
35
40
|
- warning
|
|
36
41
|
- danger
|
|
42
|
+
- primary
|
|
37
43
|
duration:
|
|
38
44
|
description: Auto-fade timer in ms; null/0 = sticky (requires user input)
|
|
39
45
|
type: number
|
package/components/feed/feed.css
CHANGED
|
@@ -99,7 +99,16 @@ feed-item-ui[data-closing] {
|
|
|
99
99
|
/* Item — atomic feed entry. */
|
|
100
100
|
@scope (feed-item-ui) {
|
|
101
101
|
:where(:scope) {
|
|
102
|
-
|
|
102
|
+
/* -container-high (NOT plain -background/-container) — a top-layer
|
|
103
|
+
overlay item needs a tonal step strong enough to read as its own
|
|
104
|
+
elevated surface against arbitrary page content behind it
|
|
105
|
+
(gh#1091). Plain -background is literally the page's own bg
|
|
106
|
+
token, so an un-varianted item had ZERO tonal separation. Every
|
|
107
|
+
family (incl. neutral) has a generated -container-high role in
|
|
108
|
+
colors/material-static.css — the same tier `--a-{family}-subtle`
|
|
109
|
+
aliases to (component-token-contract.md's `-strong` semantics
|
|
110
|
+
note). */
|
|
111
|
+
--feed-item-bg: var(--md-sys-color-neutral-container-high);
|
|
103
112
|
--feed-item-fg: var(--md-sys-color-neutral-on-surface);
|
|
104
113
|
--feed-item-border: var(--md-sys-color-neutral-outline-variant);
|
|
105
114
|
--feed-item-radius: var(--a-radius-md);
|
|
@@ -117,10 +126,20 @@ feed-item-ui[data-closing] {
|
|
|
117
126
|
NOT --a-{variant}-fg (= --a-{variant}-text-strong, locked for solid
|
|
118
127
|
--variant-strong fills) — pairing a locked fg with a flipping bg goes
|
|
119
128
|
low-contrast in one scheme. Mirrors alert.css + toast.css wiring. */
|
|
120
|
-
:scope[variant="info"] { --feed-item-fg: var(--md-sys-color-info-on-surface); --feed-item-bg: var(--md-sys-color-info-container); }
|
|
121
|
-
:scope[variant="success"] { --feed-item-fg: var(--md-sys-color-success-on-surface); --feed-item-bg: var(--md-sys-color-success-container); }
|
|
122
|
-
:scope[variant="warning"] { --feed-item-fg: var(--md-sys-color-warning-on-surface); --feed-item-bg: var(--md-sys-color-warning-container); }
|
|
123
|
-
:scope[variant="danger"] { --feed-item-fg: var(--md-sys-color-danger-on-surface); --feed-item-bg: var(--md-sys-color-danger-container); }
|
|
129
|
+
:scope[variant="info"] { --feed-item-fg: var(--md-sys-color-info-on-surface); --feed-item-bg: var(--md-sys-color-info-container-high); }
|
|
130
|
+
:scope[variant="success"] { --feed-item-fg: var(--md-sys-color-success-on-surface); --feed-item-bg: var(--md-sys-color-success-container-high); }
|
|
131
|
+
:scope[variant="warning"] { --feed-item-fg: var(--md-sys-color-warning-on-surface); --feed-item-bg: var(--md-sys-color-warning-container-high); }
|
|
132
|
+
:scope[variant="danger"] { --feed-item-fg: var(--md-sys-color-danger-on-surface); --feed-item-bg: var(--md-sys-color-danger-container-high); }
|
|
133
|
+
/* `primary` — the accent-filled "branded" tone (welcome banners,
|
|
134
|
+
feature-launch nudges), ported from toast.css's `primary` variant
|
|
135
|
+
(gh#1104 — toast.css documented this but its rules never paint;
|
|
136
|
+
feed-item-ui is the real render surface post-migration). No
|
|
137
|
+
border override, matching this block's existing two-property
|
|
138
|
+
shape (unlike toast.css's variant block, which does override
|
|
139
|
+
border). Deliberately NOT the -container-high convention above —
|
|
140
|
+
primary is a stronger/bolder branded solid-fill per gh#1104's own
|
|
141
|
+
acceptance criteria, not a softer semantic-status tone. */
|
|
142
|
+
:scope[variant="primary"] { --feed-item-fg: var(--md-sys-color-primary-on-primary); --feed-item-bg: var(--a-primary-bg); }
|
|
124
143
|
|
|
125
144
|
:scope {
|
|
126
145
|
box-sizing: border-box;
|
|
@@ -143,6 +162,10 @@ feed-item-ui[data-closing] {
|
|
|
143
162
|
opacity: 0;
|
|
144
163
|
transform: translateY(0.5rem);
|
|
145
164
|
pointer-events: auto;
|
|
165
|
+
/* Elevated-surface separation over busy page content (gh#1091) —
|
|
166
|
+
mirrors loading-overlay.css's [variant="blur"] treatment. */
|
|
167
|
+
backdrop-filter: blur(4px);
|
|
168
|
+
-webkit-backdrop-filter: blur(4px);
|
|
146
169
|
}
|
|
147
170
|
:scope [slot="body"] {
|
|
148
171
|
flex: 1;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
```html
|
|
14
14
|
<row-ui gap="6" align="start" wrap>
|
|
15
15
|
<col-ui gap="2">
|
|
16
|
-
<text-ui variant="caption" color="muted">
|
|
17
|
-
<div
|
|
16
|
+
<text-ui variant="caption" color="muted">scale="ui-sm"</text-ui>
|
|
17
|
+
<div scale="ui-sm" style="width:220px">
|
|
18
18
|
<field-ui label="Email">
|
|
19
19
|
<input-ui type="email" placeholder="you@example.com"></input-ui>
|
|
20
20
|
</field-ui>
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
</div>
|
|
30
30
|
</col-ui>
|
|
31
31
|
<col-ui gap="2">
|
|
32
|
-
<text-ui variant="caption" color="muted">
|
|
33
|
-
<div
|
|
32
|
+
<text-ui variant="caption" color="muted">scale="content-md"</text-ui>
|
|
33
|
+
<div scale="content-md" style="width:220px">
|
|
34
34
|
<field-ui label="Email">
|
|
35
35
|
<input-ui type="email" placeholder="you@example.com"></input-ui>
|
|
36
36
|
</field-ui>
|
|
@@ -66,6 +66,14 @@ export class UIImage extends UIElement {
|
|
|
66
66
|
}
|
|
67
67
|
this.#img.addEventListener('load', this.#onLoad);
|
|
68
68
|
this.#img.addEventListener('error', this.#onError);
|
|
69
|
+
// Missed-load guard (gh#999): an image already complete when these
|
|
70
|
+
// listeners attach — a parse-time-loaded authored [slot="image"], or a
|
|
71
|
+
// cache hit racing the upgrade — fired `load` before we were listening.
|
|
72
|
+
// Without this check [data-loaded] never stamps and the reveal
|
|
73
|
+
// (skeleton → img opacity 1) never happens: the permanent blank box
|
|
74
|
+
// gh#988 reported. naturalWidth guards the broken-image case, which
|
|
75
|
+
// fires `error` (still listened) rather than a false reveal.
|
|
76
|
+
if (this.#img.complete && this.#img.naturalWidth > 0) this.#onLoad();
|
|
69
77
|
|
|
70
78
|
this.#applySize();
|
|
71
79
|
}
|
|
@@ -51,6 +51,12 @@ export class UIModal extends UIElement {
|
|
|
51
51
|
#closeTimer = null;
|
|
52
52
|
#dialogRef = null;
|
|
53
53
|
|
|
54
|
+
// Monotonic, instance-agnostic counter for the fallback aria-labelledby
|
|
55
|
+
// id stamped onto an author's [slot="heading"] (see render()) — multiple
|
|
56
|
+
// <modal-ui> instances on one page must not collide on the same id.
|
|
57
|
+
// (Extracted from PR #944 — gh#947.)
|
|
58
|
+
static #headingIdSeq = 0;
|
|
59
|
+
|
|
54
60
|
// Phosphor icons this primitive auto-stamps (without consumer markup).
|
|
55
61
|
// Aggregated by installIconLoadersForRegistered() across all defined
|
|
56
62
|
// elements. Audited by check-required-icons.mjs.
|
|
@@ -150,8 +156,25 @@ export class UIModal extends UIElement {
|
|
|
150
156
|
const panel = this.ensure('panel');
|
|
151
157
|
if (panel.parentElement !== dialog) dialog.appendChild(panel);
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
// Header — accept an authored bare <header> or [slot="header"] child
|
|
160
|
+
// (card-ui style), mirroring the footer contract below. Without this
|
|
161
|
+
// branch an authored <header> fell through to the loose-children
|
|
162
|
+
// migration and rendered inside the BODY, displacing icon/heading to
|
|
163
|
+
// the bottom of the modal (gh#941).
|
|
164
|
+
const userHeader = [...this.children].find(c =>
|
|
165
|
+
c.getAttribute('slot') === 'header' || (!c.getAttribute('slot') && c.localName === 'header')
|
|
166
|
+
);
|
|
167
|
+
let header;
|
|
168
|
+
if (userHeader) {
|
|
169
|
+
if (userHeader.getAttribute('slot') !== 'header') userHeader.setAttribute('slot', 'header');
|
|
170
|
+
if (userHeader.parentElement !== panel) panel.prepend(userHeader);
|
|
171
|
+
panel.querySelector(':scope > [slot="header"][data-stamped]')?.remove();
|
|
172
|
+
header = userHeader;
|
|
173
|
+
} else {
|
|
174
|
+
header = this.ensure('header');
|
|
175
|
+
header.setAttribute('data-stamped', '');
|
|
176
|
+
if (header.parentElement !== panel) panel.prepend(header);
|
|
177
|
+
}
|
|
155
178
|
// Only set `text=` when actually present — the CSS rule
|
|
156
179
|
// `:scope [slot="header"][text]:not([text=""])::before { content: attr(text); flex: 1 }`
|
|
157
180
|
// generates a flex:1 pseudo-element. Setting `text=""` unconditionally
|
|
@@ -165,6 +188,24 @@ export class UIModal extends UIElement {
|
|
|
165
188
|
header.removeAttribute('text');
|
|
166
189
|
}
|
|
167
190
|
|
|
191
|
+
// Accessible name — [text] stamps aria-label (host + dialog), the
|
|
192
|
+
// long-standing contract above. Absent that, fall back to the author's
|
|
193
|
+
// [slot="heading"] via aria-labelledby so a heading-only modal (e.g.
|
|
194
|
+
// destructive-confirm-modal, gh#941) still satisfies the WAI-ARIA APG
|
|
195
|
+
// dialog-modal pattern's accessible-name requirement. Re-evaluated
|
|
196
|
+
// every render so a later `text=` set, or the heading being removed,
|
|
197
|
+
// clears the reference instead of leaving it stale. (gh#947, from
|
|
198
|
+
// PR #944.)
|
|
199
|
+
const authoredHeading = header.querySelector(':scope > [slot="heading"]');
|
|
200
|
+
if (!this.text && authoredHeading) {
|
|
201
|
+
if (!authoredHeading.id) authoredHeading.id = `modal-heading-${++UIModal.#headingIdSeq}`;
|
|
202
|
+
if (dialog.getAttribute('aria-labelledby') !== authoredHeading.id) {
|
|
203
|
+
dialog.setAttribute('aria-labelledby', authoredHeading.id);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
dialog.removeAttribute('aria-labelledby');
|
|
207
|
+
}
|
|
208
|
+
|
|
168
209
|
if (!this.permanent) {
|
|
169
210
|
const close = this.ensure('close');
|
|
170
211
|
if (close.parentElement !== header) header.appendChild(close);
|
|
@@ -131,6 +131,29 @@
|
|
|
131
131
|
flex: 1;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/* ═══════ Header icon + heading pair (drawer-ui/card-ui parity) ═══════
|
|
135
|
+
Author-supplied icon/heading content nested inside <header> — an
|
|
136
|
+
alternative to [text]. Positioned by tag + DOM order, not by reading
|
|
137
|
+
[slot="icon"]/[slot="heading"] (AGENTS.md: "slot= attributes are
|
|
138
|
+
decorative metadata; positioning is CSS by tag + ancestor + DOM
|
|
139
|
+
order") — `button-ui` identifies the stamped auto-close affordance by
|
|
140
|
+
TAG, and render() guarantees it's the header's trailing child when
|
|
141
|
+
present. The header's last non-button child claims the row's free
|
|
142
|
+
space (heading position); any earlier content child (a leading
|
|
143
|
+
icon/avatar) stays fixed-width. (gh#947, from PR #944.) */
|
|
144
|
+
:scope [slot="header"] > :not(button-ui):has(~ :not(button-ui)) {
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
flex-shrink: 0;
|
|
148
|
+
}
|
|
149
|
+
:scope [slot="header"] > :not(button-ui):not(:has(~ :not(button-ui))) {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: var(--a-space-2);
|
|
153
|
+
flex: 1;
|
|
154
|
+
min-width: 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
/* ═══════ Close button (button-ui) ═══════ */
|
|
135
158
|
:scope [slot="close"] {
|
|
136
159
|
flex-shrink: 0;
|
|
@@ -10,15 +10,32 @@
|
|
|
10
10
|
--nav-group-font-size-sm: var(--a-ui-sm);
|
|
11
11
|
--nav-group-font-size-lg: var(--a-ui-lg);
|
|
12
12
|
--nav-group-font-weight: var(--a-weight-normal);
|
|
13
|
-
|
|
13
|
+
/* State color spec (gh#968, operator ruling 2026-08-11) — one
|
|
14
|
+
language with nav-item-ui:
|
|
15
|
+
idle — bg transparent · icon NEUTRAL · text NEUTRAL-HIGH
|
|
16
|
+
hover — bg container-low · icon + text ON-SURFACE
|
|
17
|
+
selected — bg container · icon + text ON-SURFACE
|
|
18
|
+
The selected icon no longer takes primary (supersedes gh#522's
|
|
19
|
+
glyph ruling); Figma's neutral/surfaceBright was considered for the
|
|
20
|
+
selected fill and RULED OUT — container stays (gh#795's mirror). */
|
|
21
|
+
--nav-group-fg: var(--md-sys-color-neutral-high);
|
|
14
22
|
--nav-group-fg-hover: var(--a-fg-strong);
|
|
15
23
|
--nav-group-fg-muted: var(--a-fg-muted);
|
|
16
24
|
--nav-group-fg-selected: var(--a-fg-strong);
|
|
17
|
-
--nav-group-icon-fg
|
|
25
|
+
--nav-group-icon-fg: var(--md-sys-color-neutral);
|
|
26
|
+
--nav-group-icon-fg-selected: var(--a-fg-strong);
|
|
18
27
|
--nav-group-bg-hover: var(--a-bg-muted);
|
|
19
|
-
--nav-group-bg-selected: var(--a-bg-selected); /* mirrors --nav-item-bg-selected
|
|
28
|
+
--nav-group-bg-selected: var(--a-bg-selected); /* mirrors --nav-item-bg-selected (gh#795) */
|
|
20
29
|
--nav-group-icon-size: calc(var(--nav-group-row-height) - var(--a-space-2));
|
|
21
30
|
--nav-group-badge-size: var(--a-ui-sm);
|
|
31
|
+
/* Continuous indent rail color (gh#1016) — was referenced at the
|
|
32
|
+
::after usage site below but never given a base value here, the
|
|
33
|
+
same empty-var failure class --nav-gap already documents in this
|
|
34
|
+
file: an undefined custom property resolves to the guaranteed-
|
|
35
|
+
invalid value, so `background: var(--nav-group-indent-rail-bg)`
|
|
36
|
+
painted nothing. Same house role accordion, agent- components,
|
|
37
|
+
chart etc. use for subtle structural lines. */
|
|
38
|
+
--nav-group-indent-rail-bg: var(--md-sys-color-neutral-outline-variant);
|
|
22
39
|
|
|
23
40
|
--nav-item-child-height: var(--a-size-sm);
|
|
24
41
|
--nav-item-child-font: var(--nav-group-font-size);
|
|
@@ -48,7 +65,10 @@
|
|
|
48
65
|
top: var(--nav-group-row-height);
|
|
49
66
|
bottom: calc(var(--nav-group-row-height) / 2);
|
|
50
67
|
width: 1px;
|
|
51
|
-
|
|
68
|
+
/* Explicit fallback — same defensive shape --nav-gap got above —
|
|
69
|
+
so an ancestor/override cascade that sets this token to an empty
|
|
70
|
+
string still paints a rail instead of silently disappearing. */
|
|
71
|
+
background: var(--nav-group-indent-rail-bg, var(--md-sys-color-neutral-outline-variant));
|
|
52
72
|
pointer-events: none;
|
|
53
73
|
}
|
|
54
74
|
/* Selected-look header treatment keys on [selected] — the CSS half of
|
|
@@ -119,7 +139,9 @@
|
|
|
119
139
|
width: var(--nav-group-icon-size);
|
|
120
140
|
height: var(--nav-group-icon-size);
|
|
121
141
|
flex-shrink: 0;
|
|
122
|
-
|
|
142
|
+
/* idle icon = NEUTRAL (gh#968 state spec), one step quieter than the
|
|
143
|
+
header's neutral-high text. */
|
|
144
|
+
color: var(--nav-group-icon-fg);
|
|
123
145
|
transition: color var(--nav-duration-fast) var(--nav-easing);
|
|
124
146
|
display: flex;
|
|
125
147
|
align-items: center;
|
|
@@ -40,11 +40,19 @@ nav-item-ui[selected] [slot="icon"]:empty::before {
|
|
|
40
40
|
--nav-item-font-size-sm: var(--a-ui-sm);
|
|
41
41
|
--nav-item-font-size-lg: var(--a-ui-lg);
|
|
42
42
|
--nav-item-font-weight: var(--a-weight-normal);
|
|
43
|
-
|
|
43
|
+
/* State color spec (gh#968, operator ruling 2026-08-11):
|
|
44
|
+
idle — bg transparent · icon NEUTRAL · text NEUTRAL-HIGH
|
|
45
|
+
hover — bg container-low · icon + text ON-SURFACE
|
|
46
|
+
selected — bg container · icon + text ON-SURFACE
|
|
47
|
+
Idle sits two quiet steps down (icon quieter than text); hover and
|
|
48
|
+
selected converge on on-surface. The selected ICON no longer takes
|
|
49
|
+
the primary accent (supersedes gh#522 for the glyph) — the accent
|
|
50
|
+
BAR remains the primary selection marker. */
|
|
51
|
+
--nav-item-fg: var(--md-sys-color-neutral-high);
|
|
44
52
|
--nav-item-fg-hover: var(--a-ui-text-hover);
|
|
45
53
|
--nav-item-fg-selected: var(--a-ui-text-selected);
|
|
46
|
-
--nav-item-icon-fg: var(--
|
|
47
|
-
--nav-item-icon-fg-selected: var(--
|
|
54
|
+
--nav-item-icon-fg: var(--md-sys-color-neutral);
|
|
55
|
+
--nav-item-icon-fg-selected: var(--a-ui-text-selected);
|
|
48
56
|
/* Two real container roles, one mechanism — no opacity synth.
|
|
49
57
|
hover = --a-bg-muted (--md-sys-color-neutral-container-low, 10%),
|
|
50
58
|
matching nav-group-ui's own --nav-group-bg-hover exactly.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://adiaui.dev/a2ui/v0_9/components/OptionCard.json",
|
|
4
4
|
"title": "OptionCard",
|
|
5
|
-
"description": "Selectable card with radio semantics. A \"rich radio\" — single-select one of N where each option carries a heading, optional description, and optional
|
|
5
|
+
"description": "Selectable card with radio semantics. A \"rich radio\" — single-select one of N where each option carries a heading, optional description, and an optional icon at the trailing end of the header row. Siblings sharing a `name` form a radiogroup. The whole card is the click target; a CSS-rendered radio circle in the top-left signals state. Renders on a container-low tonal surface (no outline as the primary boundary); the checked state keeps an accent border + fill so selection stays unmistakable. Form-associated, so `name=value` submits with the parent form when checked.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"allOf": [
|
|
8
8
|
{
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"default": ""
|
|
43
43
|
},
|
|
44
44
|
"icon": {
|
|
45
|
-
"description": "Optional Phosphor icon name. Stamped as a
|
|
45
|
+
"description": "Optional Phosphor icon name. Stamped as a trailing <icon-ui slot=\"icon\"> when set.",
|
|
46
46
|
"type": "string",
|
|
47
47
|
"default": ""
|
|
48
48
|
},
|
|
49
49
|
"layout": {
|
|
50
|
-
"description": "Internal layout. `default` puts the indicator on the left and the icon
|
|
50
|
+
"description": "Internal layout. `default` puts the indicator on the left and, when [icon] is set, the icon at the trailing end of the header row alongside the heading. `tile` stacks vertically — icon top-left, indicator top-right, heading + description below — for hero pickers (source / role / plan tiles) where the icon is a primary brand cue.",
|
|
51
51
|
"type": "string",
|
|
52
52
|
"enum": [
|
|
53
53
|
"default",
|
|
@@ -171,22 +171,22 @@
|
|
|
171
171
|
"tag": "option-card-ui",
|
|
172
172
|
"tokens": {
|
|
173
173
|
"--option-card-bg": {
|
|
174
|
-
"description": "Default background."
|
|
174
|
+
"description": "Default background (container-low tonal surface, gh#1018)."
|
|
175
175
|
},
|
|
176
176
|
"--option-card-bg-checked": {
|
|
177
177
|
"description": "Background when checked."
|
|
178
178
|
},
|
|
179
179
|
"--option-card-bg-hover": {
|
|
180
|
-
"description": "Hover background (non-checked)."
|
|
180
|
+
"description": "Hover background (non-checked) — one tonal step up from --option-card-bg so hover stays visibly distinct."
|
|
181
181
|
},
|
|
182
182
|
"--option-card-border": {
|
|
183
|
-
"description": "Default border color."
|
|
183
|
+
"description": "Default border color (transparent — the tonal bg is the primary boundary, not an outline; gh#1018)."
|
|
184
184
|
},
|
|
185
185
|
"--option-card-border-checked": {
|
|
186
186
|
"description": "Border color when checked."
|
|
187
187
|
},
|
|
188
188
|
"--option-card-border-hover": {
|
|
189
|
-
"description": "Hover border color (non-checked)."
|
|
189
|
+
"description": "Hover border color (non-checked; transparent by default, matching the no-outline base)."
|
|
190
190
|
},
|
|
191
191
|
"--option-card-desc-color": {
|
|
192
192
|
"description": "Description text color."
|
|
@@ -228,13 +228,13 @@
|
|
|
228
228
|
"description": "Heading font weight."
|
|
229
229
|
},
|
|
230
230
|
"--option-card-icon-color": {
|
|
231
|
-
"description": "
|
|
231
|
+
"description": "Trailing icon color when the card is not checked."
|
|
232
232
|
},
|
|
233
233
|
"--option-card-icon-color-checked": {
|
|
234
|
-
"description": "
|
|
234
|
+
"description": "Trailing icon color when the card is checked."
|
|
235
235
|
},
|
|
236
236
|
"--option-card-icon-size": {
|
|
237
|
-
"description": "
|
|
237
|
+
"description": "Trailing icon size (sets `--a-icon-size` on the slotted icon-ui)."
|
|
238
238
|
},
|
|
239
239
|
"--option-card-padding-block": {
|
|
240
240
|
"description": "Vertical padding inside the card."
|