@adia-ai/web-components 0.6.50 → 0.7.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 +134 -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.class.js +38 -0
- package/components/input/input.css +9 -5
- package/components/input/input.test.js +57 -0
- 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.class.js +12 -3
- package/components/menu/menu.css +4 -1
- package/components/menu/menu.test.js +130 -0
- 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/components/tree/tree.class.js +24 -4
- package/components/tree/tree.test.js +108 -0
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +83 -83
- 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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <menu-ui> behavioral tests.
|
|
3
|
+
*
|
|
4
|
+
* FEEDBACK-92 (2026-05-31): `#show()` collected items with a direct-child
|
|
5
|
+
* `:scope >` query, so items rendered through the template engine's
|
|
6
|
+
* `.map()` / `repeat()` (each wrapped in a `display:contents` <span>) were
|
|
7
|
+
* skipped → the popover opened empty. The fix uses a descendant query
|
|
8
|
+
* (mirroring `#hide()`). These tests pin the dynamic + static paths.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
|
|
12
|
+
import { html, stamp, repeat } from '../../core/template.js';
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
await import('../../core/element.js');
|
|
16
|
+
await import('./menu.js');
|
|
17
|
+
await import('../button/button.js');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const settle = () => new Promise((r) => setTimeout(r, 30));
|
|
21
|
+
|
|
22
|
+
describe('<menu-ui> collects dynamically-rendered items (FEEDBACK-92)', () => {
|
|
23
|
+
let host;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
host = document.createElement('div');
|
|
27
|
+
document.body.appendChild(host);
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => host.remove());
|
|
30
|
+
|
|
31
|
+
function popItems(menu) {
|
|
32
|
+
const pop = menu.querySelector('[data-menu-popover]');
|
|
33
|
+
return pop ? pop.querySelectorAll('menu-item-ui') : [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
it('populates the popover from `.map()`-rendered items (the bug)', async () => {
|
|
37
|
+
const ITEMS = [{ id: 'a', text: 'Alpha' }, { id: 'b', text: 'Beta' }];
|
|
38
|
+
// Render the menu (closed) the documented way — items via `.map()`. The
|
|
39
|
+
// consumer opens it later via interaction, after the template has rendered.
|
|
40
|
+
stamp(html`
|
|
41
|
+
<menu-ui>
|
|
42
|
+
<button-ui slot="trigger" text="Open"></button-ui>
|
|
43
|
+
${ITEMS.map((i) => html`<menu-item-ui .text=${i.text} .value=${i.id}></menu-item-ui>`)}
|
|
44
|
+
</menu-ui>
|
|
45
|
+
`, host);
|
|
46
|
+
await settle();
|
|
47
|
+
|
|
48
|
+
const menu = host.querySelector('menu-ui');
|
|
49
|
+
// The exact bug condition: items exist under the host but are NOT direct
|
|
50
|
+
// children — the template engine nested them inside display:contents spans,
|
|
51
|
+
// which the old `:scope >` query in #show() could not see.
|
|
52
|
+
expect(menu.querySelectorAll('menu-item-ui').length).toBe(2);
|
|
53
|
+
expect(menu.querySelector(':scope > menu-item-ui')).toBeNull();
|
|
54
|
+
|
|
55
|
+
menu.open = true; // user opens the menu post-render
|
|
56
|
+
await settle();
|
|
57
|
+
|
|
58
|
+
const items = popItems(menu);
|
|
59
|
+
expect(items.length).toBe(2);
|
|
60
|
+
expect([...items].map((el) => el.value)).toEqual(['a', 'b']);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('populates the popover from `repeat()`-rendered items', async () => {
|
|
64
|
+
const ITEMS = [{ id: 'x', text: 'Ex' }, { id: 'y', text: 'Why' }, { id: 'z', text: 'Zee' }];
|
|
65
|
+
stamp(html`
|
|
66
|
+
<menu-ui>
|
|
67
|
+
<button-ui slot="trigger" text="Open"></button-ui>
|
|
68
|
+
${repeat(ITEMS, (i) => i.id, (i) => html`<menu-item-ui .text=${i.text} .value=${i.id}></menu-item-ui>`)}
|
|
69
|
+
</menu-ui>
|
|
70
|
+
`, host);
|
|
71
|
+
await settle();
|
|
72
|
+
|
|
73
|
+
const menu = host.querySelector('menu-ui');
|
|
74
|
+
expect(menu.querySelector(':scope > menu-item-ui')).toBeNull(); // behind wrappers
|
|
75
|
+
menu.open = true;
|
|
76
|
+
await settle();
|
|
77
|
+
expect(popItems(menu).length).toBe(3);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('still populates from static literal children (no regression)', async () => {
|
|
81
|
+
const menu = document.createElement('menu-ui');
|
|
82
|
+
menu.innerHTML = `
|
|
83
|
+
<button-ui slot="trigger" text="Open"></button-ui>
|
|
84
|
+
<menu-item-ui text="Edit" value="edit"></menu-item-ui>
|
|
85
|
+
<menu-divider-ui></menu-divider-ui>
|
|
86
|
+
<menu-item-ui text="Delete" value="delete"></menu-item-ui>`;
|
|
87
|
+
host.appendChild(menu);
|
|
88
|
+
await settle();
|
|
89
|
+
menu.open = true;
|
|
90
|
+
await settle();
|
|
91
|
+
|
|
92
|
+
expect(popItems(menu).length).toBe(2);
|
|
93
|
+
const pop = menu.querySelector('[data-menu-popover]');
|
|
94
|
+
expect(pop.querySelectorAll('menu-divider-ui').length).toBe(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('does not absorb the trigger into the popover', async () => {
|
|
98
|
+
const menu = document.createElement('menu-ui');
|
|
99
|
+
menu.innerHTML = `
|
|
100
|
+
<button-ui slot="trigger" text="Open"></button-ui>
|
|
101
|
+
<menu-item-ui text="Edit" value="edit"></menu-item-ui>`;
|
|
102
|
+
host.appendChild(menu);
|
|
103
|
+
await settle();
|
|
104
|
+
menu.open = true;
|
|
105
|
+
await settle();
|
|
106
|
+
|
|
107
|
+
const pop = menu.querySelector('[data-menu-popover]');
|
|
108
|
+
expect(pop.querySelector('[slot="trigger"]')).toBeNull();
|
|
109
|
+
expect(menu.querySelector(':scope > [slot="trigger"]')).not.toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('does not reorder items on a re-entrant #show() (open → re-render → still open)', async () => {
|
|
113
|
+
const menu = document.createElement('menu-ui');
|
|
114
|
+
menu.innerHTML = `
|
|
115
|
+
<button-ui slot="trigger" text="Open"></button-ui>
|
|
116
|
+
<menu-item-ui text="One" value="1"></menu-item-ui>
|
|
117
|
+
<menu-item-ui text="Two" value="2"></menu-item-ui>`;
|
|
118
|
+
host.appendChild(menu);
|
|
119
|
+
await settle();
|
|
120
|
+
menu.open = true;
|
|
121
|
+
await settle();
|
|
122
|
+
// Force another render() pass while still open (re-invokes #show()).
|
|
123
|
+
menu.placement = 'top-start';
|
|
124
|
+
await settle();
|
|
125
|
+
|
|
126
|
+
const pop = menu.querySelector('[data-menu-popover]');
|
|
127
|
+
const order = [...pop.querySelectorAll('menu-item-ui')].map((el) => el.value);
|
|
128
|
+
expect(order).toEqual(['1', '2']);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -122,7 +122,16 @@ export class UIModal extends UIElement {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
#getDuration() {
|
|
125
|
-
|
|
125
|
+
// Read the same fallback chain the CSS transitions use —
|
|
126
|
+
// var(--modal-duration, var(--modal-duration-default)). Reading only the
|
|
127
|
+
// public token (unset unless a consumer overrides) made this always fall
|
|
128
|
+
// back to 200ms while the CSS exit animation ran for --modal-duration-default
|
|
129
|
+
// (= --a-duration = 250ms), so the dialog closed 50ms early, clipping the
|
|
130
|
+
// animation. Same class as swiper bug-29 (the OD-5 -default token sweep left
|
|
131
|
+
// JS read-sites pointed at the now-unset public token).
|
|
132
|
+
const cs = getComputedStyle(this);
|
|
133
|
+
const raw = cs.getPropertyValue('--modal-duration').trim()
|
|
134
|
+
|| cs.getPropertyValue('--modal-duration-default').trim();
|
|
126
135
|
return parseFloat(raw) || 200;
|
|
127
136
|
}
|
|
128
137
|
|
|
@@ -142,6 +142,12 @@
|
|
|
142
142
|
flex: 1;
|
|
143
143
|
overflow: auto;
|
|
144
144
|
}
|
|
145
|
+
/* Form controls in the modal body fill the available width (a modal body is
|
|
146
|
+
a form column, not a content-width row) — matches the field/input family's
|
|
147
|
+
full-width behaviour in a real form. */
|
|
148
|
+
:scope [slot="body"] :is(field-ui, input-ui, select-ui, textarea-ui, combobox-ui, tags-input-ui, color-input-ui, slider-ui) {
|
|
149
|
+
width: 100%;
|
|
150
|
+
}
|
|
145
151
|
|
|
146
152
|
/* ═══════ Footer ═══════ */
|
|
147
153
|
:scope [slot="footer"] {
|
|
@@ -153,4 +159,7 @@
|
|
|
153
159
|
border-top: 1px solid var(--modal-border, var(--modal-border-default));
|
|
154
160
|
flex-shrink: 0;
|
|
155
161
|
}
|
|
162
|
+
/* No content → no footer chrome: an empty footer slot otherwise renders a
|
|
163
|
+
dead band (border-top + padding) under the body. */
|
|
164
|
+
:scope [slot="footer"]:empty { display: none; }
|
|
156
165
|
}
|
|
@@ -114,6 +114,9 @@
|
|
|
114
114
|
"default": {
|
|
115
115
|
"description": "Spillover content revealed only when the card is checked — typically a follow-up form field (e.g. a textarea on an \"Other\" option, conditional inputs that depend on the selection). Aligns with the heading/description column; hidden via `display: none` when not checked."
|
|
116
116
|
},
|
|
117
|
+
"action": {
|
|
118
|
+
"description": "CTA slot for `layout=\"tile\"` — a button-ui anchored to the card's bottom edge and stretched full-width, so a row of tile cards keeps their CTAs aligned regardless of body length (plan-picker-style pickers)."
|
|
119
|
+
},
|
|
117
120
|
"heading": {
|
|
118
121
|
"description": "Rich heading content. Overrides the `heading` attribute when present."
|
|
119
122
|
},
|
|
@@ -173,34 +173,59 @@ option-card-ui[checked] [slot="icon"] {
|
|
|
173
173
|
description below, all left-aligned. Used for hero pickers
|
|
174
174
|
(data source, role, plan tiles) where the icon is a primary
|
|
175
175
|
brand cue rather than secondary chrome. ── */
|
|
176
|
+
/* Tile layout — a vertical content stack (icon → heading → description →
|
|
177
|
+
body → action) with the radio indicator pinned top-right OUT of flow.
|
|
178
|
+
Flex (not grid) so content flows in DOM order with no reserved/empty
|
|
179
|
+
tracks: a no-icon plan card has no dead left column, and a slotted CTA
|
|
180
|
+
(slot="action") anchors to the card bottom regardless of how much body
|
|
181
|
+
content (price / feature list) precedes it. (Plan Picker option-card grid) */
|
|
176
182
|
:scope[layout="tile"] {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
gap: var(--option-card-gap-y, var(--option-card-gap-y-default));
|
|
186
|
+
position: relative;
|
|
187
|
+
/* padding inherited from base :scope (token-driven, --option-card-padding-*) */
|
|
188
|
+
}
|
|
189
|
+
/* Radio pinned to the card's top-right corner, OUT of flow. Clear the
|
|
190
|
+
base grid-area:indicator so the absolute containing block is the card
|
|
191
|
+
itself (position:relative) rather than a collapsed grid cell — otherwise
|
|
192
|
+
a grid-forcing host (plan-picker-ui) strands the indicator in an 8px
|
|
193
|
+
gutter and inset-inline-end resolves negative. Inset by the active
|
|
194
|
+
padding token so the dot aligns with the content edge. */
|
|
195
|
+
:scope[layout="tile"]::before {
|
|
196
|
+
position: absolute;
|
|
197
|
+
grid-area: auto;
|
|
198
|
+
inset-block-start: var(--option-card-padding-block, var(--option-card-padding-block-default));
|
|
199
|
+
inset-inline-end: var(--option-card-padding-inline, var(--option-card-padding-inline-default));
|
|
200
|
+
margin-block-start: 0;
|
|
186
201
|
}
|
|
202
|
+
/* Visual order is fixed via `order`, NOT DOM order: composites
|
|
203
|
+
(plan-picker-ui) append default-slot body content BEFORE option-card
|
|
204
|
+
stamps the heading/description spans on connect, so a flex stack that
|
|
205
|
+
trusted DOM order would render the body above the title. */
|
|
187
206
|
:scope[layout="tile"] [slot="icon"] {
|
|
188
|
-
|
|
189
|
-
justify-self: start;
|
|
207
|
+
order: 1;
|
|
190
208
|
align-self: start;
|
|
191
209
|
--option-card-icon-size-default: 1.75rem;
|
|
192
210
|
}
|
|
193
|
-
:scope[layout="tile"]::before {
|
|
194
|
-
grid-area: indicator;
|
|
195
|
-
align-self: start;
|
|
196
|
-
justify-self: end;
|
|
197
|
-
margin-block-start: 0;
|
|
198
|
-
}
|
|
199
211
|
:scope[layout="tile"] [slot="heading"] {
|
|
200
|
-
|
|
212
|
+
order: 2;
|
|
213
|
+
/* Reserve the top-right radio's footprint so a long heading doesn't run
|
|
214
|
+
under it. */
|
|
215
|
+
padding-inline-end: var(--a-space-6);
|
|
216
|
+
}
|
|
217
|
+
:scope[layout="tile"] [slot="description"] {
|
|
218
|
+
order: 3;
|
|
201
219
|
}
|
|
202
220
|
:scope[layout="tile"] > :not([slot]) {
|
|
203
|
-
|
|
221
|
+
order: 4;
|
|
222
|
+
}
|
|
223
|
+
/* CTA anchors to the card bottom (auto top-margin) + full-width, so a row of
|
|
224
|
+
plan cards with different body lengths keeps their buttons aligned. */
|
|
225
|
+
:scope[layout="tile"] [slot="action"] {
|
|
226
|
+
order: 5;
|
|
227
|
+
width: 100%;
|
|
228
|
+
margin-block-start: auto;
|
|
204
229
|
}
|
|
205
230
|
|
|
206
231
|
/* ── State: disabled ── */
|
|
@@ -76,6 +76,11 @@ slots:
|
|
|
76
76
|
description: Rich description content. Overrides the `description` attribute when present.
|
|
77
77
|
icon:
|
|
78
78
|
description: Custom icon element. Overrides the `icon` attribute when present.
|
|
79
|
+
action:
|
|
80
|
+
description: >-
|
|
81
|
+
CTA slot for `layout="tile"` — a button-ui anchored to the card's bottom
|
|
82
|
+
edge and stretched full-width, so a row of tile cards keeps their CTAs
|
|
83
|
+
aligned regardless of body length (plan-picker-style pickers).
|
|
79
84
|
default:
|
|
80
85
|
description: >-
|
|
81
86
|
Spillover content revealed only when the card is checked — typically a
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
@scope (otp-input-ui) {
|
|
2
2
|
:where(:scope) {
|
|
3
3
|
/* ── Tokens ── */
|
|
4
|
-
|
|
4
|
+
/* Digit boxes grow to fill the row, capped at ~1.75× the base control size
|
|
5
|
+
(~52px) so they read as comfortable single-digit boxes. Radius is the
|
|
6
|
+
plain --a-radius-lg token (no min()/clamp expression — radius patterns
|
|
7
|
+
stay simple). Scales with [size] + density (relative to --a-size). */
|
|
8
|
+
--otp-input-size-default: calc(var(--a-size) * 1.75);
|
|
5
9
|
--otp-input-gap-default: var(--a-space-2);
|
|
6
|
-
--otp-input-radius-default: var(--a-radius-
|
|
10
|
+
--otp-input-radius-default: var(--a-radius-lg);
|
|
7
11
|
--otp-input-border-default: var(--a-ui-border);
|
|
8
12
|
--otp-input-border-hover-default: var(--a-ui-border-hover);
|
|
9
13
|
--otp-input-border-focus-default: var(--a-accent);
|
|
@@ -24,15 +28,27 @@
|
|
|
24
28
|
/* ── Base ── */
|
|
25
29
|
box-sizing: border-box;
|
|
26
30
|
display: flex;
|
|
27
|
-
justify-content:
|
|
31
|
+
justify-content: center;
|
|
28
32
|
gap: var(--otp-input-gap, var(--otp-input-gap-default));
|
|
33
|
+
/* Fill the container (a block-level control per ADR-0037) so the digit
|
|
34
|
+
boxes grow to use the available width instead of sitting content-width
|
|
35
|
+
on the left. Boxes flex to fill up to their cap; once capped (very wide
|
|
36
|
+
container) the group centers rather than packing left. (bug-37 follow-up) */
|
|
37
|
+
width: 100%;
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
/* ── Digit inputs ── */
|
|
41
|
+
/* Boxes grow equally to fill the row (flex), staying square (aspect-ratio)
|
|
42
|
+
and capped at --otp-input-size so they read as digit boxes — not huge on a
|
|
43
|
+
wide container, not cramped on a narrow one. The cap keeps --a-radius-md
|
|
44
|
+
proportional (a rounded square, ~25-30%), which is the whole point of the
|
|
45
|
+
bug-37 sizing. */
|
|
32
46
|
[slot="digit"] {
|
|
33
47
|
box-sizing: border-box;
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
flex: 1 1 0;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
max-width: var(--otp-input-size, var(--otp-input-size-default));
|
|
51
|
+
aspect-ratio: 1;
|
|
36
52
|
text-align: center;
|
|
37
53
|
border: 1px solid var(--otp-input-border, var(--otp-input-border-default));
|
|
38
54
|
border-radius: var(--otp-input-radius, var(--otp-input-radius-default));
|
|
@@ -67,12 +83,11 @@
|
|
|
67
83
|
:scope[disabled] [slot="digit"] {
|
|
68
84
|
background: var(--otp-input-bg-disabled, var(--otp-input-bg-disabled-default));
|
|
69
85
|
color: var(--otp-input-fg-disabled, var(--otp-input-fg-disabled-default));
|
|
70
|
-
/*
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
border-
|
|
86
|
+
/* Native dashed border — it follows the box's border-radius (a rounded
|
|
87
|
+
dashed square, matching the enabled state). An SVG border-image would
|
|
88
|
+
give more uniform dashes but is clipped to a SQUARE — it ignores
|
|
89
|
+
border-radius — so the disabled corners wouldn't round. */
|
|
74
90
|
border: 1px dashed var(--otp-input-border-disabled, var(--otp-input-border-disabled-default));
|
|
75
|
-
border-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' preserveAspectRatio='none'><rect x='0.5' y='0.5' width='27' height='27' fill='none' stroke='%23999' stroke-width='1' stroke-dasharray='8 6'/></svg>") 1 repeat;
|
|
76
91
|
cursor: not-allowed;
|
|
77
92
|
}
|
|
78
93
|
}
|
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
|
},
|