@adia-ai/web-components 0.6.35 → 0.6.37
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 +56 -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/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/combobox/combobox.css +12 -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 +3 -1
- package/components/date-range-picker/date-range-picker.css +4 -1
- 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.css +7 -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.class.js +2 -0
- package/components/feed/feed.class.js +13 -5
- package/components/feed/feed.css +14 -0
- package/components/index.js +9 -0
- package/components/input/input.css +15 -1
- package/components/input/input.test.js +40 -0
- package/components/integration-card/integration-card.class.js +9 -0
- package/components/integration-card/integration-card.test.js +4 -3
- package/components/nav-group/nav-group.css +7 -1
- 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/search/search.class.js +2 -0
- package/components/segmented/segmented.class.js +5 -1
- package/components/select/select.class.js +4 -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 +16 -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 +28 -6
- package/components/table/table.class.js +29 -6
- package/components/table/table.css +31 -4
- package/components/table-toolbar/table-toolbar.class.js +4 -1
- package/components/tag/tag.a2ui.json +10 -0
- package/components/tag/tag.class.js +8 -1
- package/components/tag/tag.css +108 -20
- package/components/tag/tag.d.ts +14 -0
- package/components/tag/tag.test.js +99 -1
- package/components/tag/tag.yaml +20 -0
- package/components/tags-input/tags-input.class.js +10 -3
- package/components/tags-input/tags-input.css +12 -3
- package/components/textarea/textarea.css +10 -1
- 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/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/core/provider.js +19 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +101 -89
- package/package.json +1 -1
- package/styles/colors/semantics.css +11 -2
- package/styles/components.css +9 -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.
|
|
@@ -94,12 +116,12 @@ tokens:
|
|
|
94
116
|
description: Full track height (scales via universal [size] attribute)
|
|
95
117
|
a2ui:
|
|
96
118
|
rules:
|
|
97
|
-
- rule: '
|
|
119
|
+
- rule: 'Default mode is single-handle. Form-associated; emits numeric change events with detail.value.'
|
|
98
120
|
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.'
|
|
121
|
+
- 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.'
|
|
122
|
+
reason: 'Dual-thumb range selection lives on slider-ui[dual], not on a separate primitive.'
|
|
123
|
+
- 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.'
|
|
124
|
+
reason: 'Standard slider knobs + dual-mode constraint.'
|
|
103
125
|
anti_patterns: []
|
|
104
126
|
examples:
|
|
105
127
|
- name: slider-range
|
|
@@ -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
|
|
|
@@ -290,6 +290,7 @@ export class UITableToolbar extends UIElement {
|
|
|
290
290
|
// name renders as literal text before the icon registry resolves.
|
|
291
291
|
const search = document.createElement('search-ui');
|
|
292
292
|
search.setAttribute('data-search', '');
|
|
293
|
+
search.setAttribute('size', 'sm'); // match toolbar buttons + badge + filter chips
|
|
293
294
|
search.setAttribute('placeholder', this.placeholder);
|
|
294
295
|
search.setAttribute('debounce', String(SEARCH_DEBOUNCE));
|
|
295
296
|
search.addEventListener('search', this.#onSearch);
|
|
@@ -434,7 +435,9 @@ export class UITableToolbar extends UIElement {
|
|
|
434
435
|
document.body.appendChild(panel);
|
|
435
436
|
|
|
436
437
|
try { panel.showPopover(); } catch { /* popover API unavailable */ }
|
|
437
|
-
|
|
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 });
|
|
438
441
|
|
|
439
442
|
this.#activePopover = { kind, btn, panel, cleanup };
|
|
440
443
|
|
|
@@ -44,6 +44,16 @@
|
|
|
44
44
|
"description": "Tag label. Renderer routes this to the `text` attribute, rendered via CSS attr(text) on ::after.",
|
|
45
45
|
"$ref": "common_types.json#/$defs/DynamicString"
|
|
46
46
|
},
|
|
47
|
+
"tone": {
|
|
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
|
+
"type": "string",
|
|
50
|
+
"enum": [
|
|
51
|
+
"solid",
|
|
52
|
+
"muted",
|
|
53
|
+
"outline"
|
|
54
|
+
],
|
|
55
|
+
"default": "solid"
|
|
56
|
+
},
|
|
47
57
|
"variant": {
|
|
48
58
|
"description": "Semantic variant — `default | info | success | warning | danger`.",
|
|
49
59
|
"type": "string",
|
|
@@ -42,13 +42,20 @@ export class UITag extends UIElement {
|
|
|
42
42
|
// `tag.textContent = …` working natively; the prop declaration
|
|
43
43
|
// broke that path. v0.5.x §327.
|
|
44
44
|
variant: { type: String, default: 'default', reflect: true },
|
|
45
|
+
// `tone` is read-only via attribute selectors (CSS), not by the
|
|
46
|
+
// class — declared in yaml for documentation + a2ui hint but NOT
|
|
47
|
+
// in `static properties` so the runtime doesn't reflect an
|
|
48
|
+
// ever-present `tone="solid"` attribute onto every <tag-ui>
|
|
49
|
+
// (which would defeat the variant-default cascade and force CSS
|
|
50
|
+
// selectors to compete with reflected defaults). Author sets
|
|
51
|
+
// `[tone]` directly when opting in.
|
|
45
52
|
size: { type: String, default: 'md', reflect: true },
|
|
46
53
|
removable: { type: Boolean, default: false, reflect: true },
|
|
47
54
|
disabled: { type: Boolean, default: false, reflect: true },
|
|
48
55
|
};
|
|
49
56
|
|
|
50
57
|
static parts = {
|
|
51
|
-
dismiss: '<button slot="dismiss" type="button" aria-label="Remove"><icon-ui name="x"></icon-ui></button>',
|
|
58
|
+
dismiss: '<button slot="dismiss" type="button" aria-label="Remove"><icon-ui name="x" weight="bold"></icon-ui></button>',
|
|
52
59
|
};
|
|
53
60
|
|
|
54
61
|
static template = () => null;
|
package/components/tag/tag.css
CHANGED
|
@@ -27,11 +27,21 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
27
27
|
--tag-duration-default: var(--a-duration-fast);
|
|
28
28
|
--tag-easing-default: var(--a-easing);
|
|
29
29
|
|
|
30
|
-
/* ── Dismiss ──
|
|
30
|
+
/* ── Dismiss ──
|
|
31
|
+
Rest = inherit the host's text color at reduced opacity (so the X
|
|
32
|
+
reads as quieter chrome than the label, but tracks every variant
|
|
33
|
+
— info/success/danger get a near-white X on saturated bg; warning
|
|
34
|
+
gets a dark-brown X on bright amber; default gets a fg-muted X on
|
|
35
|
+
quiet chrome). Hover restores full opacity + drops a translucent
|
|
36
|
+
overlay in the same color family for the affordance.
|
|
37
|
+
Pre-v0.6.36 used `--a-fg-muted` / `--a-fg` directly, which gave
|
|
38
|
+
a neutral-grey X on saturated solid pills — the X disappeared
|
|
39
|
+
against the variant bg. */
|
|
31
40
|
--tag-dismiss-radius-default: var(--a-radius-full);
|
|
32
|
-
--tag-dismiss-fg-default:
|
|
33
|
-
--tag-dismiss-
|
|
34
|
-
--tag-dismiss-
|
|
41
|
+
--tag-dismiss-fg-default: currentColor;
|
|
42
|
+
--tag-dismiss-opacity-default: 0.85;
|
|
43
|
+
--tag-dismiss-opacity-hover-default: 1;
|
|
44
|
+
--tag-dismiss-bg-hover-default: color-mix(in oklch, currentColor 18%, transparent);
|
|
35
45
|
text-align: start; /* §text-align-reset — blocks inheritance from centered ancestors */
|
|
36
46
|
}
|
|
37
47
|
|
|
@@ -64,37 +74,105 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
64
74
|
content: attr(text);
|
|
65
75
|
}
|
|
66
76
|
|
|
67
|
-
/* ── Variants ──
|
|
77
|
+
/* ── Variants ──
|
|
78
|
+
Default tone is `solid` for the four family variants:
|
|
79
|
+
`--a-{family}-bg` (saturated surface) + `--a-{family}-fg` (on-strong
|
|
80
|
+
text, near-white in both schemes by design). Reads as a status pill
|
|
81
|
+
where the chip IS the state. Opt out per-tag via [tone="muted"] for
|
|
82
|
+
metadata-chip surfaces in dense lists. */
|
|
68
83
|
:scope[variant="info"] {
|
|
69
|
-
--tag-bg-default: var(--a-info-
|
|
70
|
-
--tag-fg-default: var(--a-info-
|
|
84
|
+
--tag-bg-default: var(--a-info-bg);
|
|
85
|
+
--tag-fg-default: var(--a-info-fg);
|
|
71
86
|
}
|
|
72
87
|
|
|
73
88
|
:scope[variant="success"] {
|
|
74
|
-
--tag-bg-default: var(--a-success-
|
|
75
|
-
--tag-fg-default: var(--a-success-
|
|
89
|
+
--tag-bg-default: var(--a-success-bg);
|
|
90
|
+
--tag-fg-default: var(--a-success-fg);
|
|
76
91
|
}
|
|
77
92
|
|
|
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. */
|
|
78
97
|
:scope[variant="warning"] {
|
|
79
|
-
--tag-bg-default: var(--a-warning-
|
|
80
|
-
--tag-fg-default: var(--a-warning-
|
|
98
|
+
--tag-bg-default: var(--a-warning-bg);
|
|
99
|
+
--tag-fg-default: var(--a-warning-fg);
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
:scope[variant="danger"] {
|
|
84
|
-
--tag-bg-default: var(--a-danger-
|
|
85
|
-
--tag-fg-default: var(--a-danger-
|
|
103
|
+
--tag-bg-default: var(--a-danger-bg);
|
|
104
|
+
--tag-fg-default: var(--a-danger-fg);
|
|
86
105
|
}
|
|
87
106
|
|
|
88
|
-
/* `default`
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
base; the explicit selector lets `<tag-ui variant="default">`
|
|
92
|
-
render identically without falling through to base. */
|
|
107
|
+
/* `default` (no family) stays as quiet chrome — a stark fg/bg-inverse
|
|
108
|
+
would be too loud for the no-variant case. Opt in to the inverse
|
|
109
|
+
stamp via `[tone="solid"]` when you want a high-contrast neutral pill. */
|
|
93
110
|
:scope[variant="default"] {
|
|
94
111
|
--tag-bg-default: var(--a-bg-muted);
|
|
95
112
|
--tag-fg-default: var(--a-fg);
|
|
96
113
|
}
|
|
97
114
|
|
|
115
|
+
/* ── Tone modifier — orthogonal to [variant] ──
|
|
116
|
+
`[tone="muted"]` opts each family variant OUT of the solid default
|
|
117
|
+
into the canonical muted pair: `--a-{family}-muted` (tinted surface)
|
|
118
|
+
+ `--a-{family}-text` (scheme-flipping text). Same shape <badge-ui>
|
|
119
|
+
uses as its default. Use on metadata chips in dense lists where the
|
|
120
|
+
saturated default would compete for attention. */
|
|
121
|
+
:scope[tone="muted"][variant="info"] {
|
|
122
|
+
--tag-bg-default: var(--a-info-muted);
|
|
123
|
+
--tag-fg-default: var(--a-info-text);
|
|
124
|
+
}
|
|
125
|
+
:scope[tone="muted"][variant="success"] {
|
|
126
|
+
--tag-bg-default: var(--a-success-muted);
|
|
127
|
+
--tag-fg-default: var(--a-success-text);
|
|
128
|
+
}
|
|
129
|
+
:scope[tone="muted"][variant="warning"] {
|
|
130
|
+
--tag-bg-default: var(--a-warning-muted);
|
|
131
|
+
--tag-fg-default: var(--a-warning-text);
|
|
132
|
+
}
|
|
133
|
+
:scope[tone="muted"][variant="danger"] {
|
|
134
|
+
--tag-bg-default: var(--a-danger-muted);
|
|
135
|
+
--tag-fg-default: var(--a-danger-text);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* `[tone="solid"]` on the neutral default (or no variant) inverts the
|
|
139
|
+
chrome — solid fg-color bg with bg-color text. High-contrast neutral
|
|
140
|
+
stamp. Explicit opt-in; the variant-less default stays quiet chrome. */
|
|
141
|
+
:scope[tone="solid"]:not([variant="info"]):not([variant="success"]):not([variant="warning"]):not([variant="danger"]) {
|
|
142
|
+
--tag-bg-default: var(--a-fg);
|
|
143
|
+
--tag-fg-default: var(--a-bg);
|
|
144
|
+
}
|
|
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
|
+
|
|
98
176
|
/* Size handled by universal [size] attribute system. */
|
|
99
177
|
|
|
100
178
|
/* hover rule moved outside @scope — see Safari 17.x bug note at top. */
|
|
@@ -105,6 +183,15 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
105
183
|
box-shadow: var(--tag-focus-ring, var(--tag-focus-ring-default));
|
|
106
184
|
}
|
|
107
185
|
|
|
186
|
+
/* ── Slotted icons (leading) ──
|
|
187
|
+
Icons placed inside the tag (e.g. `<icon-ui name="check">`) inherit
|
|
188
|
+
the host's text color so legend / status chips read as a single
|
|
189
|
+
color-coded unit. Mirrors `<badge-ui>`'s convention. */
|
|
190
|
+
:scope > icon-ui {
|
|
191
|
+
color: currentColor;
|
|
192
|
+
flex-shrink: 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
108
195
|
/* ── Dismiss button ── */
|
|
109
196
|
[slot="dismiss"] {
|
|
110
197
|
display: inline-flex;
|
|
@@ -118,13 +205,14 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
118
205
|
cursor: pointer;
|
|
119
206
|
border-radius: var(--tag-dismiss-radius, var(--tag-dismiss-radius-default));
|
|
120
207
|
color: var(--tag-dismiss-fg, var(--tag-dismiss-fg-default));
|
|
208
|
+
opacity: var(--tag-dismiss-opacity, var(--tag-dismiss-opacity-default));
|
|
121
209
|
--a-icon-size: 0.875rem;
|
|
122
210
|
order: 1; /* push dismiss to end so layout reads [text] [×] */
|
|
123
|
-
transition:
|
|
211
|
+
transition: opacity var(--tag-duration, var(--tag-duration-default)) var(--tag-easing, var(--tag-easing-default)), background var(--tag-duration, var(--tag-duration-default)) var(--tag-easing, var(--tag-easing-default));
|
|
124
212
|
}
|
|
125
213
|
|
|
126
214
|
[slot="dismiss"]:hover {
|
|
127
|
-
|
|
215
|
+
opacity: var(--tag-dismiss-opacity-hover, var(--tag-dismiss-opacity-hover-default));
|
|
128
216
|
background: var(--tag-dismiss-bg-hover, var(--tag-dismiss-bg-hover-default));
|
|
129
217
|
}
|
|
130
218
|
|
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
|
|
|
@@ -41,6 +41,104 @@ describe('tag-ui — content: attr(text) gating', () => {
|
|
|
41
41
|
// [icon] + [dismiss] slot rendering and for proper :scope[text]
|
|
42
42
|
// gap rendering when text IS set.
|
|
43
43
|
expect(TAG_CSS).toMatch(/:scope\s*\{[^}]*display:\s*inline-flex/);
|
|
44
|
-
|
|
44
|
+
// Post OD-5 sweep: tokens read via `var(--prop, var(--prop-default))`
|
|
45
|
+
// chain so consumer-named overrides AND --a-* surface overrides both work.
|
|
46
|
+
expect(TAG_CSS).toMatch(/:scope\s*\{[^}]*gap:\s*var\(--tag-gap,\s*var\(--tag-gap-default\)\)/);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ── Tone defaults (solid by default for families; muted as opt-out) ──
|
|
51
|
+
//
|
|
52
|
+
// Default tone is `solid` for family variants — the base `:scope[variant=…]`
|
|
53
|
+
// rules use the saturated-fill pair `--a-{family}-bg` + `--a-{family}-fg`
|
|
54
|
+
// (the chip IS the state). `[tone="muted"]` opts back into the canonical
|
|
55
|
+
// muted pair `--a-{family}-muted` + `--a-{family}-text` (same look as
|
|
56
|
+
// <badge-ui>'s default). The `default` (no-family) variant stays quiet
|
|
57
|
+
// chrome unless `[tone="solid"]` is set explicitly (then it inverts
|
|
58
|
+
// to fg/bg for a high-contrast neutral stamp).
|
|
59
|
+
|
|
60
|
+
describe('tag-ui — variant defaults to solid fill', () => {
|
|
61
|
+
it.each([
|
|
62
|
+
['info'],
|
|
63
|
+
['success'],
|
|
64
|
+
['danger'],
|
|
65
|
+
])('[variant="%s"] base rule uses --a-{family}-bg + --a-{family}-fg', (family) => {
|
|
66
|
+
const block = new RegExp(
|
|
67
|
+
`:scope\\[variant="${family}"\\]\\s*\\{[^}]*` +
|
|
68
|
+
`--tag-bg-default:\\s*var\\(--a-${family}-bg\\)[^}]*` +
|
|
69
|
+
`--tag-fg-default:\\s*var\\(--a-${family}-fg\\)`,
|
|
70
|
+
's'
|
|
71
|
+
);
|
|
72
|
+
expect(TAG_CSS).toMatch(block);
|
|
73
|
+
});
|
|
74
|
+
|
|
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.
|
|
80
|
+
expect(TAG_CSS).toMatch(
|
|
81
|
+
/:scope\[variant="warning"\]\s*\{[^}]*--tag-bg-default:\s*var\(--a-warning-bg\)[^}]*--tag-fg-default:\s*var\(--a-warning-fg\)/s
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('tag-ui — [tone="muted"] opts out to canonical muted pair', () => {
|
|
87
|
+
it.each([
|
|
88
|
+
['info'],
|
|
89
|
+
['success'],
|
|
90
|
+
['warning'],
|
|
91
|
+
['danger'],
|
|
92
|
+
])('[tone="muted"][variant="%s"] uses --a-{family}-muted + --a-{family}-text', (family) => {
|
|
93
|
+
const block = new RegExp(
|
|
94
|
+
`:scope\\[tone="muted"\\]\\[variant="${family}"\\]\\s*\\{[^}]*` +
|
|
95
|
+
`--tag-bg-default:\\s*var\\(--a-${family}-muted\\)[^}]*` +
|
|
96
|
+
`--tag-fg-default:\\s*var\\(--a-${family}-text\\)`,
|
|
97
|
+
's'
|
|
98
|
+
);
|
|
99
|
+
expect(TAG_CSS).toMatch(block);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('tag-ui — neutral default + explicit solid stamp', () => {
|
|
104
|
+
it('[variant="default"] stays quiet chrome (--a-bg-muted + --a-fg)', () => {
|
|
105
|
+
expect(TAG_CSS).toMatch(
|
|
106
|
+
/:scope\[variant="default"\]\s*\{[^}]*--tag-bg-default:\s*var\(--a-bg-muted\)[^}]*--tag-fg-default:\s*var\(--a-fg\)/s
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('[tone="solid"] without a family inverts to fg/bg high-contrast stamp', () => {
|
|
111
|
+
expect(TAG_CSS).toMatch(
|
|
112
|
+
/:scope\[tone="solid"\]:not\(\[variant="info"\]\)[\s\S]*?--tag-bg-default:\s*var\(--a-fg\)[\s\S]*?--tag-fg-default:\s*var\(--a-bg\)/
|
|
113
|
+
);
|
|
114
|
+
});
|
|
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
|
+
);
|
|
45
143
|
});
|
|
46
144
|
});
|