@adia-ai/web-components 0.6.36 → 0.6.38
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 +48 -1
- package/components/accordion/accordion-item.a2ui.json +3 -0
- package/components/accordion/accordion-item.yaml +5 -0
- package/components/action-list/action-item.a2ui.json +5 -1
- package/components/action-list/action-item.yaml +7 -0
- package/components/badge/badge.a2ui.json +10 -0
- package/components/badge/badge.css +70 -0
- package/components/badge/badge.yaml +20 -0
- package/components/blockquote/blockquote.a2ui.json +121 -0
- package/components/blockquote/blockquote.class.js +68 -0
- package/components/blockquote/blockquote.css +46 -0
- package/components/blockquote/blockquote.d.ts +31 -0
- package/components/blockquote/blockquote.js +17 -0
- package/components/blockquote/blockquote.yaml +124 -0
- package/components/button/button.css +11 -3
- package/components/calendar-picker/calendar-picker.a2ui.json +15 -0
- package/components/calendar-picker/calendar-picker.class.js +7 -1
- package/components/calendar-picker/calendar-picker.yaml +14 -0
- package/components/card/card.a2ui.json +17 -1
- package/components/card/card.yaml +24 -1
- package/components/color-input/color-input.a2ui.json +2 -2
- package/components/color-input/color-input.class.js +9 -2
- package/components/color-input/color-input.yaml +2 -2
- package/components/combobox/combobox.class.js +4 -0
- package/components/context-menu/context-menu.a2ui.json +159 -0
- package/components/context-menu/context-menu.class.js +275 -0
- package/components/context-menu/context-menu.css +56 -0
- package/components/context-menu/context-menu.d.ts +70 -0
- package/components/context-menu/context-menu.js +17 -0
- package/components/context-menu/context-menu.yaml +136 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
- package/components/date-range-picker/date-range-picker.class.js +2 -0
- package/components/date-range-picker/date-range-picker.yaml +14 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
- package/components/datetime-picker/datetime-picker.class.js +3 -1
- package/components/datetime-picker/datetime-picker.d.ts +2 -0
- package/components/datetime-picker/datetime-picker.yaml +14 -0
- package/components/empty-state/empty-state.a2ui.json +9 -0
- package/components/empty-state/empty-state.class.js +2 -0
- package/components/empty-state/empty-state.yaml +15 -0
- package/components/feed/feed-item.a2ui.json +5 -0
- package/components/feed/feed-item.yaml +10 -0
- package/components/feed/feed.class.js +13 -5
- package/components/feed/feed.css +14 -0
- package/components/field/field.a2ui.json +6 -0
- package/components/field/field.yaml +10 -0
- package/components/index.js +11 -0
- package/components/inline-edit/inline-edit.a2ui.json +159 -0
- package/components/inline-edit/inline-edit.class.js +184 -0
- package/components/inline-edit/inline-edit.css +62 -0
- package/components/inline-edit/inline-edit.d.ts +52 -0
- package/components/inline-edit/inline-edit.js +12 -0
- package/components/inline-edit/inline-edit.yaml +125 -0
- package/components/integration-card/integration-card.class.js +9 -0
- package/components/integration-card/integration-card.test.js +4 -3
- package/components/list/list-item.a2ui.json +8 -1
- package/components/list/list-item.yaml +12 -0
- package/components/list/list.css +36 -6
- package/components/mark/mark.a2ui.json +109 -0
- package/components/mark/mark.class.js +22 -0
- package/components/mark/mark.css +39 -0
- package/components/mark/mark.d.ts +27 -0
- package/components/mark/mark.js +12 -0
- package/components/mark/mark.yaml +87 -0
- package/components/modal/modal.a2ui.json +9 -0
- package/components/modal/modal.yaml +14 -0
- package/components/nav-group/nav-group.a2ui.json +3 -0
- package/components/nav-group/nav-group.css +7 -1
- package/components/nav-group/nav-group.yaml +5 -0
- package/components/nav-item/nav-item.a2ui.json +3 -0
- package/components/nav-item/nav-item.yaml +5 -0
- package/components/number-format/number-format.a2ui.json +180 -0
- package/components/number-format/number-format.class.js +96 -0
- package/components/number-format/number-format.css +18 -0
- package/components/number-format/number-format.d.ts +68 -0
- package/components/number-format/number-format.js +17 -0
- package/components/number-format/number-format.yaml +204 -0
- package/components/pagination/pagination.a2ui.json +19 -2
- package/components/pagination/pagination.class.js +90 -37
- package/components/pagination/pagination.css +32 -127
- package/components/pagination/pagination.d.ts +8 -2
- package/components/pagination/pagination.test.js +195 -0
- package/components/pagination/pagination.yaml +22 -1
- package/components/password-strength/password-strength.a2ui.json +152 -0
- package/components/password-strength/password-strength.class.js +157 -0
- package/components/password-strength/password-strength.css +80 -0
- package/components/password-strength/password-strength.d.ts +59 -0
- package/components/password-strength/password-strength.js +17 -0
- package/components/password-strength/password-strength.yaml +153 -0
- package/components/popover/popover.css +43 -23
- package/components/popover/popover.yaml +8 -4
- package/components/qr-code/QR-TEST.svg +4 -0
- package/components/qr-code/qr-code.a2ui.json +154 -0
- package/components/qr-code/qr-code.class.js +129 -0
- package/components/qr-code/qr-code.css +41 -0
- package/components/qr-code/qr-code.d.ts +83 -0
- package/components/qr-code/qr-code.js +17 -0
- package/components/qr-code/qr-code.yaml +203 -0
- package/components/qr-code/qr-encoder.js +633 -0
- package/components/relative-time/relative-time.a2ui.json +120 -0
- package/components/relative-time/relative-time.class.js +136 -0
- package/components/relative-time/relative-time.css +22 -0
- package/components/relative-time/relative-time.d.ts +51 -0
- package/components/relative-time/relative-time.js +17 -0
- package/components/relative-time/relative-time.yaml +133 -0
- package/components/segmented/segmented.class.js +15 -3
- package/components/select/select.a2ui.json +3 -0
- package/components/select/select.class.js +4 -0
- package/components/select/select.yaml +5 -0
- package/components/skip-nav/skip-nav.a2ui.json +92 -0
- package/components/skip-nav/skip-nav.class.js +45 -0
- package/components/skip-nav/skip-nav.css +54 -0
- package/components/skip-nav/skip-nav.d.ts +27 -0
- package/components/skip-nav/skip-nav.js +12 -0
- package/components/skip-nav/skip-nav.yaml +68 -0
- package/components/slider/slider.a2ui.json +22 -1
- package/components/slider/slider.class.js +264 -122
- package/components/slider/slider.css +82 -2
- package/components/slider/slider.d.ts +19 -3
- package/components/slider/slider.test.js +55 -0
- package/components/slider/slider.yaml +38 -6
- package/components/stat/stat.css +18 -14
- package/components/stepper/stepper-item.a2ui.json +3 -0
- package/components/stepper/stepper-item.yaml +5 -0
- package/components/table/table.class.js +29 -6
- package/components/table/table.css +31 -4
- package/components/table-toolbar/table-toolbar.class.js +3 -1
- package/components/tag/tag.a2ui.json +3 -2
- package/components/tag/tag.css +35 -11
- package/components/tag/tag.d.ts +14 -0
- package/components/tag/tag.test.js +35 -11
- package/components/tag/tag.yaml +13 -7
- package/components/timeline/timeline-item.a2ui.json +8 -1
- package/components/timeline/timeline-item.yaml +12 -0
- package/components/toast/toast.class.js +12 -4
- package/components/toc/toc.a2ui.json +159 -0
- package/components/toc/toc.class.js +222 -0
- package/components/toc/toc.css +92 -0
- package/components/toc/toc.d.ts +61 -0
- package/components/toc/toc.js +17 -0
- package/components/toc/toc.yaml +180 -0
- package/components/toolbar/toolbar.class.js +3 -0
- package/components/tree/tree-item.a2ui.json +5 -1
- package/components/tree/tree-item.yaml +7 -0
- package/components/tree/tree.a2ui.json +3 -0
- package/components/tree/tree.yaml +5 -0
- package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
- package/components/visually-hidden/visually-hidden.class.js +14 -0
- package/components/visually-hidden/visually-hidden.css +25 -0
- package/components/visually-hidden/visually-hidden.d.ts +26 -0
- package/components/visually-hidden/visually-hidden.js +12 -0
- package/components/visually-hidden/visually-hidden.yaml +54 -0
- package/core/anchor.js +19 -3
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +100 -89
- package/package.json +1 -1
- package/styles/colors/semantics.css +11 -2
- package/styles/components.css +11 -0
- package/styles/resets.css +10 -0
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `<slider-ui>` — Single-
|
|
2
|
+
* `<slider-ui>` — Single- or two-thumb slider for numeric input.
|
|
3
|
+
*
|
|
4
|
+
* Default: single-thumb (`value`).
|
|
5
|
+
* Dual mode: set `[dual]` and use `[lower-value]` / `[upper-value]`; the fill
|
|
6
|
+
* spans between the thumbs and the `change` event detail becomes
|
|
7
|
+
* `{lower, upper}`.
|
|
3
8
|
*
|
|
4
9
|
* @see https://ui-kit.exe.xyz/site/components/slider
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
import { UIFormElement } from '../../core/form.js';
|
|
8
13
|
|
|
9
|
-
export interface
|
|
14
|
+
export interface SliderChangeEventDetailSingle {
|
|
10
15
|
value: number;
|
|
11
16
|
}
|
|
17
|
+
export interface SliderChangeEventDetailDual {
|
|
18
|
+
lower: number;
|
|
19
|
+
upper: number;
|
|
20
|
+
}
|
|
21
|
+
export type SliderChangeEventDetail = SliderChangeEventDetailSingle | SliderChangeEventDetailDual;
|
|
12
22
|
export type SliderChangeEvent = CustomEvent<SliderChangeEventDetail>;
|
|
13
23
|
export type SliderInputEvent = CustomEvent<SliderChangeEventDetail>;
|
|
14
24
|
|
|
15
25
|
export class UISlider extends UIFormElement {
|
|
16
|
-
/** Numeric value — overrides UIFormElement.value (which is String). */
|
|
26
|
+
/** Numeric value — overrides UIFormElement.value (which is String). Single-thumb mode; ignored when [dual]. */
|
|
17
27
|
value: number;
|
|
28
|
+
/** Two-thumb range slider mode. */
|
|
29
|
+
dual: boolean;
|
|
30
|
+
/** Lower thumb value (dual mode). */
|
|
31
|
+
lowerValue: number;
|
|
32
|
+
/** Upper thumb value (dual mode). */
|
|
33
|
+
upperValue: number;
|
|
18
34
|
min: number;
|
|
19
35
|
max: number;
|
|
20
36
|
step: number;
|
|
@@ -144,4 +144,59 @@ describe('slider-ui', () => {
|
|
|
144
144
|
|
|
145
145
|
expect(s.querySelector('[slot="suffix"]').textContent).toBe('rem');
|
|
146
146
|
});
|
|
147
|
+
|
|
148
|
+
// ─── Dual-thumb mode ────────────────────────────────────────────────
|
|
149
|
+
// v1.9.x: dual-thumb range slider lives on slider-ui[dual] + [lower-value]/[upper-value].
|
|
150
|
+
// Fill spans BETWEEN the two thumb centers; thumbs are positioned via
|
|
151
|
+
// --slider-pct-lower / --slider-pct-upper CSS variables.
|
|
152
|
+
const pctLower = (s) => s.style.getPropertyValue('--slider-pct-lower').trim();
|
|
153
|
+
const pctUpper = (s) => s.style.getPropertyValue('--slider-pct-upper').trim();
|
|
154
|
+
|
|
155
|
+
it('dual mode: writes --slider-pct-lower and --slider-pct-upper from lower-value / upper-value', async () => {
|
|
156
|
+
const s = mount('<slider-ui dual lower-value="20" upper-value="80" min="0" max="100"></slider-ui>');
|
|
157
|
+
await tick();
|
|
158
|
+
expect(pctLower(s)).toBe('0.2');
|
|
159
|
+
expect(pctUpper(s)).toBe('0.8');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('dual mode: stamps two thumb elements with data-thumb="lower" and data-thumb="upper"', async () => {
|
|
163
|
+
const s = mount('<slider-ui dual lower-value="20" upper-value="80" min="0" max="100"></slider-ui>');
|
|
164
|
+
await tick();
|
|
165
|
+
const lowerThumb = s.querySelector('[slot="thumb"][data-thumb="lower"]');
|
|
166
|
+
const upperThumb = s.querySelector('[slot="thumb"][data-thumb="upper"]');
|
|
167
|
+
expect(lowerThumb).not.toBeNull();
|
|
168
|
+
expect(upperThumb).not.toBeNull();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('dual mode: change event detail carries {lower, upper}', async () => {
|
|
172
|
+
const s = mount('<slider-ui dual lower-value="20" upper-value="80" min="0" max="100" step="1"></slider-ui>');
|
|
173
|
+
await tick();
|
|
174
|
+
let captured = null;
|
|
175
|
+
s.addEventListener('change', (e) => { captured = e; });
|
|
176
|
+
// Focus the lower thumb explicitly so the keyboard handler targets it
|
|
177
|
+
const lowerThumb = s.querySelector('[slot="thumb"][data-thumb="lower"]');
|
|
178
|
+
lowerThumb.focus();
|
|
179
|
+
lowerThumb.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
|
|
180
|
+
await tick();
|
|
181
|
+
expect(captured).not.toBeNull();
|
|
182
|
+
expect(captured.detail).toEqual({ lower: 21, upper: 80 });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('dual mode: lower setter clamps DOWN to upper when set too high', async () => {
|
|
186
|
+
const s = mount('<slider-ui dual lower-value="20" upper-value="80" min="0" max="100" step="1"></slider-ui>');
|
|
187
|
+
await tick();
|
|
188
|
+
s.lowerValue = 90; // > upperValue (80) → constraint clamps lower DOWN to upper
|
|
189
|
+
await tick();
|
|
190
|
+
expect(s.lowerValue).toBe(80);
|
|
191
|
+
expect(s.upperValue).toBe(80); // upper unchanged
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('dual mode: upper setter clamps UP to lower when set too low', async () => {
|
|
195
|
+
const s = mount('<slider-ui dual lower-value="20" upper-value="80" min="0" max="100" step="1"></slider-ui>');
|
|
196
|
+
await tick();
|
|
197
|
+
s.upperValue = 10; // < lowerValue (20) → constraint clamps upper UP to lower
|
|
198
|
+
await tick();
|
|
199
|
+
expect(s.upperValue).toBe(20);
|
|
200
|
+
expect(s.lowerValue).toBe(20); // lower unchanged
|
|
201
|
+
});
|
|
147
202
|
});
|
|
@@ -49,9 +49,31 @@ props:
|
|
|
49
49
|
type: string
|
|
50
50
|
default: ""
|
|
51
51
|
value:
|
|
52
|
-
description: Current slider value
|
|
52
|
+
description: Current slider value (single-thumb mode; ignored when [dual] is set)
|
|
53
53
|
type: number
|
|
54
54
|
default: 50
|
|
55
|
+
dual:
|
|
56
|
+
description: |-
|
|
57
|
+
Two-thumb range slider mode. When enabled, [lower-value] and
|
|
58
|
+
[upper-value] are authoritative and [value] is ignored. The fill
|
|
59
|
+
renders between the two thumbs (not from left to thumb). Form-data
|
|
60
|
+
under [name] serializes as "<lower>,<upper>". Use for price filters,
|
|
61
|
+
date ranges, audio range gates, etc.
|
|
62
|
+
type: boolean
|
|
63
|
+
default: false
|
|
64
|
+
reflect: true
|
|
65
|
+
lowerValue:
|
|
66
|
+
description: Lower thumb value (dual mode only; clamped to ≤ [upper-value]).
|
|
67
|
+
type: number
|
|
68
|
+
default: 0
|
|
69
|
+
reflect: true
|
|
70
|
+
attribute: lower-value
|
|
71
|
+
upperValue:
|
|
72
|
+
description: Upper thumb value (dual mode only; clamped to ≥ [lower-value]).
|
|
73
|
+
type: number
|
|
74
|
+
default: 100
|
|
75
|
+
reflect: true
|
|
76
|
+
attribute: upper-value
|
|
55
77
|
throttle:
|
|
56
78
|
description: |-
|
|
57
79
|
§184 (v0.5.5, FEEDBACK-08 §4): when > 0, debounce the `input` event by this many milliseconds. Value updates + visual feedback remain immediate; only event dispatch accumulates. Pending input flushes BEFORE `change` so consumers always see input→…→input→change ordering. throttle="0" (default) preserves the pre-§184 every-pointer-move-fires-input behavior. Common values: 50-100ms for palette regen / shader compile / large list reflow.
|
|
@@ -74,6 +96,16 @@ slots:
|
|
|
74
96
|
description: Label element above the slider
|
|
75
97
|
value:
|
|
76
98
|
description: Current value display
|
|
99
|
+
header:
|
|
100
|
+
description: >-
|
|
101
|
+
Wholesale override of the stamped label + value-readout header row.
|
|
102
|
+
Use only when full custom-header markup is needed; otherwise prefer
|
|
103
|
+
the granular `label` / `value` slots and the [label] attribute.
|
|
104
|
+
hint:
|
|
105
|
+
description: >-
|
|
106
|
+
Override slot for hint markup richer than the plain [hint] attribute
|
|
107
|
+
string (inline links, code spans). Renders beneath the slider at
|
|
108
|
+
body-subtle typography. Mutually exclusive with [hint].
|
|
77
109
|
states:
|
|
78
110
|
- name: idle
|
|
79
111
|
description: Default, ready for interaction.
|
|
@@ -94,12 +126,12 @@ tokens:
|
|
|
94
126
|
description: Full track height (scales via universal [size] attribute)
|
|
95
127
|
a2ui:
|
|
96
128
|
rules:
|
|
97
|
-
- rule: '
|
|
129
|
+
- rule: 'Default mode is single-handle. Form-associated; emits numeric change events with detail.value.'
|
|
98
130
|
reason: 'Single-value range-input contract.'
|
|
99
|
-
- rule: 'For two-handle range selection use <range-ui>
|
|
100
|
-
reason: '
|
|
101
|
-
- rule: '
|
|
102
|
-
reason: 'Standard slider knobs.'
|
|
131
|
+
- rule: 'For two-handle range selection (price filters, date ranges, audio range gates), set [dual] and use [lower-value]/[upper-value] instead of [value]. Form-data submits as "<lower>,<upper>" under [name]; change event detail carries {lower, upper}. Do NOT use <range-ui> — that primitive is a draggable numeric field, not a two-thumb range slider.'
|
|
132
|
+
reason: 'Dual-thumb range selection lives on slider-ui[dual], not on a separate primitive.'
|
|
133
|
+
- rule: '[step] controls increments for both single and dual modes. Constraint: in dual mode, [lower-value] ≤ [upper-value] is enforced on each setter; reversed values clamp to equal.'
|
|
134
|
+
reason: 'Standard slider knobs + dual-mode constraint.'
|
|
103
135
|
anti_patterns: []
|
|
104
136
|
examples:
|
|
105
137
|
- name: slider-range
|
package/components/stat/stat.css
CHANGED
|
@@ -38,34 +38,38 @@
|
|
|
38
38
|
|
|
39
39
|
/* ── Chart slot (inline sparkline) ──
|
|
40
40
|
When an author adds <chart-ui slot="chart"> (or any sparkline-shape
|
|
41
|
-
child), the grid reflows so the chart
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
child), the grid reflows so the chart sits in the value row and
|
|
42
|
+
ends at the value's baseline — hero number and chart read as one
|
|
43
|
+
visual unit, the chart "resting on" the value text. */
|
|
44
44
|
:scope:has([slot="chart"]) {
|
|
45
45
|
grid-template-columns: minmax(0, 1fr) minmax(5rem, 40%);
|
|
46
46
|
grid-template-areas:
|
|
47
47
|
"label icon"
|
|
48
48
|
"value chart"
|
|
49
|
-
"change
|
|
49
|
+
"change .";
|
|
50
50
|
align-items: end;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
compound selector to beat chart-ui's own `:scope { max-height: 28rem }`
|
|
58
|
-
(specificity 0,1,1 beats 0,0,1). `[slot="chart"]` alone is (0,1,0)
|
|
59
|
-
— enough on paper but empirically losing to chart-ui's own scope in
|
|
60
|
-
practice; the type selector makes it explicit. */
|
|
53
|
+
/* Chart cell — bottom-aligned to the value row's baseline; aspect
|
|
54
|
+
ratio 4:3 (width:height) keeps it readable as a small inline chart
|
|
55
|
+
rather than a thin sparkline rail. Consumers override via
|
|
56
|
+
--stat-chart-aspect. */
|
|
61
57
|
[slot="chart"] {
|
|
62
58
|
grid-area: chart;
|
|
63
|
-
align-self:
|
|
59
|
+
align-self: end;
|
|
64
60
|
min-width: 0;
|
|
65
61
|
width: 100%;
|
|
62
|
+
aspect-ratio: var(--stat-chart-aspect, 4 / 3);
|
|
66
63
|
}
|
|
67
64
|
chart-ui[slot="chart"] {
|
|
68
|
-
|
|
65
|
+
height: 100%;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* When chart is present, value must NOT span into the chart column
|
|
69
|
+
(its default `grid-column: 1 / -1` would overlap the chart cell and
|
|
70
|
+
misalign both). Confine it to col 1. */
|
|
71
|
+
:scope:has([slot="chart"]) [slot="value"] {
|
|
72
|
+
grid-column: 1;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/* ── Label (eyebrow) ── */
|
|
@@ -70,6 +70,9 @@
|
|
|
70
70
|
"description": {
|
|
71
71
|
"description": "Custom description content; falls through to `[description]` prop if not slotted."
|
|
72
72
|
},
|
|
73
|
+
"icon": {
|
|
74
|
+
"description": "Override the step-status glyph with a custom slotted element. By default the step renders a numeral / check / x indicator based on [status]; slot an `<icon-ui>` or image to customize."
|
|
75
|
+
},
|
|
73
76
|
"label": {
|
|
74
77
|
"description": "Custom label content; falls through to `[text]` prop if not slotted."
|
|
75
78
|
}
|
|
@@ -44,6 +44,11 @@ slots:
|
|
|
44
44
|
description: Custom label content; falls through to `[text]` prop if not slotted.
|
|
45
45
|
description:
|
|
46
46
|
description: Custom description content; falls through to `[description]` prop if not slotted.
|
|
47
|
+
icon:
|
|
48
|
+
description: >-
|
|
49
|
+
Override the step-status glyph with a custom slotted element. By default
|
|
50
|
+
the step renders a numeral / check / x indicator based on [status];
|
|
51
|
+
slot an `<icon-ui>` or image to customize.
|
|
47
52
|
|
|
48
53
|
keywords:
|
|
49
54
|
- stepper-item
|
|
@@ -326,12 +326,20 @@ export class UITable extends UIElement {
|
|
|
326
326
|
this.#bound = true;
|
|
327
327
|
this.addEventListener('click', this.#onClick);
|
|
328
328
|
this.addEventListener('keydown', this.#onKeydown);
|
|
329
|
+
// Column resize needs pointerdown (drag start) — NOT click. click fires
|
|
330
|
+
// after pointerup, so by the time #startResize runs from a click
|
|
331
|
+
// handler, the user has already released and the document-level
|
|
332
|
+
// move/up listeners never trigger. pointerdown also covers touch +
|
|
333
|
+
// pen automatically; Playwright's page.mouse.* synthesizes pointer
|
|
334
|
+
// events (not mouse events), so a mousedown-only handler tests dead.
|
|
335
|
+
this.addEventListener('pointerdown', this.#onPointerdown);
|
|
329
336
|
}
|
|
330
337
|
}
|
|
331
338
|
|
|
332
339
|
disconnected() {
|
|
333
340
|
this.removeEventListener('click', this.#onClick);
|
|
334
341
|
this.removeEventListener('keydown', this.#onKeydown);
|
|
342
|
+
this.removeEventListener('pointerdown', this.#onPointerdown);
|
|
335
343
|
this.#bound = false;
|
|
336
344
|
this.#cleanupResize();
|
|
337
345
|
if (this.#renderRaf) {
|
|
@@ -1178,6 +1186,21 @@ export class UITable extends UIElement {
|
|
|
1178
1186
|
bar.appendChild(clearAll);
|
|
1179
1187
|
}
|
|
1180
1188
|
|
|
1189
|
+
// ── Event Handling: Pointerdown (resize-drag start) ───────────────────────
|
|
1190
|
+
|
|
1191
|
+
/** Resize-handle drag MUST start on pointerdown, not click — click fires
|
|
1192
|
+
* only on full down→up gestures (no drag delta), so the document-level
|
|
1193
|
+
* move/up listeners #startResize attaches arrive after the release.
|
|
1194
|
+
* pointer events cover mouse + touch + pen in one handler. */
|
|
1195
|
+
#onPointerdown = (e) => {
|
|
1196
|
+
if (e.button !== undefined && e.button !== 0) return; // primary button only
|
|
1197
|
+
const handle = e.target.closest('[data-resize-handle]');
|
|
1198
|
+
if (handle && this.contains(handle)) {
|
|
1199
|
+
e.preventDefault();
|
|
1200
|
+
this.#startResize(e, handle);
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1181
1204
|
// ── Event Handling: Click ──────────────────────────────────────────────────
|
|
1182
1205
|
|
|
1183
1206
|
#onClick = (e) => {
|
|
@@ -1355,8 +1378,8 @@ export class UITable extends UIElement {
|
|
|
1355
1378
|
|
|
1356
1379
|
this.#resizeState = { key, col, startX, startWidth };
|
|
1357
1380
|
|
|
1358
|
-
document.addEventListener('
|
|
1359
|
-
document.addEventListener('
|
|
1381
|
+
document.addEventListener('pointermove', this.#onResizeMove);
|
|
1382
|
+
document.addEventListener('pointerup', this.#onResizeEnd);
|
|
1360
1383
|
}
|
|
1361
1384
|
|
|
1362
1385
|
#onResizeMove = (e) => {
|
|
@@ -1378,8 +1401,8 @@ export class UITable extends UIElement {
|
|
|
1378
1401
|
if (!this.#resizeState) return;
|
|
1379
1402
|
const { key } = this.#resizeState;
|
|
1380
1403
|
|
|
1381
|
-
document.removeEventListener('
|
|
1382
|
-
document.removeEventListener('
|
|
1404
|
+
document.removeEventListener('pointermove', this.#onResizeMove);
|
|
1405
|
+
document.removeEventListener('pointerup', this.#onResizeEnd);
|
|
1383
1406
|
|
|
1384
1407
|
this.removeAttribute('data-resizing');
|
|
1385
1408
|
|
|
@@ -1395,8 +1418,8 @@ export class UITable extends UIElement {
|
|
|
1395
1418
|
};
|
|
1396
1419
|
|
|
1397
1420
|
#cleanupResize() {
|
|
1398
|
-
document.removeEventListener('
|
|
1399
|
-
document.removeEventListener('
|
|
1421
|
+
document.removeEventListener('pointermove', this.#onResizeMove);
|
|
1422
|
+
document.removeEventListener('pointerup', this.#onResizeEnd);
|
|
1400
1423
|
this.#resizeState = null;
|
|
1401
1424
|
}
|
|
1402
1425
|
|
|
@@ -25,7 +25,11 @@
|
|
|
25
25
|
--table-accent-default: var(--a-primary);
|
|
26
26
|
--table-fg-disabled-default: var(--a-ui-text-disabled);
|
|
27
27
|
--table-bg-default: var(--a-bg);
|
|
28
|
-
|
|
28
|
+
/* No default rounding — table-ui composes inside card-ui / drawer-ui
|
|
29
|
+
which own the surface rounding; doubly-rounded corners (table inside
|
|
30
|
+
card) look broken. Override per-instance via `--table-radius: …` if
|
|
31
|
+
table-ui sits standalone on the page. */
|
|
32
|
+
--table-radius-default: 0;
|
|
29
33
|
|
|
30
34
|
/* ── Resize + pinned-column intrinsics ── */
|
|
31
35
|
--table-resize-width-default: var(--a-space-1);
|
|
@@ -103,11 +107,22 @@
|
|
|
103
107
|
:scope {
|
|
104
108
|
box-sizing: border-box;
|
|
105
109
|
display: grid;
|
|
106
|
-
overflow
|
|
110
|
+
/* `overflow: auto` (both axes) — needed so position:sticky on the
|
|
111
|
+
header works relative to table-ui's own scroll viewport. Vertical
|
|
112
|
+
scroll only kicks in when consumer sets max-height; horizontal
|
|
113
|
+
only when grid-template-columns sum exceeds the container width.
|
|
114
|
+
Pre-fix: overflow-x: auto alone made table-ui the sticky-containing
|
|
115
|
+
block but with no vertical clip — wrapping in an outer max-height
|
|
116
|
+
div made the WHOLE table scroll (header included), defeating sticky. */
|
|
117
|
+
overflow: auto;
|
|
107
118
|
font-size: var(--table-font-size, var(--table-font-size-default));
|
|
108
119
|
color: var(--table-fg, var(--table-fg-default));
|
|
109
120
|
background: var(--table-bg, var(--table-bg-default));
|
|
110
|
-
|
|
121
|
+
/* Use inset box-shadow as a faux border so the 1px chrome paints
|
|
122
|
+
inside the content box and doesn't shrink clientWidth — a real
|
|
123
|
+
`border: 1px` made grid content overflow by exactly 2 px on every
|
|
124
|
+
table (scrollbar appeared with nothing to scroll). */
|
|
125
|
+
box-shadow: inset 0 0 0 1px var(--table-border, var(--table-border-default));
|
|
111
126
|
border-radius: var(--table-radius, var(--table-radius-default));
|
|
112
127
|
position: relative;
|
|
113
128
|
/* Own stacking context — sticky headers, pinned columns, and filter
|
|
@@ -119,7 +134,7 @@
|
|
|
119
134
|
|
|
120
135
|
:scope[raw] {
|
|
121
136
|
background: none;
|
|
122
|
-
|
|
137
|
+
box-shadow: none;
|
|
123
138
|
border-radius: 0;
|
|
124
139
|
}
|
|
125
140
|
|
|
@@ -377,6 +392,18 @@
|
|
|
377
392
|
bottom: 0;
|
|
378
393
|
width: var(--table-resize-width, var(--table-resize-width-default));
|
|
379
394
|
cursor: col-resize;
|
|
395
|
+
/* Sit above the next column header so pointer events hit the handle
|
|
396
|
+
(the handle is centered on the column boundary; without z-index the
|
|
397
|
+
neighbor's content wins the overlap and the resize never starts). */
|
|
398
|
+
z-index: 2;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* Hide the resize handle on the last column — it overflows 2 px past the
|
|
402
|
+
table's right edge (handle centered on the column boundary; the last
|
|
403
|
+
column has no neighbor to resize against) and triggered a 2 px scrollbar
|
|
404
|
+
overhang on every table even with nothing to scroll. */
|
|
405
|
+
[role="row"] > [role="columnheader"]:last-child [data-resize-handle] {
|
|
406
|
+
display: none;
|
|
380
407
|
z-index: 1;
|
|
381
408
|
}
|
|
382
409
|
|
|
@@ -435,7 +435,9 @@ export class UITableToolbar extends UIElement {
|
|
|
435
435
|
document.body.appendChild(panel);
|
|
436
436
|
|
|
437
437
|
try { panel.showPopover(); } catch { /* popover API unavailable */ }
|
|
438
|
-
|
|
438
|
+
// ADR-0034 Rule 2: filter/sort/columns panels (200-320px) >> icon trigger (~32px) → center.
|
|
439
|
+
// Placement is internal — not consumer-overridable on table-toolbar-ui.
|
|
440
|
+
const cleanup = anchorPopover(btn, panel, { placement: 'bottom', gap: 4 });
|
|
439
441
|
|
|
440
442
|
this.#activePopover = { kind, btn, panel, cleanup };
|
|
441
443
|
|
|
@@ -45,11 +45,12 @@
|
|
|
45
45
|
"$ref": "common_types.json#/$defs/DynamicString"
|
|
46
46
|
},
|
|
47
47
|
"tone": {
|
|
48
|
-
"description": "Fill style — orthogonal to [variant].
|
|
48
|
+
"description": "Fill style — orthogonal to [variant]. Three values:\n - `solid` (default for family variants) — saturated bg + on-strong\n (near-white) text. The chip IS the state.\n - `muted` — tinted bg with scheme-paired text. Matches <badge-ui>'s\n default look. Use on metadata chips in dense lists where the\n saturated default would compete for attention.\n - `outline` — transparent bg + family-colored border + family-colored\n text. The lightest visual weight; good in dense data tables or\n faceted filter rows where multiple chips would otherwise compete.\nThe `default` variant (no family) stays quiet chrome regardless of\ntone unless `tone=\"solid\"` is set explicitly (high-contrast neutral\ninverse), or `tone=\"outline\"` (fg-muted text + subtle border).\n",
|
|
49
49
|
"type": "string",
|
|
50
50
|
"enum": [
|
|
51
51
|
"solid",
|
|
52
|
-
"muted"
|
|
52
|
+
"muted",
|
|
53
|
+
"outline"
|
|
53
54
|
],
|
|
54
55
|
"default": "solid"
|
|
55
56
|
},
|
package/components/tag/tag.css
CHANGED
|
@@ -90,18 +90,12 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
90
90
|
--tag-fg-default: var(--a-success-fg);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/* `--a-warning-bg`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
intent is bright-amber-bg + dark-text. The literal `-20-tint`
|
|
98
|
-
step is bright in BOTH schemes (independent of light-dark swap),
|
|
99
|
-
so warning solid keeps the same caution-tape look across modes.
|
|
100
|
-
The other family variants stay on `-bg` + `-fg` because their
|
|
101
|
-
-text-strong is `-05-tint` (near-white) which contrasts fine
|
|
102
|
-
against their saturated `-strong` bg. */
|
|
93
|
+
/* `--a-warning-bg` is the bright-amber step (semantics.css L3 redirect
|
|
94
|
+
to `-20-tint`) paired with `--a-warning-fg` (dark brown text) — the
|
|
95
|
+
canonical caution-tape pair. Pre-v0.6.36 this rule had a local
|
|
96
|
+
`-20-tint` override; that workaround moved to the token system. */
|
|
103
97
|
:scope[variant="warning"] {
|
|
104
|
-
--tag-bg-default: var(--a-warning-
|
|
98
|
+
--tag-bg-default: var(--a-warning-bg);
|
|
105
99
|
--tag-fg-default: var(--a-warning-fg);
|
|
106
100
|
}
|
|
107
101
|
|
|
@@ -149,6 +143,36 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
149
143
|
--tag-fg-default: var(--a-bg);
|
|
150
144
|
}
|
|
151
145
|
|
|
146
|
+
/* ── `[tone="outline"]` — transparent bg + family-colored border + text ──
|
|
147
|
+
The third common chip shape: no fill, just a ring + colored label.
|
|
148
|
+
Reads as "metadata that wants color recognition but minimum
|
|
149
|
+
visual weight" — good in dense data tables, faceted filter rows,
|
|
150
|
+
or anywhere multiple chips would compete if filled. */
|
|
151
|
+
:scope[tone="outline"] {
|
|
152
|
+
--tag-bg-default: transparent;
|
|
153
|
+
}
|
|
154
|
+
:scope[tone="outline"][variant="info"] {
|
|
155
|
+
--tag-fg-default: var(--a-info-text);
|
|
156
|
+
--tag-border-default: var(--a-info-border);
|
|
157
|
+
}
|
|
158
|
+
:scope[tone="outline"][variant="success"] {
|
|
159
|
+
--tag-fg-default: var(--a-success-text);
|
|
160
|
+
--tag-border-default: var(--a-success-border);
|
|
161
|
+
}
|
|
162
|
+
:scope[tone="outline"][variant="warning"] {
|
|
163
|
+
--tag-fg-default: var(--a-warning-text);
|
|
164
|
+
--tag-border-default: var(--a-warning-border);
|
|
165
|
+
}
|
|
166
|
+
:scope[tone="outline"][variant="danger"] {
|
|
167
|
+
--tag-fg-default: var(--a-danger-text);
|
|
168
|
+
--tag-border-default: var(--a-danger-border);
|
|
169
|
+
}
|
|
170
|
+
/* Outline on neutral (no family) — fg-muted text + subtle border. */
|
|
171
|
+
:scope[tone="outline"]:not([variant="info"]):not([variant="success"]):not([variant="warning"]):not([variant="danger"]) {
|
|
172
|
+
--tag-fg-default: var(--a-fg-muted);
|
|
173
|
+
--tag-border-default: var(--a-border);
|
|
174
|
+
}
|
|
175
|
+
|
|
152
176
|
/* Size handled by universal [size] attribute system. */
|
|
153
177
|
|
|
154
178
|
/* hover rule moved outside @scope — see Safari 17.x bug note at top. */
|
package/components/tag/tag.d.ts
CHANGED
|
@@ -40,6 +40,20 @@ export class UITag extends UIElement {
|
|
|
40
40
|
text: string;
|
|
41
41
|
/** Tag label. Renderer routes this to the `text` attribute, rendered via CSS attr(text) on ::after. */
|
|
42
42
|
textContent: string;
|
|
43
|
+
/** Fill style — orthogonal to [variant]. Three values:
|
|
44
|
+
- `solid` (default for family variants) — saturated bg + on-strong
|
|
45
|
+
(near-white) text. The chip IS the state.
|
|
46
|
+
- `muted` — tinted bg with scheme-paired text. Matches <badge-ui>'s
|
|
47
|
+
default look. Use on metadata chips in dense lists where the
|
|
48
|
+
saturated default would compete for attention.
|
|
49
|
+
- `outline` — transparent bg + family-colored border + family-colored
|
|
50
|
+
text. The lightest visual weight; good in dense data tables or
|
|
51
|
+
faceted filter rows where multiple chips would otherwise compete.
|
|
52
|
+
The `default` variant (no family) stays quiet chrome regardless of
|
|
53
|
+
tone unless `tone="solid"` is set explicitly (high-contrast neutral
|
|
54
|
+
inverse), or `tone="outline"` (fg-muted text + subtle border).
|
|
55
|
+
*/
|
|
56
|
+
tone: 'solid' | 'muted' | 'outline';
|
|
43
57
|
/** Semantic variant — `default | info | success | warning | danger`. */
|
|
44
58
|
variant: 'default' | 'info' | 'success' | 'warning' | 'danger';
|
|
45
59
|
|
|
@@ -72,18 +72,13 @@ describe('tag-ui — variant defaults to solid fill', () => {
|
|
|
72
72
|
expect(TAG_CSS).toMatch(block);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
it('[variant="warning"] uses the
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
// the caution-tape look the semantics.css comment intends.
|
|
75
|
+
it('[variant="warning"] uses the canonical --a-warning-bg + --a-warning-fg pair', () => {
|
|
76
|
+
// v0.6.36 token-system fix: --a-warning-bg now redirects to
|
|
77
|
+
// --a-warning-20-tint at the L3 layer (semantics.css), so consumers
|
|
78
|
+
// can pair `-bg` + `-fg` cleanly without local overrides. The pair
|
|
79
|
+
// resolves to bright-amber-bg + dark-brown-text in both schemes.
|
|
81
80
|
expect(TAG_CSS).toMatch(
|
|
82
|
-
/:scope\[variant="warning"\]\s*\{[^}]*--tag-bg-default:\s*var\(--a-warning-
|
|
83
|
-
);
|
|
84
|
-
// Negative — guard against accidental revert to the muddy pair.
|
|
85
|
-
expect(TAG_CSS).not.toMatch(
|
|
86
|
-
/:scope\[variant="warning"\]\s*\{[^}]*--tag-bg-default:\s*var\(--a-warning-bg\)/s
|
|
81
|
+
/:scope\[variant="warning"\]\s*\{[^}]*--tag-bg-default:\s*var\(--a-warning-bg\)[^}]*--tag-fg-default:\s*var\(--a-warning-fg\)/s
|
|
87
82
|
);
|
|
88
83
|
});
|
|
89
84
|
});
|
|
@@ -118,3 +113,32 @@ describe('tag-ui — neutral default + explicit solid stamp', () => {
|
|
|
118
113
|
);
|
|
119
114
|
});
|
|
120
115
|
});
|
|
116
|
+
|
|
117
|
+
describe('tag-ui — [tone="outline"] strips fill + colors border', () => {
|
|
118
|
+
it('[tone="outline"] base rule sets bg to transparent', () => {
|
|
119
|
+
expect(TAG_CSS).toMatch(
|
|
120
|
+
/:scope\[tone="outline"\]\s*\{[^}]*--tag-bg-default:\s*transparent/s
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it.each([
|
|
125
|
+
['info'],
|
|
126
|
+
['success'],
|
|
127
|
+
['warning'],
|
|
128
|
+
['danger'],
|
|
129
|
+
])('[tone="outline"][variant="%s"] colors fg + border per family', (family) => {
|
|
130
|
+
const block = new RegExp(
|
|
131
|
+
`:scope\\[tone="outline"\\]\\[variant="${family}"\\]\\s*\\{[^}]*` +
|
|
132
|
+
`--tag-fg-default:\\s*var\\(--a-${family}-text\\)[^}]*` +
|
|
133
|
+
`--tag-border-default:\\s*var\\(--a-${family}-border\\)`,
|
|
134
|
+
's'
|
|
135
|
+
);
|
|
136
|
+
expect(TAG_CSS).toMatch(block);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('[tone="outline"] on neutral uses --a-fg-muted text + --a-border ring', () => {
|
|
140
|
+
expect(TAG_CSS).toMatch(
|
|
141
|
+
/:scope\[tone="outline"\]:not\(\[variant="info"\]\)[\s\S]*?--tag-fg-default:\s*var\(--a-fg-muted\)[\s\S]*?--tag-border-default:\s*var\(--a-border\)/
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
});
|
package/components/tag/tag.yaml
CHANGED
|
@@ -53,18 +53,24 @@ props:
|
|
|
53
53
|
- danger
|
|
54
54
|
tone:
|
|
55
55
|
description: |
|
|
56
|
-
Fill style — orthogonal to [variant].
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
Fill style — orthogonal to [variant]. Three values:
|
|
57
|
+
- `solid` (default for family variants) — saturated bg + on-strong
|
|
58
|
+
(near-white) text. The chip IS the state.
|
|
59
|
+
- `muted` — tinted bg with scheme-paired text. Matches <badge-ui>'s
|
|
60
|
+
default look. Use on metadata chips in dense lists where the
|
|
61
|
+
saturated default would compete for attention.
|
|
62
|
+
- `outline` — transparent bg + family-colored border + family-colored
|
|
63
|
+
text. The lightest visual weight; good in dense data tables or
|
|
64
|
+
faceted filter rows where multiple chips would otherwise compete.
|
|
65
|
+
The `default` variant (no family) stays quiet chrome regardless of
|
|
66
|
+
tone unless `tone="solid"` is set explicitly (high-contrast neutral
|
|
67
|
+
inverse), or `tone="outline"` (fg-muted text + subtle border).
|
|
63
68
|
type: string
|
|
64
69
|
default: solid
|
|
65
70
|
enum:
|
|
66
71
|
- solid
|
|
67
72
|
- muted
|
|
73
|
+
- outline
|
|
68
74
|
events:
|
|
69
75
|
remove:
|
|
70
76
|
description: Fired when the dismiss button is activated.
|
|
@@ -75,7 +75,14 @@
|
|
|
75
75
|
"FeedItem",
|
|
76
76
|
"Stepper"
|
|
77
77
|
],
|
|
78
|
-
"slots": {
|
|
78
|
+
"slots": {
|
|
79
|
+
"description": {
|
|
80
|
+
"description": "Override slot for rich description markup (inline links, code spans, embedded badges). Renders beneath the primary text at body-subtle typography."
|
|
81
|
+
},
|
|
82
|
+
"icon": {
|
|
83
|
+
"description": "Override the [icon] glyph with a custom slotted element. Use for branded icons, avatars, or non-Phosphor sources. Mutually exclusive with the [icon] attribute."
|
|
84
|
+
}
|
|
85
|
+
},
|
|
79
86
|
"states": [],
|
|
80
87
|
"status": "stable",
|
|
81
88
|
"synonyms": {
|
|
@@ -47,6 +47,18 @@ props:
|
|
|
47
47
|
type: boolean
|
|
48
48
|
default: false
|
|
49
49
|
|
|
50
|
+
slots:
|
|
51
|
+
icon:
|
|
52
|
+
description: >-
|
|
53
|
+
Override the [icon] glyph with a custom slotted element. Use for
|
|
54
|
+
branded icons, avatars, or non-Phosphor sources. Mutually exclusive
|
|
55
|
+
with the [icon] attribute.
|
|
56
|
+
description:
|
|
57
|
+
description: >-
|
|
58
|
+
Override slot for rich description markup (inline links, code spans,
|
|
59
|
+
embedded badges). Renders beneath the primary text at body-subtle
|
|
60
|
+
typography.
|
|
61
|
+
|
|
50
62
|
keywords:
|
|
51
63
|
- timeline-item
|
|
52
64
|
- timeline-row
|