@urbicon-ui/blocks 6.25.0 → 6.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CompositionBar/CompositionBar.svelte +1 -1
- package/dist/primitives/Alert/alert.variants.js +1 -1
- package/dist/primitives/Badge/badge.variants.js +1 -1
- package/dist/primitives/Button/button.variants.js +2 -2
- package/dist/primitives/Checkbox/checkbox.variants.js +2 -2
- package/dist/primitives/Dialog/Dialog.svelte +5 -0
- package/dist/primitives/Drawer/Drawer.svelte +5 -0
- package/dist/primitives/RadioGroup/radioGroup.variants.js +1 -1
- package/dist/primitives/Stepper/stepper.variants.js +1 -1
- package/dist/primitives/Tooltip/tooltip.variants.js +1 -1
- package/dist/style/foundation.css +22 -15
- package/dist/style/semantic.css +21 -9
- package/dist/style/themes/forest.css +3 -1
- package/dist/style/themes/sunset.css +3 -1
- package/dist/utils/figma-token-export.js +5 -1
- package/dist/utils/overlay.js +24 -2
- package/package.json +6 -5
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
primary: 'text-text-on-primary',
|
|
148
148
|
secondary: 'text-text-on-primary',
|
|
149
149
|
success: 'text-text-on-primary',
|
|
150
|
-
warning: 'text-text-on-
|
|
150
|
+
warning: 'text-text-on-warning',
|
|
151
151
|
danger: 'text-text-on-primary',
|
|
152
152
|
neutral: 'text-text-on-primary'
|
|
153
153
|
};
|
|
@@ -154,7 +154,7 @@ export const alertVariants = tv({
|
|
|
154
154
|
{
|
|
155
155
|
intent: 'warning',
|
|
156
156
|
variant: 'filled',
|
|
157
|
-
class: { base: 'bg-warning text-text-on-
|
|
157
|
+
class: { base: 'bg-warning text-text-on-warning', dismissButton: 'text-text-on-warning' }
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
intent: 'danger',
|
|
@@ -175,7 +175,7 @@ export const buttonVariants = tv({
|
|
|
175
175
|
intent: 'warning',
|
|
176
176
|
variant: 'filled',
|
|
177
177
|
class: {
|
|
178
|
-
base: 'bg-warning text-text-on-
|
|
178
|
+
base: 'bg-warning text-text-on-warning border-warning hover:bg-warning-hover active:bg-warning-active'
|
|
179
179
|
}
|
|
180
180
|
},
|
|
181
181
|
{
|
|
@@ -369,7 +369,7 @@ export const buttonVariants = tv({
|
|
|
369
369
|
active: true,
|
|
370
370
|
variant: 'outlined',
|
|
371
371
|
intent: 'warning',
|
|
372
|
-
class: { base: 'bg-warning text-text-on-
|
|
372
|
+
class: { base: 'bg-warning text-text-on-warning border-warning hover:bg-warning-hover' }
|
|
373
373
|
},
|
|
374
374
|
{
|
|
375
375
|
active: true,
|
|
@@ -146,7 +146,7 @@ export const checkboxVariants = tv({
|
|
|
146
146
|
checked: true,
|
|
147
147
|
intent: 'warning',
|
|
148
148
|
class: {
|
|
149
|
-
box: 'bg-warning border-warning text-text-on-
|
|
149
|
+
box: 'bg-warning border-warning text-text-on-warning group-hover:bg-warning-hover group-active:bg-warning-active'
|
|
150
150
|
}
|
|
151
151
|
},
|
|
152
152
|
{
|
|
@@ -189,7 +189,7 @@ export const checkboxVariants = tv({
|
|
|
189
189
|
indeterminate: true,
|
|
190
190
|
intent: 'warning',
|
|
191
191
|
class: {
|
|
192
|
-
box: 'bg-warning border-warning text-text-on-
|
|
192
|
+
box: 'bg-warning border-warning text-text-on-warning group-hover:bg-warning-hover group-active:bg-warning-active'
|
|
193
193
|
}
|
|
194
194
|
},
|
|
195
195
|
{
|
|
@@ -279,6 +279,10 @@
|
|
|
279
279
|
easing: motion.easing
|
|
280
280
|
}}
|
|
281
281
|
></div>
|
|
282
|
+
<!-- tabindex=-1: focus target of last resort — focusFirstElement falls back
|
|
283
|
+
to the panel when children contain nothing focusable (untitled dialog),
|
|
284
|
+
keeping the element-level ESC handler + Tab trap live. Negative, so it
|
|
285
|
+
never joins the Tab cycle. -->
|
|
282
286
|
<div
|
|
283
287
|
bind:this={panelEl}
|
|
284
288
|
class={unstyled
|
|
@@ -286,6 +290,7 @@
|
|
|
286
290
|
: styles.panel({ class: [slotClasses?.panel, className] })}
|
|
287
291
|
data-intent={intent}
|
|
288
292
|
role="document"
|
|
293
|
+
tabindex="-1"
|
|
289
294
|
style:translate={dragX !== 0 || dragY !== 0 ? `${dragX}px ${dragY}px` : undefined}
|
|
290
295
|
transition:scale={{
|
|
291
296
|
duration: motion.enterDuration,
|
|
@@ -218,6 +218,10 @@
|
|
|
218
218
|
easing: motion.easing
|
|
219
219
|
}}
|
|
220
220
|
></div>
|
|
221
|
+
<!-- tabindex=-1: focus target of last resort — focusFirstElement falls back
|
|
222
|
+
to the panel when children contain nothing focusable (untitled drawer
|
|
223
|
+
with hideCloseButton), keeping the element-level ESC handler + Tab trap
|
|
224
|
+
live. Negative, so it never joins the Tab cycle. -->
|
|
221
225
|
<div
|
|
222
226
|
bind:this={panelElement}
|
|
223
227
|
class={unstyled
|
|
@@ -225,6 +229,7 @@
|
|
|
225
229
|
: styles.panel({ class: [slotClasses?.panel, className] })}
|
|
226
230
|
data-intent={intent}
|
|
227
231
|
role="document"
|
|
232
|
+
tabindex="-1"
|
|
228
233
|
transition:fly={flyParams}
|
|
229
234
|
onoutroend={handleOutroEnd}
|
|
230
235
|
>
|
|
@@ -157,7 +157,7 @@ export const radioItemVariants = tv({
|
|
|
157
157
|
{
|
|
158
158
|
checked: true,
|
|
159
159
|
intent: 'warning',
|
|
160
|
-
class: { indicator: 'bg-warning border-warning', dot: 'bg-
|
|
160
|
+
class: { indicator: 'bg-warning border-warning', dot: 'bg-text-on-warning' }
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
checked: true,
|
|
@@ -122,7 +122,7 @@ export const stepperVariants = tv({
|
|
|
122
122
|
label: 'text-danger'
|
|
123
123
|
},
|
|
124
124
|
warning: {
|
|
125
|
-
indicator: 'border-warning bg-warning text-text-on-
|
|
125
|
+
indicator: 'border-warning bg-warning text-text-on-warning',
|
|
126
126
|
label: 'text-warning-emphasis'
|
|
127
127
|
}
|
|
128
128
|
},
|
|
@@ -53,7 +53,7 @@ export const tooltipVariants = tv({
|
|
|
53
53
|
secondary: { base: 'bg-secondary text-text-on-primary' },
|
|
54
54
|
info: { base: 'bg-info text-text-on-primary' },
|
|
55
55
|
success: { base: 'bg-success text-text-on-primary' },
|
|
56
|
-
warning: { base: 'bg-warning text-text-on-
|
|
56
|
+
warning: { base: 'bg-warning text-text-on-warning' },
|
|
57
57
|
danger: { base: 'bg-danger text-text-on-primary' },
|
|
58
58
|
neutral: { base: 'bg-surface-inverted text-text-inverted' }
|
|
59
59
|
},
|
|
@@ -52,20 +52,20 @@
|
|
|
52
52
|
--color-warm-neutral-900: oklch(0.2 0.008 45);
|
|
53
53
|
--color-warm-neutral-950: oklch(0.12 0.006 45);
|
|
54
54
|
|
|
55
|
-
/* Darkened *-500 / *-600 / *-700 so text-on-primary (white
|
|
56
|
-
AA (4.5:1) on the solid intent backgrounds **in light
|
|
57
|
-
--color-<intent> resolves to the -500/-600 shades.
|
|
58
|
-
L=0.65 which produced ~3:1 contrast — below AA.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
55
|
+
/* Darkened *-500 / *-600 / *-700 so text-on-primary (white in light mode)
|
|
56
|
+
passes WCAG AA (4.5:1) on the solid intent backgrounds **in light
|
|
57
|
+
mode**, where --color-<intent> resolves to the -500/-600 shades.
|
|
58
|
+
Previous values had L=0.65 which produced ~3:1 contrast — below AA.
|
|
59
|
+
Warning is the one inverted ramp: its fill stays light in both modes
|
|
60
|
+
(500 keeps its yellow at L=0.75) and pairs with the dark,
|
|
61
|
+
non-mode-aware `text-on-warning` (= warning-950), not white.
|
|
62
|
+
|
|
63
|
+
In dark mode --color-<intent> resolves to the *lighter* -400/-300/-200
|
|
64
|
+
shades and pairs with the dark branch of the mode-aware
|
|
65
|
+
`text-on-primary` (semantic.css, 2026-07-14). Both directions are
|
|
66
|
+
measured across all six themes by style/contrast.test.ts. Do not
|
|
67
|
+
lighten these ramps to "help" dark mode; light mode is the branch
|
|
68
|
+
these stops guarantee. */
|
|
69
69
|
--color-success-50: oklch(0.95 0.03 140);
|
|
70
70
|
--color-success-100: oklch(0.9 0.05 140);
|
|
71
71
|
--color-success-200: oklch(0.82 0.08 140);
|
|
@@ -85,7 +85,14 @@
|
|
|
85
85
|
--color-warning-400: oklch(0.66 0.14 80);
|
|
86
86
|
--color-warning-500: oklch(0.75 0.15 80);
|
|
87
87
|
--color-warning-600: oklch(0.65 0.15 80);
|
|
88
|
-
|
|
88
|
+
/* L=0.59, not the ramp's usual 0.55: warning-700 is the light-mode press
|
|
89
|
+
fill (--color-warning-active) under the dark `text-on-warning`
|
|
90
|
+
(warning-950). Warning's ramp is inverted — pressing moves TOWARD the
|
|
91
|
+
dark label — and at L=0.55 the press state measured 3.90–4.05:1, under
|
|
92
|
+
AA. 0.59 is the lowest 0.01 step at which every themed hue clears
|
|
93
|
+
4.5:1 (0.58 still fails forest's hue 60 at 4.46:1). Mirrored by the
|
|
94
|
+
forest/sunset ramps; guarded by contrast.test.ts. */
|
|
95
|
+
--color-warning-700: oklch(0.59 0.13 80);
|
|
89
96
|
--color-warning-800: oklch(0.45 0.11 80);
|
|
90
97
|
--color-warning-900: oklch(0.25 0.08 80);
|
|
91
98
|
--color-warning-950: oklch(0.15 0.05 80);
|
package/dist/style/semantic.css
CHANGED
|
@@ -54,12 +54,13 @@
|
|
|
54
54
|
--color-text-tertiary: light-dark(var(--color-neutral-600), var(--color-neutral-300));
|
|
55
55
|
--color-text-quaternary: light-dark(var(--color-neutral-500), var(--color-neutral-400));
|
|
56
56
|
--color-text-disabled: light-dark(var(--color-neutral-300), var(--color-neutral-600));
|
|
57
|
-
/* The
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
deliberately NOT aliased to one
|
|
61
|
-
--color-text-on-dark)`), even though
|
|
62
|
-
they answer to different fills and
|
|
57
|
+
/* The on-colors below pair text with a *fill* rather than a surface.
|
|
58
|
+
text-on-dark and text-on-primary answer fills that are dark in light
|
|
59
|
+
mode and light in dark mode, so the label has to travel the opposite
|
|
60
|
+
way — white, then near-black. They are deliberately NOT aliased to one
|
|
61
|
+
another (`text-on-primary: var(--color-text-on-dark)`), even though
|
|
62
|
+
they carry the same value today — they answer to different fills and
|
|
63
|
+
must stay independently overridable:
|
|
63
64
|
|
|
64
65
|
- text-on-dark is the partner of the avatar identity palette, whose 12
|
|
65
66
|
slots hold ONE lightness per mode (0.45 / 0.72) by construction.
|
|
@@ -75,10 +76,21 @@
|
|
|
75
76
|
Material-3 `onPrimary` pattern). White here was unconditional until
|
|
76
77
|
2026-07-14, which put 125 of the 126 dark-mode intent fills under WCAG AA
|
|
77
78
|
— down to 1.51:1, and getting *worse* on press. The mode-aware branch
|
|
78
|
-
clears all 108 fills this token governs; `warning` is not one of them
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
clears all 108 fills this token governs; `warning` is not one of them —
|
|
80
|
+
its fill is light in BOTH modes, so it carries its own non-mode-aware
|
|
81
|
+
on-color below (`--color-text-on-warning`). */
|
|
81
82
|
--color-text-on-primary: light-dark(var(--color-neutral-0), var(--color-neutral-900));
|
|
83
|
+
/* Warning's own on-color. Warning is the one intent whose fill stays a
|
|
84
|
+
light amber in BOTH modes (base/hover/active ride L 0.59–0.82), so unlike
|
|
85
|
+
text-on-primary this token is deliberately NOT mode-aware — the label is
|
|
86
|
+
a warm dark in both modes. It rides the warning ramp's own 950 stop
|
|
87
|
+
rather than a neutral, so theme re-hues (forest 60, sunset 92) flow
|
|
88
|
+
through and the label reads as part of the swatch instead of soot on top
|
|
89
|
+
of it. Until 2026-07-20 filled warning paired with `text-on-surface` — a
|
|
90
|
+
*surface* token that flips light in dark mode — which put all 18
|
|
91
|
+
dark-mode warning fills at 1.51–2.80:1. Measured across every theme ×
|
|
92
|
+
mode × state by style/contrast.test.ts. */
|
|
93
|
+
--color-text-on-warning: var(--color-warning-950);
|
|
82
94
|
--color-text-on-surface: light-dark(var(--color-neutral-900), var(--color-neutral-100));
|
|
83
95
|
|
|
84
96
|
/* === PRIMARY INTENT ===
|
|
@@ -86,7 +86,9 @@
|
|
|
86
86
|
--color-warning-400: oklch(0.66 0.14 60);
|
|
87
87
|
--color-warning-500: oklch(0.75 0.15 60);
|
|
88
88
|
--color-warning-600: oklch(0.65 0.15 60);
|
|
89
|
-
|
|
89
|
+
/* 0.59 mirrors foundation.css: light-mode press fill under the dark
|
|
90
|
+
text-on-warning — hue 60 is the binding case (0.58 = 4.46:1, under AA). */
|
|
91
|
+
--color-warning-700: oklch(0.59 0.13 60);
|
|
90
92
|
--color-warning-800: oklch(0.45 0.11 60);
|
|
91
93
|
--color-warning-900: oklch(0.25 0.08 60);
|
|
92
94
|
--color-warning-950: oklch(0.15 0.05 60);
|
|
@@ -73,7 +73,9 @@
|
|
|
73
73
|
--color-warning-400: oklch(0.66 0.14 92);
|
|
74
74
|
--color-warning-500: oklch(0.75 0.15 92);
|
|
75
75
|
--color-warning-600: oklch(0.65 0.15 92);
|
|
76
|
-
|
|
76
|
+
/* 0.59 mirrors foundation.css: light-mode press fill under the dark
|
|
77
|
+
text-on-warning (AA floor across all themed hues). */
|
|
78
|
+
--color-warning-700: oklch(0.59 0.13 92);
|
|
77
79
|
--color-warning-800: oklch(0.45 0.11 92);
|
|
78
80
|
--color-warning-900: oklch(0.25 0.08 92);
|
|
79
81
|
--color-warning-950: oklch(0.15 0.05 92);
|
|
@@ -99,7 +99,7 @@ const foundationColors = {
|
|
|
99
99
|
'400': 'oklch(0.66 0.14 80)',
|
|
100
100
|
'500': 'oklch(0.75 0.15 80)',
|
|
101
101
|
'600': 'oklch(0.65 0.15 80)',
|
|
102
|
-
'700': 'oklch(0.
|
|
102
|
+
'700': 'oklch(0.59 0.13 80)',
|
|
103
103
|
'800': 'oklch(0.45 0.11 80)',
|
|
104
104
|
'900': 'oklch(0.25 0.08 80)',
|
|
105
105
|
'950': 'oklch(0.15 0.05 80)'
|
|
@@ -155,6 +155,10 @@ const semanticTokens = {
|
|
|
155
155
|
disabled: { value: '{color.neutral.300}', description: 'Disabled text' },
|
|
156
156
|
'on-dark': { value: '{color.neutral.0}', description: 'Text on dark surfaces' },
|
|
157
157
|
'on-primary': { value: '{color.neutral.0}', description: 'Text on primary color bg' },
|
|
158
|
+
'on-warning': {
|
|
159
|
+
value: '{color.warning.950}',
|
|
160
|
+
description: 'Text on the warning fill (warm dark in both modes)'
|
|
161
|
+
},
|
|
158
162
|
'on-surface': { value: '{color.neutral.900}', description: 'Text on tinted surfaces' }
|
|
159
163
|
},
|
|
160
164
|
border: {
|
package/dist/utils/overlay.js
CHANGED
|
@@ -18,8 +18,18 @@ export function trapFocus(event, container) {
|
|
|
18
18
|
if (!isBrowser)
|
|
19
19
|
return;
|
|
20
20
|
const focusable = getFocusableElements(container);
|
|
21
|
-
if (focusable.length === 0)
|
|
21
|
+
if (focusable.length === 0) {
|
|
22
|
+
// Nothing tabbable inside the overlay (untitled Dialog with static
|
|
23
|
+
// children, Drawer with hideCloseButton): park focus on the container —
|
|
24
|
+
// the panel that focusFirstElement's fallback focused via tabindex="-1" —
|
|
25
|
+
// instead of letting Tab walk out of the modal. Browsers with a real top
|
|
26
|
+
// layer get this from `showModal()` inertness; the guard keeps the trap's
|
|
27
|
+
// contract honest everywhere else. `contains` includes the container
|
|
28
|
+
// itself, so focus already parked there stays put.
|
|
29
|
+
if (container?.contains(document.activeElement))
|
|
30
|
+
event.preventDefault();
|
|
22
31
|
return;
|
|
32
|
+
}
|
|
23
33
|
const first = focusable[0];
|
|
24
34
|
const last = focusable[focusable.length - 1];
|
|
25
35
|
if (event.shiftKey) {
|
|
@@ -74,8 +84,20 @@ export function focusFirstElement(container) {
|
|
|
74
84
|
return;
|
|
75
85
|
tick().then(() => {
|
|
76
86
|
const focusable = getFocusableElements(container);
|
|
77
|
-
if (focusable.length > 0)
|
|
87
|
+
if (focusable.length > 0) {
|
|
78
88
|
focusable[0].focus();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// No focusable descendant — e.g. an untitled Dialog whose children are
|
|
92
|
+
// static text (no header, so no close button), or a Drawer with
|
|
93
|
+
// hideCloseButton. Fall back to the container itself, which the overlay
|
|
94
|
+
// panels make programmatically focusable via tabindex="-1" (excluded from
|
|
95
|
+
// getFocusableElements, so it never joins the Tab cycle). Without this,
|
|
96
|
+
// focus stays on <body>: the overlay's element-level keydown never fires,
|
|
97
|
+
// leaving ESC to the window fallback and the Tab trap inert until the
|
|
98
|
+
// user clicks in — a WCAG 2.1.2 problem. Mirrors the native <dialog>
|
|
99
|
+
// focusing steps, which likewise fall back to the dialog element.
|
|
100
|
+
container?.focus();
|
|
79
101
|
});
|
|
80
102
|
}
|
|
81
103
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urbicon-ui/blocks",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.26.1",
|
|
4
4
|
"description": "Svelte 5 UI component library with Tailwind CSS 4, OKLCH design tokens and zero runtime dependencies",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"test:run": "vitest run"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
|
+
"LICENSE",
|
|
37
38
|
"dist",
|
|
38
39
|
"!dist/**/*.test.*",
|
|
39
40
|
"!dist/**/*.spec.*",
|
|
@@ -81,18 +82,18 @@
|
|
|
81
82
|
],
|
|
82
83
|
"peerDependencies": {
|
|
83
84
|
"svelte": "^5.56.4",
|
|
84
|
-
"@sveltejs/kit": "^2.
|
|
85
|
+
"@sveltejs/kit": "^2.70.1",
|
|
85
86
|
"@urbicon-ui/i18n": "^6.0.0",
|
|
86
87
|
"@urbicon-ui/shared-types": "^6.0.0"
|
|
87
88
|
},
|
|
88
89
|
"dependencies": {},
|
|
89
90
|
"devDependencies": {
|
|
90
|
-
"@sveltejs/kit": "^2.
|
|
91
|
+
"@sveltejs/kit": "^2.70.1",
|
|
91
92
|
"@sveltejs/package": "^2.5.8",
|
|
92
93
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
93
94
|
"@tailwindcss/vite": "^4.3.1",
|
|
94
|
-
"@urbicon-ui/i18n": "6.
|
|
95
|
-
"@urbicon-ui/shared-types": "6.
|
|
95
|
+
"@urbicon-ui/i18n": "6.26.1",
|
|
96
|
+
"@urbicon-ui/shared-types": "6.26.1",
|
|
96
97
|
"prettier": "^3.8.4",
|
|
97
98
|
"prettier-plugin-svelte": "^4.1.1",
|
|
98
99
|
"prettier-plugin-tailwindcss": "^0.8.0",
|