@adia-ai/web-components 0.6.50 → 0.7.0
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 +120 -0
- package/components/action-list/action-list.css +1 -1
- package/components/agent-artifact/agent-artifact.class.js +10 -10
- package/components/agent-artifact/agent-artifact.css +1 -1
- package/components/agent-reasoning/agent-reasoning.class.js +51 -0
- package/components/agent-reasoning/agent-reasoning.css +49 -22
- package/components/alert/alert.class.js +8 -1
- package/components/alert/alert.css +13 -1
- package/components/avatar/avatar.a2ui.json +2 -14
- package/components/avatar/avatar.class.js +3 -15
- package/components/avatar/avatar.d.ts +2 -4
- package/components/avatar/avatar.yaml +1 -18
- package/components/breadcrumb/breadcrumb.css +4 -1
- package/components/button/button.a2ui.json +3 -0
- package/components/button/button.css +14 -3
- package/components/button/button.yaml +5 -0
- package/components/calendar-grid/calendar-grid.css +1 -1
- package/components/calendar-picker/calendar-picker.css +5 -2
- package/components/chart/chart.a2ui.json +0 -18
- package/components/chart/chart.class.js +8 -50
- package/components/chart/chart.css +1 -15
- package/components/chart/chart.d.ts +0 -4
- package/components/chart/chart.yaml +0 -24
- package/components/color-input/color-input.css +4 -1
- package/components/combobox/combobox.class.js +11 -0
- package/components/combobox/combobox.css +8 -0
- package/components/date-range-picker/date-range-picker.class.js +5 -1
- package/components/date-range-picker/date-range-picker.css +12 -2
- package/components/datetime-picker/datetime-picker.class.js +3 -0
- package/components/datetime-picker/datetime-picker.css +16 -2
- package/components/empty-state/empty-state.css +11 -4
- package/components/field/field.css +17 -6
- package/components/grid/grid.a2ui.json +5 -0
- package/components/grid/grid.class.js +16 -6
- package/components/grid/grid.css +17 -3
- package/components/grid/grid.d.ts +2 -0
- package/components/grid/grid.yaml +9 -0
- package/components/heatmap/heatmap.class.js +9 -3
- package/components/heatmap/heatmap.css +19 -2
- package/components/image/image.css +4 -1
- package/components/input/input.css +1 -1
- package/components/integration-card/integration-card.class.js +31 -7
- package/components/integration-card/integration-card.test.js +12 -1
- package/components/kbd/kbd.a2ui.json +3 -2
- package/components/kbd/kbd.css +7 -4
- package/components/kbd/kbd.d.ts +2 -2
- package/components/kbd/kbd.yaml +2 -1
- package/components/list/list.class.js +8 -1
- package/components/menu/menu.css +4 -1
- package/components/modal/modal.class.js +10 -1
- package/components/modal/modal.css +9 -0
- package/components/option-card/option-card.a2ui.json +3 -0
- package/components/option-card/option-card.css +44 -19
- package/components/option-card/option-card.yaml +5 -0
- package/components/otp-input/otp-input.css +25 -10
- package/components/page/page.css +64 -11
- package/components/pagination/pagination.class.js +1 -1
- package/components/pagination/pagination.css +9 -1
- package/components/pipeline-status/pipeline-status.css +6 -0
- package/components/popover/popover.css +12 -1
- package/components/preview/preview.css +30 -3
- package/components/progress-row/progress-row.css +3 -1
- package/components/qr-code/qr-code.css +4 -1
- package/components/segmented/segmented.css +4 -1
- package/components/select/select.a2ui.json +1 -1
- package/components/select/select.class.js +63 -7
- package/components/select/select.css +18 -0
- package/components/select/select.yaml +9 -2
- package/components/stack/stack.a2ui.json +12 -1
- package/components/stack/stack.d.ts +2 -2
- package/components/stack/stack.yaml +13 -1
- package/components/stat/stat.a2ui.json +5 -0
- package/components/stat/stat.css +55 -0
- package/components/stat/stat.d.ts +2 -0
- package/components/stat/stat.js +4 -0
- package/components/stat/stat.yaml +9 -0
- package/components/swiper/swiper.class.js +14 -6
- package/components/switch/switch.css +13 -0
- package/components/table/table.a2ui.json +2 -2
- package/components/table/table.css +13 -1
- package/components/table/table.yaml +2 -2
- package/components/time-picker/time-picker.css +4 -1
- package/components/timeline/timeline.class.js +3 -3
- package/components/timeline/timeline.css +23 -5
- package/components/toggle-group/toggle-group.css +4 -1
- package/components/toggle-scheme/toggle-scheme.css +4 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +81 -81
- package/package.json +3 -3
- package/styles/api/layout.css +7 -0
- package/styles/api/text.css +9 -5
- package/styles/index.css +11 -2
- package/styles/prose.css +8 -0
- package/styles/resets.css +5 -5
- package/styles/themes.css +8 -1
- package/styles/tokens.css +3 -3
- package/styles/type/elements.css +73 -0
- package/styles/type/roles.css +14 -49
- package/styles/type/scale.css +0 -5
- package/styles/typography.css +3 -3
package/components/page/page.css
CHANGED
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
/* ── Padding default (when [padding] is set without a value) ── */
|
|
10
10
|
--page-padding-default: var(--a-space-6);
|
|
11
11
|
|
|
12
|
+
/* ── Region rhythm — vertical gap between header / section / footer,
|
|
13
|
+
mirroring card-ui's section inset. The page [padding] supplies the
|
|
14
|
+
outer frame; this is the inter-region spacing. ── */
|
|
15
|
+
--page-inset-default: var(--a-space-5);
|
|
16
|
+
|
|
17
|
+
/* ── Padded-region sub-surface (a section[padding] inside the page) ── */
|
|
18
|
+
--page-region-bg-default: var(--a-bg-subtle);
|
|
19
|
+
--page-region-radius-default: var(--a-radius-md);
|
|
20
|
+
|
|
12
21
|
/* ── Surfaces ── */
|
|
13
22
|
--page-bg-default: var(--a-canvas-0);
|
|
14
23
|
--page-fg-default: var(--a-fg);
|
|
@@ -23,6 +32,9 @@
|
|
|
23
32
|
box-sizing: border-box;
|
|
24
33
|
display: block;
|
|
25
34
|
width: 100%;
|
|
35
|
+
/* --page-pad carries the resolved [padding] value so the sticky header can
|
|
36
|
+
bleed back over it (card-ui pattern) — see the sticky-header rule. */
|
|
37
|
+
padding: var(--page-pad, 0);
|
|
26
38
|
background: var(--page-bg, var(--page-bg-default));
|
|
27
39
|
color: var(--page-fg, var(--page-fg-default));
|
|
28
40
|
}
|
|
@@ -33,17 +45,47 @@
|
|
|
33
45
|
:scope[max-width="wide"] { max-width: var(--page-max-width-wide, var(--page-max-width-wide-default)); margin-inline: auto; }
|
|
34
46
|
:scope[max-width="full"] { max-width: var(--page-max-width-full, var(--page-max-width-full-default)); }
|
|
35
47
|
|
|
36
|
-
/* ── Padding scale (mirrors --a-space-N) ── */
|
|
37
|
-
:scope[padding=""] {
|
|
38
|
-
:scope[padding="0"] {
|
|
39
|
-
:scope[padding="1"] {
|
|
40
|
-
:scope[padding="2"] {
|
|
41
|
-
:scope[padding="3"] {
|
|
42
|
-
:scope[padding="4"] {
|
|
43
|
-
:scope[padding="5"] {
|
|
44
|
-
:scope[padding="6"] {
|
|
45
|
-
:scope[padding="7"] {
|
|
46
|
-
:scope[padding="8"] {
|
|
48
|
+
/* ── Padding scale (mirrors --a-space-N) — sets --page-pad, applied above ── */
|
|
49
|
+
:scope[padding=""] { --page-pad: var(--page-padding-default); }
|
|
50
|
+
:scope[padding="0"] { --page-pad: 0; }
|
|
51
|
+
:scope[padding="1"] { --page-pad: var(--a-space-1); }
|
|
52
|
+
:scope[padding="2"] { --page-pad: var(--a-space-2); }
|
|
53
|
+
:scope[padding="3"] { --page-pad: var(--a-space-3); }
|
|
54
|
+
:scope[padding="4"] { --page-pad: var(--a-space-4); }
|
|
55
|
+
:scope[padding="5"] { --page-pad: var(--a-space-5); }
|
|
56
|
+
:scope[padding="6"] { --page-pad: var(--a-space-6); }
|
|
57
|
+
:scope[padding="7"] { --page-pad: var(--a-space-7); }
|
|
58
|
+
:scope[padding="8"] { --page-pad: var(--a-space-8); }
|
|
59
|
+
|
|
60
|
+
/* ═══════ Region model — header / section / footer ═══════
|
|
61
|
+
Mirrors card-ui's section model, adapted to the page's [padding] frame:
|
|
62
|
+
the page [padding] is the OUTER inset; these rules add the vertical
|
|
63
|
+
rhythm BETWEEN regions plus the [bleed] / [padding] modifiers. The
|
|
64
|
+
page's own @scope styling the slot primitives is what the docs promise
|
|
65
|
+
([<header>] / [<section>] / [<footer>] and their -ui variants). */
|
|
66
|
+
|
|
67
|
+
/* Vertical rhythm: every region after the first picks up a top margin so
|
|
68
|
+
header → body → footer breathe consistently (the first region hugs the
|
|
69
|
+
padding edge). */
|
|
70
|
+
:scope > :where(header, header-ui, section, section-ui, footer, footer-ui)
|
|
71
|
+
~ :where(header, header-ui, section, section-ui, footer, footer-ui) {
|
|
72
|
+
margin-block-start: var(--page-inset, var(--page-inset-default));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* [bleed] — edge-to-edge region: cancel the page's inline [padding] so
|
|
76
|
+
full-width content (hero, banner, table, chart) reaches the page edges.
|
|
77
|
+
Resolves to 0 (no-op) when the page has no padding. */
|
|
78
|
+
:scope > :where(header, header-ui, section, section-ui, footer, footer-ui)[bleed] {
|
|
79
|
+
margin-inline: calc(-1 * var(--page-pad, 0));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* [padding] on a region — a padded sub-surface with its own background +
|
|
83
|
+
radius, like card-ui's section[padding]. */
|
|
84
|
+
:scope > :where(section, section-ui)[padding] {
|
|
85
|
+
padding: var(--page-inset, var(--page-inset-default));
|
|
86
|
+
background: var(--page-region-bg, var(--page-region-bg-default));
|
|
87
|
+
border-radius: var(--page-region-radius, var(--page-region-radius-default));
|
|
88
|
+
}
|
|
47
89
|
|
|
48
90
|
/* ── Scroll container ── */
|
|
49
91
|
:scope[scroll] {
|
|
@@ -53,11 +95,22 @@
|
|
|
53
95
|
}
|
|
54
96
|
|
|
55
97
|
/* ── Sticky-header support ── */
|
|
98
|
+
/* Drop the page's TOP padding when the header is sticky — that gap is where
|
|
99
|
+
scrolling content used to peek ABOVE the pinned header. The header then
|
|
100
|
+
sits flush at the scroll-container top and supplies its own top spacing
|
|
101
|
+
(padding-block below). */
|
|
102
|
+
:scope[sticky-header] { padding-top: 0; }
|
|
56
103
|
:scope[sticky-header] > :where(header, header-ui) {
|
|
57
104
|
position: sticky;
|
|
58
105
|
top: 0;
|
|
59
106
|
z-index: 1;
|
|
60
107
|
background: var(--page-sticky-bg, var(--page-sticky-bg-default));
|
|
108
|
+
/* Bleed horizontally over the page's inline [padding] so the opaque band
|
|
109
|
+
spans the full scroll width, then re-inset the content so it stays
|
|
110
|
+
aligned with the body. Vertical = its own breathing room. Card-ui
|
|
111
|
+
header pattern. (No negative margin-top — that breaks position:sticky.) */
|
|
112
|
+
margin-inline: calc(-1 * var(--page-pad, 0));
|
|
113
|
+
padding: var(--a-space-3) var(--page-pad, 0);
|
|
61
114
|
transition: border-color var(--a-duration-fast) var(--a-easing), box-shadow var(--a-duration-fast) var(--a-easing);
|
|
62
115
|
}
|
|
63
116
|
|
|
@@ -37,7 +37,7 @@ export class UIPagination extends UIElement {
|
|
|
37
37
|
size: { type: String, default: 'md', reflect: true },
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
// Phosphor icons stamped by this primitive (prev/next
|
|
40
|
+
// Phosphor icons stamped by this primitive (prev/next carets inside
|
|
41
41
|
// the nested <button-ui>). Audited by check-required-icons.mjs.
|
|
42
42
|
static requiredIcons = ['caret-left', 'caret-right'];
|
|
43
43
|
|
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
:scope {
|
|
24
24
|
/* ── Base ── */
|
|
25
25
|
box-sizing: border-box;
|
|
26
|
-
display:
|
|
26
|
+
display: flex;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/* Display convention (ADR-0037): block-level by default; [inline] opts back to inline-level. */
|
|
30
|
+
:scope[inline] { display: inline-flex; }
|
|
31
|
+
|
|
29
32
|
/* ── Nav container ── */
|
|
30
33
|
[slot="nav"] {
|
|
31
34
|
display: flex;
|
|
@@ -66,5 +69,10 @@
|
|
|
66
69
|
aspect-ratio 1 here for the bordered-cell look). */
|
|
67
70
|
:scope[variant="button"] [slot="nav"] button-ui {
|
|
68
71
|
aspect-ratio: 1;
|
|
72
|
+
/* 1:1 cells inherit button-ui's --a-radius-md (~13px), which on a square
|
|
73
|
+
cell is ~37% of the side → a circle, not the "square 1:1 bordered button"
|
|
74
|
+
the variant promises. Tighten to -sm so the bordered cells read as rounded
|
|
75
|
+
squares. Same square+large-radius trap as otp-input (bug-37 / bug-39). */
|
|
76
|
+
--button-radius: var(--a-radius-sm);
|
|
69
77
|
}
|
|
70
78
|
}
|
|
@@ -96,11 +96,17 @@
|
|
|
96
96
|
transition: background var(--pipeline-status-duration, var(--pipeline-status-duration-default)) var(--pipeline-status-easing, var(--pipeline-status-easing-default));
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/* State colors: the -active (accent) / -complete (success) dot-bg tokens were
|
|
100
|
+
defined but never applied — these rules only set animation, so every dot
|
|
101
|
+
stayed the default gray (--a-border). Now active dots read accent + pulse,
|
|
102
|
+
complete dots read success. (bug-40 — "use color more") */
|
|
99
103
|
[data-pipeline-dot="active"] {
|
|
104
|
+
background: var(--pipeline-status-dot-bg-active, var(--pipeline-status-dot-bg-active-default));
|
|
100
105
|
animation: pipeline-pulse var(--pipeline-status-pulse-duration, var(--pipeline-status-pulse-duration-default)) var(--pipeline-status-pulse-easing, var(--pipeline-status-pulse-easing-default)) infinite;
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
[data-pipeline-dot="complete"] {
|
|
109
|
+
background: var(--pipeline-status-dot-bg-complete, var(--pipeline-status-dot-bg-complete-default));
|
|
104
110
|
animation: none;
|
|
105
111
|
}
|
|
106
112
|
|
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
|
|
15
15
|
:scope {
|
|
16
16
|
box-sizing: border-box;
|
|
17
|
-
display:
|
|
17
|
+
display: flex;
|
|
18
18
|
position: relative;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/* Display convention (ADR-0037): block-level by default; [inline] opts back to inline-level. */
|
|
22
|
+
:scope[inline] { display: inline-flex; }
|
|
23
|
+
|
|
21
24
|
[slot="trigger"] {
|
|
22
25
|
display: inline-flex;
|
|
23
26
|
}
|
|
@@ -81,6 +84,14 @@
|
|
|
81
84
|
box-shadow: var(--popover-shadow, var(--popover-shadow-default));
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/* Prose margin reset on the content's edge children — a slotted <p> / <h*>
|
|
88
|
+
carries the UA stylesheet's margin-block (~1em), which lands inside the
|
|
89
|
+
panel padding and adds asymmetric top/bottom space (a single-line <p> looked
|
|
90
|
+
like it had a stray bottom margin). Zero the leading/trailing margins so the
|
|
91
|
+
panel padding alone frames the content. (bug — Popover single-line space) */
|
|
92
|
+
[slot="content"] > :first-child { margin-block-start: 0; }
|
|
93
|
+
[slot="content"] > :last-child { margin-block-end: 0; }
|
|
94
|
+
|
|
84
95
|
[slot="content"]:popover-open {
|
|
85
96
|
@starting-style {
|
|
86
97
|
opacity: 0;
|
|
@@ -39,16 +39,42 @@
|
|
|
39
39
|
by the frame's overflow:hidden. Most demos fit (the component's overflow
|
|
40
40
|
check stacks split→full-width first), so the scrollbar only appears when a
|
|
41
41
|
demo is genuinely wider than the docs column. */
|
|
42
|
+
/* Column flow is the default: each example stacks in DOM order and fills the
|
|
43
|
+
stage width (align-items: stretch), so block-level demos (progress / bars /
|
|
44
|
+
accordion / cards / pages …) read at full width without a per-component
|
|
45
|
+
list. Text-flow atoms (button / badge / tag / …) opt back to content-width
|
|
46
|
+
via align-self below. (bug-46 — replaces the old flex-row + :only-child
|
|
47
|
+
block-flow hack, which only filled single-child demos.) */
|
|
42
48
|
[data-preview-render] {
|
|
43
49
|
display: flex;
|
|
44
|
-
flex-
|
|
45
|
-
align-items:
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
align-items: stretch;
|
|
46
52
|
gap: var(--preview-render-gap, var(--preview-render-gap-default));
|
|
47
53
|
padding: var(--preview-render-pad, var(--preview-render-pad-default));
|
|
48
54
|
background: var(--preview-render-bg, var(--preview-render-bg-default));
|
|
49
55
|
overflow-x: auto;
|
|
50
56
|
}
|
|
51
|
-
|
|
57
|
+
/* Lone text-flow atoms shouldn't span the whole stage — keep them
|
|
58
|
+
content-width + leading-aligned (the ADR-0037 inline-display set). */
|
|
59
|
+
[data-preview-render] > :is(button-ui, badge-ui, tag-ui, chip-ui, kbd-ui, icon-ui, swatch-ui, spinner-ui, switch-ui, avatar-ui) {
|
|
60
|
+
align-self: start;
|
|
61
|
+
}
|
|
62
|
+
/* Form-field demos collapse to content-width as flex items in the render
|
|
63
|
+
cell — a placeholder-only combobox shrank to a ~52px "Sel…" stub — so
|
|
64
|
+
stretch form controls to the cell width with width:100%.
|
|
65
|
+
• <field-ui> is a full-width form ROW: let it fill the whole stage,
|
|
66
|
+
NO measure cap. The prior 28rem cap read as "not using available
|
|
67
|
+
space" on wide single-block demos (bug-55 follow-up).
|
|
68
|
+
• Bare atomic controls (input/select/…) keep the 28rem measure cap so
|
|
69
|
+
a lone control doesn't sprawl into an unrealistic bar on a wide stage.
|
|
70
|
+
Non-form demos (buttons, badges, etc.) are untouched — content-width. */
|
|
71
|
+
[data-preview-render] field-ui {
|
|
72
|
+
width: 100%;
|
|
73
|
+
}
|
|
74
|
+
[data-preview-render] > :is(input-ui, select-ui, combobox-ui, textarea-ui, tags-input-ui, color-input-ui) {
|
|
75
|
+
width: 100%;
|
|
76
|
+
max-width: 28rem;
|
|
77
|
+
}
|
|
52
78
|
/* Code pane — divider line between panes; flatten the nested code-ui
|
|
53
79
|
chrome so the preview frame owns the outer border + radius. */
|
|
54
80
|
[data-preview-code] {
|
|
@@ -151,6 +177,7 @@
|
|
|
151
177
|
color: var(--a-fg-muted);
|
|
152
178
|
background: var(--a-bg-muted);
|
|
153
179
|
padding: 0.125rem 0.375rem;
|
|
180
|
+
margin-bottom: 0.5rem;
|
|
154
181
|
border-radius: var(--a-radius-sm);
|
|
155
182
|
}
|
|
156
183
|
/* Narrow: collapse each row to stacked render-over-code. */
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
@scope (progress-row-ui) {
|
|
2
2
|
:where(:scope) {
|
|
3
3
|
/* ── Tokens ── */
|
|
4
|
-
|
|
4
|
+
/* Row-gap between the label/meta row and the bar. Bumped --a-space-1 →
|
|
5
|
+
--a-space-2 so the bar isn't crammed under the label. (bug-42) */
|
|
6
|
+
--progress-row-gap-default: var(--a-space-2);
|
|
5
7
|
--progress-row-column-gap-default: var(--a-space-2);
|
|
6
8
|
--progress-row-label-size-default: var(--a-ui-size);
|
|
7
9
|
--progress-row-label-fg-default: var(--a-fg);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
:scope {
|
|
12
12
|
box-sizing: border-box;
|
|
13
|
-
display:
|
|
13
|
+
display: block;
|
|
14
14
|
/* The SVG itself carries explicit width/height; this is a fallback
|
|
15
15
|
for cases where the SVG hasn't stamped yet (empty value/matrix). */
|
|
16
16
|
line-height: 0;
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
background: var(--qr-code-bg, var(--qr-code-bg-default));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/* Display convention (ADR-0037): block-level by default; [inline] opts back to inline-level. */
|
|
22
|
+
:scope[inline] { display: inline-block; }
|
|
23
|
+
|
|
21
24
|
:scope svg {
|
|
22
25
|
display: block;
|
|
23
26
|
/* Width / height come from the explicit SVG attributes set by JS
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
:scope {
|
|
29
29
|
/* ── Base ── */
|
|
30
30
|
box-sizing: border-box;
|
|
31
|
-
display:
|
|
31
|
+
display: grid;
|
|
32
32
|
grid-auto-flow: column;
|
|
33
33
|
grid-auto-columns: 1fr;
|
|
34
34
|
align-items: stretch;
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
border-radius: var(--segmented-radius, var(--segmented-radius-default));
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/* Display convention (ADR-0037): block-level by default; [inline] opts back to inline-level. */
|
|
59
|
+
:scope[inline] { display: inline-grid; }
|
|
60
|
+
|
|
58
61
|
/* -- Indicator (hidden until first selection) -- */
|
|
59
62
|
:scope > [data-indicator] {
|
|
60
63
|
display: none;
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"default": false
|
|
108
108
|
},
|
|
109
109
|
"options": {
|
|
110
|
-
"description": "Option list. Array of {value, label, disabled?} or grouped {label, options: [...]}. Alternative to declarative <option> / <optgroup> children.",
|
|
110
|
+
"description": "Option list. Array of {value, label, disabled?, icon?, avatar?} or grouped {label, options: [...]}. Alternative to declarative <option> / <optgroup> children. Per-option icon/avatar render in the list AND reflect in the trigger's selected state.",
|
|
111
111
|
"type": "array",
|
|
112
112
|
"default": []
|
|
113
113
|
},
|
|
@@ -71,6 +71,7 @@ export class UISelect extends UIFormElement {
|
|
|
71
71
|
static template = () => null;
|
|
72
72
|
|
|
73
73
|
#options = [];
|
|
74
|
+
#ownTrigger = false; // true when WE stamped the trigger (vs a consumer-custom one)
|
|
74
75
|
#listbox = null;
|
|
75
76
|
#anchorCleanup = null;
|
|
76
77
|
#query = '';
|
|
@@ -386,14 +387,18 @@ export class UISelect extends UIFormElement {
|
|
|
386
387
|
|
|
387
388
|
// Stamp default trigger if none provided
|
|
388
389
|
if (!this.querySelector('[slot="trigger"]')) {
|
|
390
|
+
this.#ownTrigger = true;
|
|
389
391
|
// Detach listbox before innerHTML wipe so it isn't destroyed
|
|
390
392
|
const lb = this.#listbox;
|
|
391
393
|
if (lb?.parentNode === this) this.removeChild(lb);
|
|
392
394
|
|
|
395
|
+
// Initial leading reflects the host [avatar]/[icon]; #syncLeading() then
|
|
396
|
+
// reconciles it to the SELECTED option's icon/avatar on every render. The
|
|
397
|
+
// `data-select-leading` marker scopes that reconciliation to our element.
|
|
393
398
|
const leading = this.avatar
|
|
394
|
-
? `<img slot="leading" src="${this.avatar}" alt="" />`
|
|
399
|
+
? `<img slot="leading" data-select-leading src="${escapeHTML(this.avatar)}" alt="" />`
|
|
395
400
|
: this.icon
|
|
396
|
-
? `<icon-ui slot="leading" name="${this.icon}"></icon-ui>`
|
|
401
|
+
? `<icon-ui slot="leading" data-select-leading name="${escapeHTML(this.icon)}"></icon-ui>`
|
|
397
402
|
: '';
|
|
398
403
|
const displayMarkup = this.searchable
|
|
399
404
|
? `<input slot="display" type="text" role="combobox" aria-autocomplete="list" autocomplete="off" placeholder="${escapeHTML(this.placeholder || '')}" value="${escapeHTML(this.#displayText() === this.placeholder ? '' : this.#displayText())}" />`
|
|
@@ -470,6 +475,9 @@ export class UISelect extends UIFormElement {
|
|
|
470
475
|
}
|
|
471
476
|
}
|
|
472
477
|
|
|
478
|
+
// Reflect the selected option's icon/avatar in the trigger leading.
|
|
479
|
+
this.#syncLeading();
|
|
480
|
+
|
|
473
481
|
// SPEC-040 — stamp / reconcile chips + "+N more" pill on every render.
|
|
474
482
|
if (this.multiple) this.#stampChips();
|
|
475
483
|
// Show clear-all only in multi-select mode when [clearable] + chips present.
|
|
@@ -548,12 +556,12 @@ export class UISelect extends UIFormElement {
|
|
|
548
556
|
if (child.tagName === 'OPTGROUP') {
|
|
549
557
|
const group = { label: child.label || child.getAttribute('label') || '', options: [] };
|
|
550
558
|
for (const opt of child.querySelectorAll('option')) {
|
|
551
|
-
group.options.push({ value: opt.value, label: opt.textContent.trim(), disabled: opt.disabled });
|
|
559
|
+
group.options.push({ value: opt.value, label: opt.textContent.trim(), disabled: opt.disabled, icon: opt.getAttribute('icon') || '', avatar: opt.getAttribute('avatar') || '' });
|
|
552
560
|
if (opt.hasAttribute('selected')) preSelectedArr.push(opt.value);
|
|
553
561
|
}
|
|
554
562
|
this.#options.push(group);
|
|
555
563
|
} else if (child.tagName === 'OPTION') {
|
|
556
|
-
this.#options.push({ value: child.value, label: child.textContent.trim(), disabled: child.disabled });
|
|
564
|
+
this.#options.push({ value: child.value, label: child.textContent.trim(), disabled: child.disabled, icon: child.getAttribute('icon') || '', avatar: child.getAttribute('avatar') || '' });
|
|
557
565
|
if (child.hasAttribute('selected')) preSelectedArr.push(child.value);
|
|
558
566
|
} else if (
|
|
559
567
|
// §225: skip [slot="display"] / [slot="listbox"] / [slot="action"] etc. — these are
|
|
@@ -614,6 +622,52 @@ export class UISelect extends UIFormElement {
|
|
|
614
622
|
|
|
615
623
|
get options() { return this.#options; }
|
|
616
624
|
|
|
625
|
+
// Per-option leading markup: avatar (img) beats icon (icon-ui). Shared by
|
|
626
|
+
// the listbox rows AND the trigger (resolved against the selected option).
|
|
627
|
+
static #optionLeadHTML(opt) {
|
|
628
|
+
if (!opt) return '';
|
|
629
|
+
if (opt.avatar) return `<img data-option-avatar src="${escapeHTML(opt.avatar)}" alt="" />`;
|
|
630
|
+
if (opt.icon) return `<icon-ui name="${escapeHTML(opt.icon)}"></icon-ui>`;
|
|
631
|
+
return '';
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Reflect the SELECTED option's icon/avatar in the trigger's leading slot.
|
|
636
|
+
* Single-select only (multi-select shows chips). Falls back to the host
|
|
637
|
+
* [avatar]/[icon] when the selected option carries neither. Only manages the
|
|
638
|
+
* leading WE stamped (`[data-select-leading]`) — a consumer-custom trigger
|
|
639
|
+
* owns its own leading.
|
|
640
|
+
*/
|
|
641
|
+
#syncLeading() {
|
|
642
|
+
if (this.multiple || !this.#ownTrigger) return;
|
|
643
|
+
const trigger = this.querySelector('[slot="trigger"]');
|
|
644
|
+
if (!trigger) return;
|
|
645
|
+
const flat = this.#options.flatMap((o) => o.options || [o]);
|
|
646
|
+
const sel = flat.find((o) => !o.header && !o.separator && o.value === this.value);
|
|
647
|
+
const avatar = (sel && sel.avatar) || this.avatar || '';
|
|
648
|
+
const icon = (sel && sel.icon) || this.icon || '';
|
|
649
|
+
const existing = trigger.querySelector(':scope > [data-select-leading]');
|
|
650
|
+
let html = '';
|
|
651
|
+
if (avatar) html = `<img slot="leading" data-select-leading src="${escapeHTML(avatar)}" alt="" />`;
|
|
652
|
+
else if (icon) html = `<icon-ui slot="leading" data-select-leading name="${escapeHTML(icon)}"></icon-ui>`;
|
|
653
|
+
if (!html) { existing?.remove(); return; }
|
|
654
|
+
const tmp = document.createElement('template');
|
|
655
|
+
tmp.innerHTML = html;
|
|
656
|
+
const next = tmp.content.firstElementChild;
|
|
657
|
+
if (!existing) {
|
|
658
|
+
trigger.insertBefore(next, trigger.firstChild);
|
|
659
|
+
} else if (existing.tagName === next.tagName) {
|
|
660
|
+
// Same element type — update the changing attribute in place.
|
|
661
|
+
if (next.tagName === 'IMG') {
|
|
662
|
+
if (existing.getAttribute('src') !== next.getAttribute('src')) existing.setAttribute('src', next.getAttribute('src'));
|
|
663
|
+
} else if (existing.getAttribute('name') !== next.getAttribute('name')) {
|
|
664
|
+
existing.setAttribute('name', next.getAttribute('name'));
|
|
665
|
+
}
|
|
666
|
+
} else {
|
|
667
|
+
existing.replaceWith(next); // icon ↔ avatar switch
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
617
671
|
#renderOptions() {
|
|
618
672
|
if (!this.#listbox) return;
|
|
619
673
|
this.#listbox.innerHTML = '';
|
|
@@ -653,6 +707,8 @@ export class UISelect extends UIFormElement {
|
|
|
653
707
|
// SPEC-040 — multi-select option rows render a leading checkbox
|
|
654
708
|
// indicator (CSS-driven via [data-multi-option]); the `check` icon
|
|
655
709
|
// shows when aria-selected="true".
|
|
710
|
+
// Per-option leading glyph — avatar (img) wins over icon (icon-ui).
|
|
711
|
+
const lead = UISelect.#optionLeadHTML(opt);
|
|
656
712
|
if (this.multiple) {
|
|
657
713
|
el.setAttribute('data-multi-option', '');
|
|
658
714
|
const box = document.createElement('span');
|
|
@@ -661,11 +717,11 @@ export class UISelect extends UIFormElement {
|
|
|
661
717
|
el.appendChild(box);
|
|
662
718
|
const label = document.createElement('span');
|
|
663
719
|
label.setAttribute('data-option-label', '');
|
|
664
|
-
if (
|
|
720
|
+
if (lead) label.innerHTML = `${lead}${escapeHTML(opt.label)}`;
|
|
665
721
|
else label.textContent = opt.label;
|
|
666
722
|
el.appendChild(label);
|
|
667
|
-
} else if (
|
|
668
|
-
el.innerHTML =
|
|
723
|
+
} else if (lead) {
|
|
724
|
+
el.innerHTML = `${lead}${escapeHTML(opt.label)}`;
|
|
669
725
|
} else {
|
|
670
726
|
el.textContent = opt.label;
|
|
671
727
|
}
|
|
@@ -191,6 +191,11 @@
|
|
|
191
191
|
:scope[data-multi-chips] [slot="trigger"] {
|
|
192
192
|
flex-wrap: wrap;
|
|
193
193
|
align-items: center;
|
|
194
|
+
/* Pack chips left with a small gap — override the base trigger's
|
|
195
|
+
`space-between` (which is for the single-select content↔caret split),
|
|
196
|
+
otherwise the chips spread across the full trigger width. The display
|
|
197
|
+
slot flex-grows (below) to push the caret to the trailing edge. */
|
|
198
|
+
justify-content: flex-start;
|
|
194
199
|
gap: var(--a-space-1);
|
|
195
200
|
/* min-height tracks single-row height when empty; flex-wrap allows
|
|
196
201
|
it to grow as chips overflow. */
|
|
@@ -290,6 +295,8 @@ select-ui [slot="listbox"]:popover-open {
|
|
|
290
295
|
}
|
|
291
296
|
|
|
292
297
|
select-ui [role="option"] {
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
293
300
|
padding: var(--a-space-1) var(--a-ui-px);
|
|
294
301
|
border-radius: var(--a-radius-sm);
|
|
295
302
|
white-space: nowrap;
|
|
@@ -325,6 +332,17 @@ select-ui [role="option"] icon-ui {
|
|
|
325
332
|
vertical-align: -0.125em;
|
|
326
333
|
}
|
|
327
334
|
|
|
335
|
+
/* Option with avatar — small inline image (matches the trigger leading
|
|
336
|
+
avatar radius), sized to the option line-height. */
|
|
337
|
+
select-ui [role="option"] img[data-option-avatar] {
|
|
338
|
+
width: var(--a-ui-size);
|
|
339
|
+
height: var(--a-ui-size);
|
|
340
|
+
border-radius: var(--select-leading-radius, var(--select-leading-radius-default));
|
|
341
|
+
object-fit: cover;
|
|
342
|
+
margin-inline-end: var(--a-space-1);
|
|
343
|
+
vertical-align: -0.2em;
|
|
344
|
+
}
|
|
345
|
+
|
|
328
346
|
/* Separator */
|
|
329
347
|
select-ui [data-separator] {
|
|
330
348
|
height: 1px;
|
|
@@ -23,7 +23,7 @@ description: |
|
|
|
23
23
|
# Per ADR-0027 — primitives that programmatically create other primitives
|
|
24
24
|
# do NOT auto-import them. Consumer (or demo shell) must explicitly import.
|
|
25
25
|
composes:
|
|
26
|
-
- icon-ui #
|
|
26
|
+
- icon-ui # caret + option-row affixes (created in render)
|
|
27
27
|
- tag-ui # multi-select chip per selected option in the trigger
|
|
28
28
|
props:
|
|
29
29
|
name:
|
|
@@ -151,7 +151,7 @@ props:
|
|
|
151
151
|
default: false
|
|
152
152
|
reflect: true
|
|
153
153
|
options:
|
|
154
|
-
description: "Option list. Array of {value, label, disabled?} or grouped {label, options: [...]}. Alternative to declarative <option> / <optgroup> children."
|
|
154
|
+
description: "Option list. Array of {value, label, disabled?, icon?, avatar?} or grouped {label, options: [...]}. Alternative to declarative <option> / <optgroup> children. Per-option icon/avatar render in the list AND reflect in the trigger's selected state."
|
|
155
155
|
type: array
|
|
156
156
|
default: []
|
|
157
157
|
pattern:
|
|
@@ -249,6 +249,13 @@ a2ui:
|
|
|
249
249
|
tag names are silently ignored (per §225 v0.5.9) and warned once
|
|
250
250
|
at runtime. Or set `.options` programmatically as an array of
|
|
251
251
|
`{value, label, disabled?}` (grouped form: `{label, options:[…]}`).
|
|
252
|
+
- >-
|
|
253
|
+
Per-option visuals: give each <option> an `icon` (Phosphor name) or
|
|
254
|
+
`avatar` (image URL) — `<option value="light" icon="sun">`. Each row
|
|
255
|
+
renders its glyph in the list AND the trigger reflects the SELECTED
|
|
256
|
+
option's icon/avatar (theme pickers, assignee/account switchers).
|
|
257
|
+
`avatar` wins over `icon`; a host-level [icon]/[avatar] is the
|
|
258
|
+
fallback when the selected option carries neither.
|
|
252
259
|
- >-
|
|
253
260
|
For dynamic option lists rendered inside <editor-shell>, set the
|
|
254
261
|
JSON via the [data-options] attribute — <editor-shell>'s
|
|
@@ -14,8 +14,19 @@
|
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
16
|
"align": {
|
|
17
|
-
"description": "Alignment of
|
|
17
|
+
"description": "Alignment of the layered children within the shared cell (maps to grid place-items). `center` (default) plus eight directional keywords.",
|
|
18
18
|
"type": "string",
|
|
19
|
+
"enum": [
|
|
20
|
+
"center",
|
|
21
|
+
"top-left",
|
|
22
|
+
"top",
|
|
23
|
+
"top-right",
|
|
24
|
+
"left",
|
|
25
|
+
"right",
|
|
26
|
+
"bottom-left",
|
|
27
|
+
"bottom",
|
|
28
|
+
"bottom-right"
|
|
29
|
+
],
|
|
19
30
|
"default": "center"
|
|
20
31
|
},
|
|
21
32
|
"component": {
|
|
@@ -21,6 +21,6 @@ cell.
|
|
|
21
21
|
import { UIElement } from '../../core/element.js';
|
|
22
22
|
|
|
23
23
|
export class UIStack extends UIElement {
|
|
24
|
-
/** Alignment of
|
|
25
|
-
align:
|
|
24
|
+
/** Alignment of the layered children within the shared cell (maps to grid place-items). `center` (default) plus eight directional keywords. */
|
|
25
|
+
align: 'center' | 'top-left' | 'top' | 'top-right' | 'left' | 'right' | 'bottom-left' | 'bottom' | 'bottom-right';
|
|
26
26
|
}
|
|
@@ -18,9 +18,21 @@ description: |
|
|
|
18
18
|
cell.
|
|
19
19
|
props:
|
|
20
20
|
align:
|
|
21
|
-
description:
|
|
21
|
+
description: >-
|
|
22
|
+
Alignment of the layered children within the shared cell (maps to
|
|
23
|
+
grid place-items). `center` (default) plus eight directional keywords.
|
|
22
24
|
type: string
|
|
23
25
|
default: center
|
|
26
|
+
enum:
|
|
27
|
+
- center
|
|
28
|
+
- top-left
|
|
29
|
+
- top
|
|
30
|
+
- top-right
|
|
31
|
+
- left
|
|
32
|
+
- right
|
|
33
|
+
- bottom-left
|
|
34
|
+
- bottom
|
|
35
|
+
- bottom-right
|
|
24
36
|
events: {}
|
|
25
37
|
slots:
|
|
26
38
|
default:
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
|
+
"bleed": {
|
|
17
|
+
"description": "Horizontal-bleed KPI layout. With a `slot=\"chart\"` child, the value / label / change stack on the left while the chart fills the right column at full height and bleeds to the card's top / right / bottom edges (the horizontal counterpart to a chart in a card `<section bleed>`). No effect without a chart slot.",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false
|
|
20
|
+
},
|
|
16
21
|
"change": {
|
|
17
22
|
"description": "Change indicator text (e.g. '+12%', '-3%')",
|
|
18
23
|
"type": "string",
|
package/components/stat/stat.css
CHANGED
|
@@ -72,6 +72,61 @@
|
|
|
72
72
|
grid-column: 1;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/* ── Horizontal-bleed KPI tile ──
|
|
76
|
+
value / label / change stack on the LEFT; the chart fills the RIGHT
|
|
77
|
+
column at full height and bleeds to the card's top / right / bottom
|
|
78
|
+
edges (cancels the card-section inset via negative margins keyed to
|
|
79
|
+
the inherited --card-inset). The compact `slot="chart"` reflow above
|
|
80
|
+
keeps the chart resting on the value baseline; this opt-in spreads it
|
|
81
|
+
into a full-height trajectory panel. Compose inside a card section:
|
|
82
|
+
<card-ui><section>
|
|
83
|
+
<stat-ui bleed value=… label=… change=… trend=…>
|
|
84
|
+
<chart-ui slot="chart" type="area" …></chart-ui>
|
|
85
|
+
</stat-ui>
|
|
86
|
+
</section></card-ui> */
|
|
87
|
+
:scope[bleed]:has([slot="chart"]) {
|
|
88
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, var(--stat-bleed-col, 46%));
|
|
89
|
+
grid-template-areas:
|
|
90
|
+
"label chart"
|
|
91
|
+
"value chart"
|
|
92
|
+
"change chart";
|
|
93
|
+
align-items: start;
|
|
94
|
+
}
|
|
95
|
+
:scope[bleed] [slot="value"],
|
|
96
|
+
:scope[bleed] [slot="change"] {
|
|
97
|
+
grid-column: 1;
|
|
98
|
+
}
|
|
99
|
+
/* Icon shares the label row, pinned to the text column's inner edge. */
|
|
100
|
+
:scope[bleed] [slot="label"] {
|
|
101
|
+
padding-inline-end: var(--a-space-5);
|
|
102
|
+
}
|
|
103
|
+
:scope[bleed] [slot="icon"] {
|
|
104
|
+
grid-area: label;
|
|
105
|
+
justify-self: end;
|
|
106
|
+
align-self: start;
|
|
107
|
+
}
|
|
108
|
+
:scope[bleed] [slot="chart"] {
|
|
109
|
+
grid-area: chart;
|
|
110
|
+
/* VERTICAL bleed via stretch: a stretched grid item's height is
|
|
111
|
+
`track − margins`, so a negative margin-block overflows the track by
|
|
112
|
+
that amount top + bottom. `height:auto` overrides the base
|
|
113
|
+
chart-ui[slot="chart"]{height:100%} so the stretch governs. */
|
|
114
|
+
align-self: stretch;
|
|
115
|
+
height: auto;
|
|
116
|
+
margin-block: calc(-1 * var(--card-inset, var(--card-inset-default, 0px)));
|
|
117
|
+
/* HORIZONTAL bleed via explicit width: chart-ui pins its own
|
|
118
|
+
`width:100%` (chart.css), which beats justify-self:stretch and would
|
|
119
|
+
freeze the box to the track — so a negative margin-inline-end can't
|
|
120
|
+
widen it. Instead size the box to `track + inset` and left-anchor it,
|
|
121
|
+
so the right edge overflows to the card edge while the left keeps the
|
|
122
|
+
column gap. The percentage resolves against the grid track. */
|
|
123
|
+
justify-self: start;
|
|
124
|
+
width: calc(100% + var(--card-inset, var(--card-inset-default, 0px)));
|
|
125
|
+
min-width: 0;
|
|
126
|
+
/* override the inline-chart 4:3 — fill the row span instead */
|
|
127
|
+
aspect-ratio: auto;
|
|
128
|
+
}
|
|
129
|
+
|
|
75
130
|
/* ── Label (eyebrow) ── */
|
|
76
131
|
[slot="label"] {
|
|
77
132
|
grid-area: label;
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
import { UIElement } from '../../core/element.js';
|
|
14
14
|
|
|
15
15
|
export class UIStat extends UIElement {
|
|
16
|
+
/** Horizontal-bleed KPI layout. With a `slot="chart"` child, the value / label / change stack on the left while the chart fills the right column at full height and bleeds to the card's top / right / bottom edges (the horizontal counterpart to a chart in a card `<section bleed>`). No effect without a chart slot. */
|
|
17
|
+
bleed: boolean;
|
|
16
18
|
/** Change indicator text (e.g. '+12%', '-3%') */
|
|
17
19
|
change: string;
|
|
18
20
|
/** Icon name displayed in the icon slot */
|