@ponchia/ui 0.6.7 → 0.6.9
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 +129 -4
- package/README.md +4 -4
- package/annotations/index.d.ts.map +1 -1
- package/annotations/index.js +26 -9
- package/behaviors/carousel.d.ts.map +1 -1
- package/behaviors/carousel.js +145 -49
- package/behaviors/combobox.d.ts.map +1 -1
- package/behaviors/combobox.js +220 -92
- package/behaviors/command.d.ts.map +1 -1
- package/behaviors/command.js +74 -14
- package/behaviors/connectors.d.ts.map +1 -1
- package/behaviors/connectors.js +131 -32
- package/behaviors/crosshair.d.ts.map +1 -1
- package/behaviors/crosshair.js +47 -1
- package/behaviors/dialog.d.ts.map +1 -1
- package/behaviors/dialog.js +24 -9
- package/behaviors/disclosure.d.ts.map +1 -1
- package/behaviors/disclosure.js +33 -3
- package/behaviors/dismissible.d.ts.map +1 -1
- package/behaviors/dismissible.js +3 -2
- package/behaviors/forms.d.ts.map +1 -1
- package/behaviors/forms.js +211 -140
- package/behaviors/glyph.d.ts.map +1 -1
- package/behaviors/glyph.js +172 -132
- package/behaviors/inert.d.ts +1 -1
- package/behaviors/inert.d.ts.map +1 -1
- package/behaviors/inert.js +4 -3
- package/behaviors/internal.d.ts.map +1 -1
- package/behaviors/internal.js +4 -3
- package/behaviors/legend.d.ts +0 -5
- package/behaviors/legend.d.ts.map +1 -1
- package/behaviors/legend.js +45 -13
- package/behaviors/menu.d.ts.map +1 -1
- package/behaviors/menu.js +13 -8
- package/behaviors/modal.d.ts.map +1 -1
- package/behaviors/modal.js +77 -19
- package/behaviors/popover.d.ts +4 -3
- package/behaviors/popover.d.ts.map +1 -1
- package/behaviors/popover.js +94 -14
- package/behaviors/sources.d.ts.map +1 -1
- package/behaviors/sources.js +14 -2
- package/behaviors/splitter.d.ts.map +1 -1
- package/behaviors/splitter.js +149 -110
- package/behaviors/spotlight.d.ts.map +1 -1
- package/behaviors/spotlight.js +28 -2
- package/behaviors/table.d.ts +1 -1
- package/behaviors/table.d.ts.map +1 -1
- package/behaviors/table.js +108 -17
- package/behaviors/tabs.d.ts.map +1 -1
- package/behaviors/tabs.js +84 -20
- package/behaviors/theme.d.ts.map +1 -1
- package/behaviors/theme.js +25 -5
- package/behaviors/toast.js +5 -5
- package/classes/index.d.ts +15 -2
- package/classes/index.js +48 -35
- package/connectors/index.d.ts +41 -8
- package/connectors/index.d.ts.map +1 -1
- package/connectors/index.js +74 -19
- package/css/annotations.css +12 -0
- package/css/app.css +3 -4
- package/css/base.css +1 -1
- package/css/content.css +3 -3
- package/css/crosshair.css +27 -2
- package/css/disclosure.css +3 -3
- package/css/dots.css +4 -4
- package/css/feedback.css +8 -37
- package/css/forms.css +9 -12
- package/css/legend.css +1 -1
- package/css/marks.css +1 -1
- package/css/motion.css +6 -6
- package/css/navigation.css +12 -0
- package/css/overlay.css +5 -7
- package/css/primitives.css +14 -16
- package/css/sidenote.css +2 -2
- package/css/table.css +2 -2
- package/css/tokens.css +16 -0
- package/dist/bronto.css +1 -1
- package/dist/css/analytical.css +1 -1
- package/dist/css/annotations.css +1 -1
- package/dist/css/crosshair.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/navigation.css +1 -1
- package/dist/css/report-kit.css +1 -1
- package/dist/css/tokens.css +1 -1
- package/docs/adr/0001-color-system.md +3 -2
- package/docs/annotations.md +21 -1
- package/docs/architecture.md +74 -13
- package/docs/command.md +4 -1
- package/docs/connectors.md +16 -0
- package/docs/crosshair.md +1 -1
- package/docs/dots.md +4 -1
- package/docs/glyphs.md +11 -0
- package/docs/interop/react-flow.md +89 -0
- package/docs/migrations/0.2-to-0.3.md +1 -1
- package/docs/package-contract.md +7 -5
- package/docs/reporting.md +23 -12
- package/docs/stability.md +85 -9
- package/docs/theming.md +2 -2
- package/docs/usage.md +16 -2
- package/docs/vega.md +4 -4
- package/glyphs/glyphs.js +43 -33
- package/llms.txt +19 -8
- package/package.json +23 -4
- package/schemas/report-claims.v1.schema.json +1 -1
- package/svelte/index.d.ts +71 -45
- package/svelte/index.d.ts.map +1 -1
- package/svelte/index.js +29 -2
- package/tokens/index.js +2 -2
- package/vue/index.d.ts +42 -5
- package/vue/index.d.ts.map +1 -1
- package/vue/index.js +32 -1
package/connectors/index.js
CHANGED
|
@@ -43,46 +43,95 @@
|
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
45
|
// Shared scalar/geometry primitives. Exported so the annotations layer composes
|
|
46
|
-
// on the
|
|
47
|
-
//
|
|
48
|
-
// builders below.
|
|
46
|
+
// on the same kernel as the connector path builders. Low-level helpers; the
|
|
47
|
+
// documented API is the path builders below.
|
|
49
48
|
export const PRECISION = 1000;
|
|
50
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a numeric option with an optional fallback.
|
|
52
|
+
* @param {string} name
|
|
53
|
+
* @param {number | null | undefined} value
|
|
54
|
+
* @param {number | null | undefined} [fallback]
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
51
57
|
export function finite(name, value, fallback) {
|
|
52
58
|
const v = value ?? fallback;
|
|
53
59
|
if (!Number.isFinite(v)) throw new TypeError(`${name} must be a finite number`);
|
|
54
60
|
return v;
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a non-negative numeric option with an optional fallback.
|
|
65
|
+
* @param {string} name
|
|
66
|
+
* @param {number | null | undefined} value
|
|
67
|
+
* @param {number | null | undefined} [fallback]
|
|
68
|
+
* @returns {number}
|
|
69
|
+
*/
|
|
57
70
|
export function dimension(name, value, fallback) {
|
|
58
71
|
const v = finite(name, value, fallback);
|
|
59
72
|
if (v < 0) throw new RangeError(`${name} must be greater than or equal to 0`);
|
|
60
73
|
return v;
|
|
61
74
|
}
|
|
62
75
|
|
|
63
|
-
// Round to PRECISION, normalising -0 → 0, and return the NUMBER
|
|
64
|
-
//
|
|
65
|
-
|
|
76
|
+
// Round to PRECISION, normalising -0 → 0, and return the NUMBER. `fmt`
|
|
77
|
+
// stringifies the numeric core; annotations use the same rounded coordinates.
|
|
78
|
+
/**
|
|
79
|
+
* @param {number} value
|
|
80
|
+
* @returns {number}
|
|
81
|
+
*/
|
|
66
82
|
export function roundNumber(value) {
|
|
67
83
|
const rounded = Math.round((Object.is(value, -0) ? 0 : value) * PRECISION) / PRECISION;
|
|
68
84
|
return Object.is(rounded, -0) ? 0 : rounded;
|
|
69
85
|
}
|
|
70
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @param {number} value
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
71
91
|
export function fmt(value) {
|
|
72
92
|
return String(roundNumber(value));
|
|
73
93
|
}
|
|
74
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @param {number} x
|
|
97
|
+
* @param {number} y
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
75
100
|
export function point(x, y) {
|
|
76
101
|
return `${fmt(x)},${fmt(y)}`;
|
|
77
102
|
}
|
|
78
103
|
|
|
79
|
-
// Guarded form
|
|
80
|
-
|
|
104
|
+
// Guarded form: an inverted range resolves to `min`.
|
|
105
|
+
/**
|
|
106
|
+
* @param {number} value
|
|
107
|
+
* @param {number} min
|
|
108
|
+
* @param {number} max
|
|
109
|
+
* @returns {number}
|
|
110
|
+
*/
|
|
81
111
|
export function clamp(value, min, max) {
|
|
82
112
|
if (max < min) return min;
|
|
83
113
|
return Math.min(max, Math.max(min, value));
|
|
84
114
|
}
|
|
85
115
|
|
|
116
|
+
function connectorShape(value) {
|
|
117
|
+
const shape = value ?? 'straight';
|
|
118
|
+
if (shape === 'straight' || shape === 'elbow' || shape === 'curve') return shape;
|
|
119
|
+
throw new TypeError('shape must be "straight", "elbow" or "curve"');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sideValue(value) {
|
|
123
|
+
const side = value ?? 'center';
|
|
124
|
+
if (
|
|
125
|
+
side === 'top' ||
|
|
126
|
+
side === 'right' ||
|
|
127
|
+
side === 'bottom' ||
|
|
128
|
+
side === 'left' ||
|
|
129
|
+
side === 'center'
|
|
130
|
+
)
|
|
131
|
+
return side;
|
|
132
|
+
throw new TypeError('side must be "top", "right", "bottom", "left" or "center"');
|
|
133
|
+
}
|
|
134
|
+
|
|
86
135
|
/**
|
|
87
136
|
* A point on a rect's edge (or centre). `rect` is `{ x, y, width, height }`.
|
|
88
137
|
* @param {Rect} rect
|
|
@@ -90,11 +139,11 @@ export function clamp(value, min, max) {
|
|
|
90
139
|
* @returns {Point}
|
|
91
140
|
*/
|
|
92
141
|
export function anchorPoint(rect, side = 'center') {
|
|
93
|
-
const x = finite('rect.x', rect?.x
|
|
94
|
-
const y = finite('rect.y', rect?.y
|
|
95
|
-
const w = dimension('rect.width', rect?.width
|
|
96
|
-
const h = dimension('rect.height', rect?.height
|
|
97
|
-
switch (side) {
|
|
142
|
+
const x = finite('rect.x', rect?.x);
|
|
143
|
+
const y = finite('rect.y', rect?.y);
|
|
144
|
+
const w = dimension('rect.width', rect?.width);
|
|
145
|
+
const h = dimension('rect.height', rect?.height);
|
|
146
|
+
switch (sideValue(side)) {
|
|
98
147
|
case 'top':
|
|
99
148
|
return { x: x + w / 2, y };
|
|
100
149
|
case 'bottom':
|
|
@@ -185,7 +234,8 @@ export function curvePath(from, to, opts = {}) {
|
|
|
185
234
|
* @returns {string}
|
|
186
235
|
*/
|
|
187
236
|
export function connectorPath(opts = {}) {
|
|
188
|
-
const { from, to
|
|
237
|
+
const { from, to } = opts;
|
|
238
|
+
const shape = connectorShape(opts.shape);
|
|
189
239
|
if (shape === 'elbow') return elbowPath(from, to, opts);
|
|
190
240
|
if (shape === 'curve') return curvePath(from, to, opts);
|
|
191
241
|
return straightPath(from, to);
|
|
@@ -231,8 +281,8 @@ export function dotMark(p, radius = 3) {
|
|
|
231
281
|
|
|
232
282
|
/**
|
|
233
283
|
* An axis-aligned rectangle path from its corners (callers derive the corners
|
|
234
|
-
* from a centre or a top-left as they need). Shared by
|
|
235
|
-
*
|
|
284
|
+
* from a centre or a top-left as they need). Shared by annotation rect/band
|
|
285
|
+
* and evidence-marker subjects.
|
|
236
286
|
* @param {number} left
|
|
237
287
|
* @param {number} top
|
|
238
288
|
* @param {number} right
|
|
@@ -270,7 +320,8 @@ export function autoSides(fromRect, toRect) {
|
|
|
270
320
|
* @returns {number}
|
|
271
321
|
*/
|
|
272
322
|
export function endTangentAngle(from, to, shape = 'straight') {
|
|
273
|
-
|
|
323
|
+
const resolved = connectorShape(shape);
|
|
324
|
+
if (resolved === 'straight') return angleBetween(from, to);
|
|
274
325
|
const dx = finite('to.x', to?.x) - finite('from.x', from?.x);
|
|
275
326
|
const dy = finite('to.y', to?.y) - finite('from.y', from?.y);
|
|
276
327
|
if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? 0 : Math.PI;
|
|
@@ -285,10 +336,14 @@ export function endTangentAngle(from, to, shape = 'straight') {
|
|
|
285
336
|
* @returns {ConnectRectsResult}
|
|
286
337
|
*/
|
|
287
338
|
export function connectRects(opts = {}) {
|
|
288
|
-
const { fromRect, toRect,
|
|
339
|
+
const { fromRect, toRect, curvature, mid } = opts;
|
|
340
|
+
const shape = connectorShape(opts.shape);
|
|
289
341
|
// Honor each side override independently; auto-pick whichever is unset.
|
|
290
342
|
const auto = autoSides(fromRect, toRect);
|
|
291
|
-
const sides = {
|
|
343
|
+
const sides = {
|
|
344
|
+
from: opts.fromSide == null ? auto.from : sideValue(opts.fromSide),
|
|
345
|
+
to: opts.toSide == null ? auto.to : sideValue(opts.toSide),
|
|
346
|
+
};
|
|
292
347
|
const from = anchorPoint(fromRect, sides.from);
|
|
293
348
|
const to = anchorPoint(toRect, sides.to);
|
|
294
349
|
const d = connectorPath({ from, to, shape, curvature, mid });
|
package/css/annotations.css
CHANGED
|
@@ -278,8 +278,20 @@
|
|
|
278
278
|
animation: none !important;
|
|
279
279
|
opacity: 1;
|
|
280
280
|
stroke-dashoffset: 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.ui-annotation__subject,
|
|
284
|
+
.ui-annotation__connector,
|
|
285
|
+
.ui-annotation__note-line,
|
|
286
|
+
.ui-annotation__badge {
|
|
281
287
|
transform: none;
|
|
282
288
|
}
|
|
289
|
+
|
|
290
|
+
.ui-annotation--draw .ui-annotation__connector,
|
|
291
|
+
.ui-annotation--draw .ui-annotation__note-line {
|
|
292
|
+
stroke-dasharray: none;
|
|
293
|
+
stroke-dashoffset: 0;
|
|
294
|
+
}
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
@media (forced-colors: active) {
|
package/css/app.css
CHANGED
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
near-imperceptible tint/dot opacity delta survives, and current-page becomes
|
|
124
124
|
invisible to HCM users. Re-assert "current" on channels HCM preserves: a
|
|
125
125
|
Highlight start-border and marker dot, plus a NON-colour bold weight so the
|
|
126
|
-
cue does not rely on colour alone (WCAG 1.4.1).
|
|
126
|
+
cue does not rely on colour alone (WCAG 1.4.1). */
|
|
127
127
|
@media (forced-colors: active) {
|
|
128
128
|
.ui-app-nav a.is-active,
|
|
129
129
|
.ui-app-nav a[aria-current]:not([aria-current='false']) {
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
/* The shell keeps `min-block-size: 100dvh`, so with two auto rows the default
|
|
277
277
|
`align-content: stretch` distributes the leftover viewport across BOTH
|
|
278
278
|
tracks — ballooning the horizontal rail to ~half the screen. Pin tracks to
|
|
279
|
-
their content and let the content row absorb the slack.
|
|
279
|
+
their content and let the content row absorb the slack. */
|
|
280
280
|
align-content: start;
|
|
281
281
|
grid-template-rows: auto 1fr;
|
|
282
282
|
}
|
|
@@ -296,8 +296,7 @@
|
|
|
296
296
|
|
|
297
297
|
/* The rail is flex-row here, so the base `margin-block-start: auto` (which
|
|
298
298
|
pushed account to the bottom in the column layout) is inert and account can
|
|
299
|
-
scroll off. Push it to the inline end instead so sign-out stays reachable.
|
|
300
|
-
(layout review C21.) */
|
|
299
|
+
scroll off. Push it to the inline end instead so sign-out stays reachable. */
|
|
301
300
|
.ui-app-rail__account {
|
|
302
301
|
margin-block-start: 0;
|
|
303
302
|
margin-inline-start: auto;
|
package/css/base.css
CHANGED
|
@@ -230,7 +230,7 @@ textarea:focus-visible,
|
|
|
230
230
|
/* NB: the active-tab forced-colors re-assert lives in disclosure.css, right
|
|
231
231
|
after the `.ui-tab.is-active` default — placing it here (an earlier bundle
|
|
232
232
|
leaf) let the later default win even in forced-colors mode, since @media
|
|
233
|
-
adds no specificity.
|
|
233
|
+
adds no specificity. */
|
|
234
234
|
|
|
235
235
|
/* Keyboard focus must never depend on a colour that gets overridden. */
|
|
236
236
|
a:focus-visible,
|
package/css/content.css
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
/* Machine-generated Markdown carries long unbreakable tokens (URLs, hashes,
|
|
17
17
|
code) that otherwise force horizontal page scroll on the prose surface
|
|
18
|
-
itself. Break them
|
|
18
|
+
itself. Break them; table cells already do this locally. */
|
|
19
19
|
overflow-wrap: break-word;
|
|
20
20
|
|
|
21
21
|
/* Readable measure; the container can still be wider for tables/media. */
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
is the opt-in marks leaf (marks.css) and must win: this rule is higher-specificity
|
|
109
109
|
(0,1,1) than `.ui-mark` (0,1,0) and sits in the same `bronto` layer, so without the
|
|
110
110
|
`:not(.ui-mark)` it silently overrides every `.ui-mark` tone/draw modifier in prose —
|
|
111
|
-
the most-documented mark usage.
|
|
111
|
+
the most-documented mark usage. */
|
|
112
112
|
.ui-prose mark:not(.ui-mark) {
|
|
113
113
|
background: var(--accent-soft);
|
|
114
114
|
color: var(--text);
|
|
@@ -255,7 +255,7 @@
|
|
|
255
255
|
because `.ui-prose` is generated prose, not a data grid — but for a table whose
|
|
256
256
|
semantics MUST survive on WebKit, author the `.ui-table` component inside a
|
|
257
257
|
`.ui-table-wrap` instead (the wrap scrolls, the <table> keeps `display: table`),
|
|
258
|
-
or add `role="table"` to the markdown output.
|
|
258
|
+
or add `role="table"` to the markdown output. */
|
|
259
259
|
|
|
260
260
|
.ui-prose table {
|
|
261
261
|
border: 1px solid var(--line);
|
package/css/crosshair.css
CHANGED
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
--crosshair-x: 0;
|
|
14
14
|
--crosshair-y: 0;
|
|
15
15
|
--crosshair-color: var(--accent);
|
|
16
|
+
--crosshair-readout-gap: 0.35rem;
|
|
17
|
+
--crosshair-readout-x: var(--crosshair-readout-gap);
|
|
18
|
+
--crosshair-readout-y: var(--crosshair-readout-gap);
|
|
16
19
|
|
|
17
20
|
inset: 0;
|
|
18
21
|
opacity: 0;
|
|
@@ -25,6 +28,22 @@
|
|
|
25
28
|
opacity: 1;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
.ui-crosshair[data-readout-inline='before'] {
|
|
32
|
+
--crosshair-readout-x: calc(-100% - var(--crosshair-readout-gap));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.ui-crosshair[data-readout-block='above'] {
|
|
36
|
+
--crosshair-readout-y: calc(-100% - var(--crosshair-readout-gap));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.ui-crosshair:dir(rtl) {
|
|
40
|
+
--crosshair-readout-x: calc(100% + var(--crosshair-readout-gap));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ui-crosshair:dir(rtl)[data-readout-inline='before'] {
|
|
44
|
+
--crosshair-readout-x: calc(-1 * var(--crosshair-readout-gap));
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
@media (prefers-reduced-motion: reduce) {
|
|
29
48
|
.ui-crosshair {
|
|
30
49
|
transition: none;
|
|
@@ -76,8 +95,9 @@
|
|
|
76
95
|
color: var(--text);
|
|
77
96
|
}
|
|
78
97
|
|
|
79
|
-
/* A pinned readout chip — host fills the content; it follows the crosshair.
|
|
80
|
-
|
|
98
|
+
/* A pinned readout chip — host fills the content; it follows the crosshair.
|
|
99
|
+
Scoped so report-kit/crosshair imports do not restyle standalone dot readouts. */
|
|
100
|
+
.ui-crosshair .ui-readout {
|
|
81
101
|
background: var(--panel);
|
|
82
102
|
border: 1px solid var(--line);
|
|
83
103
|
border-radius: var(--radius-sm);
|
|
@@ -87,10 +107,15 @@
|
|
|
87
107
|
font-size: var(--text-xs);
|
|
88
108
|
inset-block-start: var(--crosshair-y);
|
|
89
109
|
inset-inline-start: var(--crosshair-x);
|
|
110
|
+
max-inline-size: calc(100% - var(--crosshair-readout-gap) * 2);
|
|
111
|
+
overflow: hidden;
|
|
90
112
|
padding-block: 0.2rem;
|
|
91
113
|
padding-inline: 0.4rem;
|
|
92
114
|
pointer-events: none;
|
|
93
115
|
position: absolute;
|
|
116
|
+
text-overflow: ellipsis;
|
|
117
|
+
transform: translate(var(--crosshair-readout-x), var(--crosshair-readout-y));
|
|
118
|
+
white-space: nowrap;
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
@media (forced-colors: active) {
|
package/css/disclosure.css
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
|
|
60
60
|
/* Forced-colors re-assert MUST sit after the default above (same specificity,
|
|
61
61
|
@media adds none) or the accent default wins and the selected tab loses its
|
|
62
|
-
only cue. Moved here from base.css, an earlier bundle leaf.
|
|
62
|
+
only cue. Moved here from base.css, an earlier bundle leaf. */
|
|
63
63
|
@media (forced-colors: active) {
|
|
64
64
|
.ui-tab.is-active {
|
|
65
65
|
border-block-end-color: Highlight;
|
|
@@ -306,7 +306,7 @@
|
|
|
306
306
|
/* Active page keys on BOTH `.is-active` and `[aria-current]` — usage.md calls
|
|
307
307
|
aria-current "the framework rule", and every nav sibling (breadcrumb, sitenav,
|
|
308
308
|
app-nav) highlights on it, so a pagination author who sets only aria-current
|
|
309
|
-
must still get the highlight.
|
|
309
|
+
must still get the highlight. */
|
|
310
310
|
.ui-pagination__item.is-active,
|
|
311
311
|
.ui-pagination__item[aria-current]:not([aria-current='false']) {
|
|
312
312
|
border-color: var(--accent);
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
(CSS can't intercept keys). For a fully-inert control prefer native
|
|
320
320
|
`<button disabled>`, run `initDisabledGuard()` (intercepts Enter/Space), or
|
|
321
321
|
pair aria-disabled with `tabindex="-1"`. See docs/usage.md "Disabled vs
|
|
322
|
-
aria-disabled".
|
|
322
|
+
aria-disabled". */
|
|
323
323
|
.ui-pagination__item[aria-disabled='true'],
|
|
324
324
|
.ui-pagination__item:disabled {
|
|
325
325
|
cursor: not-allowed;
|
package/css/dots.css
CHANGED
|
@@ -283,7 +283,7 @@
|
|
|
283
283
|
|
|
284
284
|
/* The comet expects exactly 8 `<i>`; a 9th+ child has no rotation rule and
|
|
285
285
|
would pile up dead-centre. Hide the overflow so extra children fail safe
|
|
286
|
-
rather than rendering a stray static dot
|
|
286
|
+
rather than rendering a stray static dot. */
|
|
287
287
|
.ui-dotspinner i:nth-child(n + 9) {
|
|
288
288
|
display: none;
|
|
289
289
|
}
|
|
@@ -523,7 +523,7 @@
|
|
|
523
523
|
clipped from-state on `scripting: enabled`: with JS off, `.is-in` is never
|
|
524
524
|
toggled, so without the gate the content stays permanently clipped away and
|
|
525
525
|
invisible to every no-JS/static/print reader. Same graceful default as
|
|
526
|
-
`.ui-reveal`.
|
|
526
|
+
`.ui-reveal`. */
|
|
527
527
|
@media (scripting: enabled) {
|
|
528
528
|
.ui-matrix {
|
|
529
529
|
clip-path: inset(0 100% 0 0);
|
|
@@ -583,7 +583,7 @@
|
|
|
583
583
|
|
|
584
584
|
/* Brand/live dots aren't status tones, but they still encode meaning via
|
|
585
585
|
background-color alone, which HCM flattens. Keep them on a distinct,
|
|
586
|
-
opted-out system colour for completeness.
|
|
586
|
+
opted-out system colour for completeness. */
|
|
587
587
|
.ui-dot--accent,
|
|
588
588
|
.ui-dot--live {
|
|
589
589
|
forced-color-adjust: none;
|
|
@@ -599,7 +599,7 @@
|
|
|
599
599
|
glyph vanishes (white-on-white) — yet .ui-icon is the recommended
|
|
600
600
|
icon-at-scale path AND backs .ui-legend__symbol, and the print block
|
|
601
601
|
already special-cases it. Opt out and pin the fill to the system text
|
|
602
|
-
colour so the glyph stays visible.
|
|
602
|
+
colour so the glyph stays visible. */
|
|
603
603
|
.ui-icon {
|
|
604
604
|
forced-color-adjust: none;
|
|
605
605
|
background: CanvasText;
|
package/css/feedback.css
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
a stray `--value: 50%` is invalid against the typed syntax and falls back to
|
|
10
10
|
the initial `0` (empty bar) instead of poisoning the `clamp()` and painting a
|
|
11
11
|
FULL bar (the old failure mode). It inherits so the value set on the host
|
|
12
|
-
`.ui-meter` / `.ui-progress` cascades to the inner `__fill`/`__bar`.
|
|
13
|
-
(component audit C8.) */
|
|
12
|
+
`.ui-meter` / `.ui-progress` cascades to the inner `__fill`/`__bar`. */
|
|
14
13
|
@property --value {
|
|
15
14
|
syntax: '<number>';
|
|
16
15
|
inherits: true;
|
|
@@ -335,9 +334,7 @@
|
|
|
335
334
|
position: absolute;
|
|
336
335
|
text-transform: uppercase;
|
|
337
336
|
transform: translate(-50%, 4px);
|
|
338
|
-
transition:
|
|
339
|
-
opacity var(--duration-fast) var(--ease-standard),
|
|
340
|
-
transform var(--duration-fast) var(--ease-standard);
|
|
337
|
+
transition: opacity var(--duration-fast) var(--ease-standard);
|
|
341
338
|
white-space: nowrap;
|
|
342
339
|
z-index: var(--z-popover);
|
|
343
340
|
}
|
|
@@ -348,33 +345,6 @@
|
|
|
348
345
|
transform: translate(-50%, 0);
|
|
349
346
|
}
|
|
350
347
|
|
|
351
|
-
/* Progressive enhancement: where CSS anchor positioning exists, lift
|
|
352
|
-
the bubble out of the normal flow so it can't be clipped by an
|
|
353
|
-
ancestor's overflow/scroll and auto-flips at the viewport edge.
|
|
354
|
-
Unsupported browsers keep the absolutely-positioned fallback above
|
|
355
|
-
(fine for short labels; use .ui-popover + initPopover for rich or
|
|
356
|
-
edge-critical content). */
|
|
357
|
-
@supports (anchor-name: --x) {
|
|
358
|
-
.ui-tooltip {
|
|
359
|
-
anchor-name: --ui-tooltip;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
.ui-tooltip__bubble {
|
|
363
|
-
inset: auto;
|
|
364
|
-
margin-block-end: 0.5rem;
|
|
365
|
-
position: fixed;
|
|
366
|
-
position-anchor: --ui-tooltip;
|
|
367
|
-
position-area: block-start center;
|
|
368
|
-
position-try-fallbacks: flip-block;
|
|
369
|
-
transform: translateY(4px);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
.ui-tooltip:hover .ui-tooltip__bubble,
|
|
373
|
-
.ui-tooltip:focus-within .ui-tooltip__bubble {
|
|
374
|
-
transform: translateY(0);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
348
|
/* Popover surface — a top-layer panel positioned by initPopover (JS
|
|
379
349
|
collision-aware, dependency-free). Uses the native [popover] top
|
|
380
350
|
layer when available so it never clips; the class styles it either
|
|
@@ -389,6 +359,7 @@
|
|
|
389
359
|
inline-size: max-content;
|
|
390
360
|
margin: 0;
|
|
391
361
|
max-inline-size: min(22rem, calc(100vw - 2rem));
|
|
362
|
+
overflow: auto;
|
|
392
363
|
padding: var(--space-sm) var(--space-md);
|
|
393
364
|
position: fixed;
|
|
394
365
|
z-index: var(--z-popover);
|
|
@@ -470,7 +441,7 @@
|
|
|
470
441
|
/* A still, solid, full-width bar reads as "100% complete" — the opposite of
|
|
471
442
|
indeterminate. Fall back to a static diagonal hatch that fills the track
|
|
472
443
|
(so it's clearly active) but doesn't read as done. AT is covered via
|
|
473
|
-
aria-busy.
|
|
444
|
+
aria-busy. */
|
|
474
445
|
background: repeating-linear-gradient(
|
|
475
446
|
-45deg,
|
|
476
447
|
var(--accent) 0,
|
|
@@ -494,7 +465,7 @@
|
|
|
494
465
|
Drive the fill with the same --value knob as progress; tone the fill by
|
|
495
466
|
threshold. Author role="meter" + aria-valuenow/min/max for AT — but role=meter
|
|
496
467
|
has uneven AT support, so keep the visible .ui-meter__label/__value (they are
|
|
497
|
-
the real channel, not just decoration).
|
|
468
|
+
the real channel, not just decoration). --- */
|
|
498
469
|
|
|
499
470
|
.ui-meter {
|
|
500
471
|
background: var(--panel-soft);
|
|
@@ -565,7 +536,7 @@
|
|
|
565
536
|
/* Prefer the natural one-line width, but never wider than the container: a
|
|
566
537
|
long step label at `max-content` couldn't shrink and overflowed the page on
|
|
567
538
|
narrow viewports (tabs scroll; steps didn't). Capping at 100% lets an
|
|
568
|
-
over-long label wrap instead of overflowing.
|
|
539
|
+
over-long label wrap instead of overflowing. */
|
|
569
540
|
min-inline-size: min(100%, max-content);
|
|
570
541
|
text-transform: uppercase;
|
|
571
542
|
}
|
|
@@ -625,10 +596,10 @@
|
|
|
625
596
|
/* Forced-colors flattens the fill's tone to the system palette and can erase it
|
|
626
597
|
against the track, dropping the only visual cue of the measured proportion.
|
|
627
598
|
Re-assert a system colour so the bar stays visible; the tone's *semantic* is
|
|
628
|
-
carried by the author-written label beside it, not the colour.
|
|
599
|
+
carried by the author-written label beside it, not the colour.
|
|
629
600
|
The toned `.ui-meter--TONE .ui-meter__fill` rules are (0,2,0); the bare
|
|
630
601
|
`.ui-meter__fill` reset is only (0,1,0), so it lost — a toned fill stayed
|
|
631
|
-
`var(--TONE)` and was forced to black-on-black
|
|
602
|
+
`var(--TONE)` and was forced to black-on-black. Match the
|
|
632
603
|
tone specificity here (and set `forced-color-adjust: none`) so every meter,
|
|
633
604
|
toned or not, paints `Highlight`, mirroring the `.ui-dot` precedent. */
|
|
634
605
|
@media (forced-colors: active) {
|
package/css/forms.css
CHANGED
|
@@ -79,8 +79,7 @@
|
|
|
79
79
|
|
|
80
80
|
/* Read-only is editable-looking but not editable; give it a distinct, quieter
|
|
81
81
|
cue (muted fill + default cursor) so it doesn't read as a live field. Not
|
|
82
|
-
disabled — value still submits and the field stays focusable/selectable.
|
|
83
|
-
(component audit C28.) */
|
|
82
|
+
disabled — value still submits and the field stays focusable/selectable. */
|
|
84
83
|
.ui-input:read-only:not(:disabled),
|
|
85
84
|
.ui-textarea:read-only:not(:disabled) {
|
|
86
85
|
background: var(--panel-soft);
|
|
@@ -91,7 +90,7 @@
|
|
|
91
90
|
the controls that WRAP a native input (switch/check/segmented) showed no
|
|
92
91
|
disabled cue and their label kept cursor:pointer — a lie. Mirror the cue via
|
|
93
92
|
:has(input:disabled); the native-element controls (range/file) take :disabled
|
|
94
|
-
directly.
|
|
93
|
+
directly. */
|
|
95
94
|
.ui-range:disabled,
|
|
96
95
|
.ui-file:disabled,
|
|
97
96
|
.ui-switch:has(input:disabled),
|
|
@@ -102,7 +101,7 @@
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
/* Keep autofilled fields on-theme — the UA's yellow fill otherwise paints over
|
|
105
|
-
the monochrome surface and breaks the contrast story.
|
|
104
|
+
the monochrome surface and breaks the contrast story. */
|
|
106
105
|
.ui-input:autofill,
|
|
107
106
|
.ui-select:autofill,
|
|
108
107
|
.ui-textarea:autofill,
|
|
@@ -121,8 +120,7 @@
|
|
|
121
120
|
/* Wrapper controls (switch / check / segmented) hide their native <input>, so
|
|
122
121
|
the `[aria-invalid]` the validator sets on it paints nothing — a sighted,
|
|
123
122
|
non-AT user couldn't see the error (WCAG 1.4.1). Mirror the invalid cue onto
|
|
124
|
-
the visible surface via :has(), the same way the disabled cue is mirrored.
|
|
125
|
-
(component audit C7.) */
|
|
123
|
+
the visible surface via :has(), the same way the disabled cue is mirrored. */
|
|
126
124
|
.ui-check:has(input[aria-invalid='true']) input {
|
|
127
125
|
outline: 2px solid var(--danger);
|
|
128
126
|
outline-offset: 1px;
|
|
@@ -141,8 +139,7 @@
|
|
|
141
139
|
one and sighted HCM users lose the only error cue (WCAG 1.4.1). The switch got
|
|
142
140
|
a forced-colors block; the error family did not. Re-assert the state on a
|
|
143
141
|
NON-colour channel — a thicker, doubled border — that survives HCM, and prefix
|
|
144
|
-
the error hint with a glyph so the message itself carries the error.
|
|
145
|
-
(component audit C5.) */
|
|
142
|
+
the error hint with a glyph so the message itself carries the error. */
|
|
146
143
|
@media (forced-colors: active) {
|
|
147
144
|
.ui-input[aria-invalid='true'],
|
|
148
145
|
.ui-select[aria-invalid='true'],
|
|
@@ -151,7 +148,7 @@
|
|
|
151
148
|
border-width: 3px;
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
/* Same NON-colour re-assertion for the wrapper controls
|
|
151
|
+
/* Same NON-colour re-assertion for the wrapper controls: a thicker,
|
|
155
152
|
doubled outline/border survives the HCM colour flattening. */
|
|
156
153
|
.ui-check:has(input[aria-invalid='true']) input {
|
|
157
154
|
outline-width: 3px;
|
|
@@ -205,7 +202,7 @@
|
|
|
205
202
|
|
|
206
203
|
.ui-input-group > .ui-input:focus-visible,
|
|
207
204
|
.ui-input-group > .ui-select:focus-visible {
|
|
208
|
-
z-index: 1; /* keep the focus ring above the adjacent addon border
|
|
205
|
+
z-index: 1; /* keep the focus ring above the adjacent addon border */
|
|
209
206
|
}
|
|
210
207
|
|
|
211
208
|
.ui-input-group__addon {
|
|
@@ -213,7 +210,7 @@
|
|
|
213
210
|
background: var(--panel-soft);
|
|
214
211
|
|
|
215
212
|
/* Match the wrapped control's `--line-strong` border so the prefix/suffix seam
|
|
216
|
-
isn't a fainter cap than the field it abuts.
|
|
213
|
+
isn't a fainter cap than the field it abuts. */
|
|
217
214
|
border: 1px solid var(--line-strong);
|
|
218
215
|
color: var(--text-dim);
|
|
219
216
|
display: flex;
|
|
@@ -358,7 +355,7 @@
|
|
|
358
355
|
|
|
359
356
|
/* Keyboard focus ring to match every sibling input (which use a 2px ring); the
|
|
360
357
|
border-colour shift alone read markedly fainter on this one control. Keyed to
|
|
361
|
-
:focus-visible on the inner input so it stays keyboard-only.
|
|
358
|
+
:focus-visible on the inner input so it stays keyboard-only. */
|
|
362
359
|
.ui-search:has(input:focus-visible) {
|
|
363
360
|
outline: 2px solid var(--focus-ring);
|
|
364
361
|
outline-offset: 1px;
|
package/css/legend.css
CHANGED
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
/* Glyph/symbol swatch — fill an `.ui-icon` mask with the series colour. Match the
|
|
94
94
|
__swatch fallback chain exactly (--chart-color → --chart-1 → --accent): without
|
|
95
95
|
the --chart-1 layer, a symbol and a swatch for the SAME series diverge to two
|
|
96
|
-
colours whenever a theme overrides --chart-1.
|
|
96
|
+
colours whenever a theme overrides --chart-1. */
|
|
97
97
|
.ui-legend__symbol {
|
|
98
98
|
block-size: 0.95rem;
|
|
99
99
|
color: var(--chart-color, var(--chart-1, var(--accent)));
|
package/css/marks.css
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
@media (prefers-reduced-motion: no-preference) {
|
|
92
92
|
/* The sweep animates `background-size`, i.e. the highlight FILL — so it is
|
|
93
93
|
inert on the no-fill styles (underline/box/strike). Scope it out rather
|
|
94
|
-
than let `--draw` look applied but do nothing.
|
|
94
|
+
than let `--draw` look applied but do nothing. */
|
|
95
95
|
.ui-mark--draw:not(.ui-mark--underline, .ui-mark--box, .ui-mark--strike) {
|
|
96
96
|
animation: ui-mark-draw 0.6s var(--ease, ease) both;
|
|
97
97
|
}
|
package/css/motion.css
CHANGED
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
scroll far enough to finish its range, so a fade-to-opacity-1 would strand the
|
|
58
58
|
content permanently transparent. Reach full opacity early (35%) and hold it,
|
|
59
59
|
so even a partially-driven reveal is fully legible — only the last few px of
|
|
60
|
-
the rise are left unfinished.
|
|
60
|
+
the rise are left unfinished. */
|
|
61
61
|
@keyframes uiScrollReveal {
|
|
62
62
|
0% {
|
|
63
63
|
opacity: 0;
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
/* Stagger children: set --i on each child (or use nth-child cap). Cap the manual
|
|
184
184
|
--i path at index 6 (360ms) to match the .ui-stagger--auto ceiling — a long
|
|
185
185
|
list with --i:30 would otherwise hold the last child at opacity:0 for 1.8s
|
|
186
|
-
before popping in.
|
|
186
|
+
before popping in. */
|
|
187
187
|
.ui-stagger > * {
|
|
188
188
|
animation: uiRise var(--duration-slow) var(--ease-spring) both;
|
|
189
189
|
animation-delay: calc(min(var(--i, 0), 6) * 60ms);
|
|
@@ -307,7 +307,7 @@
|
|
|
307
307
|
rest to one system colour, so the ring looks uniform with no visible sweep,
|
|
308
308
|
and the scroll-progress bar can vanish into the canvas. Re-assert distinct
|
|
309
309
|
system colours so each keeps a visible channel; AT is already covered via
|
|
310
|
-
aria-busy / role="status" / role="progressbar".
|
|
310
|
+
aria-busy / role="status" / role="progressbar". */
|
|
311
311
|
@media (forced-colors: active) {
|
|
312
312
|
.ui-spinner {
|
|
313
313
|
border-color: CanvasText;
|
|
@@ -321,7 +321,7 @@
|
|
|
321
321
|
/* HCM strips the shimmer gradient, leaving an invisible box where a loading
|
|
322
322
|
placeholder should be. Give it a system-colour border so the skeleton
|
|
323
323
|
still reads as present. Same decorative-loading family as the spinner +
|
|
324
|
-
scroll-progress above.
|
|
324
|
+
scroll-progress above. */
|
|
325
325
|
.ui-skeleton {
|
|
326
326
|
border: 1px solid CanvasText;
|
|
327
327
|
}
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
The range is `entry 0% entry 100%`, NOT the old `cover 40%`: `cover` only
|
|
347
347
|
completes once the element has scrolled most of the way THROUGH the viewport,
|
|
348
348
|
which an element near the document bottom can never do — it froze part-way,
|
|
349
|
-
leaving a conclusion section permanently semi-transparent
|
|
349
|
+
leaving a conclusion section permanently semi-transparent. `entry`
|
|
350
350
|
completes the moment the element is fully in view, which any scroll-to-bottom
|
|
351
351
|
reaches. Paired with the early-opacity uiScrollReveal keyframe, an element
|
|
352
352
|
taller than the viewport is legible even if its range never fully completes. */
|
|
@@ -405,7 +405,7 @@
|
|
|
405
405
|
/* Zero the delay too: `.ui-stagger` children animate `uiRise` with
|
|
406
406
|
`fill-mode: both`, so a non-zero `animation-delay` holds them at the
|
|
407
407
|
`opacity: 0` from-state for the full delay and then pops them in — the
|
|
408
|
-
exact late flash a reduced-motion user asked to avoid
|
|
408
|
+
exact late flash a reduced-motion user asked to avoid. */
|
|
409
409
|
animation-delay: 0s !important;
|
|
410
410
|
transition-duration: 0.01ms !important;
|
|
411
411
|
}
|
package/css/navigation.css
CHANGED
|
@@ -70,6 +70,18 @@
|
|
|
70
70
|
transform: translateX(0.6rem);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
@media (prefers-color-scheme: dark) {
|
|
74
|
+
:root:not([data-theme='light']) .ui-themetoggle__thumb {
|
|
75
|
+
background: var(--accent);
|
|
76
|
+
transform: translateX(0.6rem);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
:root[dir='rtl']:not([data-theme='light']) .ui-themetoggle__thumb,
|
|
80
|
+
:root:not([data-theme='light']) [dir='rtl'] .ui-themetoggle__thumb {
|
|
81
|
+
transform: translateX(-0.6rem);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
[dir='rtl'][data-theme='dark'] .ui-themetoggle__thumb,
|
|
74
86
|
[dir='rtl'] [data-theme='dark'] .ui-themetoggle__thumb {
|
|
75
87
|
transform: translateX(-0.6rem);
|