@urbicon-ui/blocks 6.40.4 → 6.43.2
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/README.md +1 -1
- package/dist/components/Calendar/calendar.variants.d.ts +63 -63
- package/dist/components/Chat/A2UIView/A2UINode.svelte +20 -39
- package/dist/components/Chat/A2UIView/A2UIView.svelte +47 -50
- package/dist/components/Chat/A2UIView/a2ui-basic-catalog.d.ts +17 -0
- package/dist/components/Chat/A2UIView/a2ui-basic-catalog.js +69 -0
- package/dist/components/Chat/A2UIView/a2ui-catalog.d.ts +95 -0
- package/dist/components/Chat/A2UIView/a2ui-catalog.js +87 -0
- package/dist/components/Chat/A2UIView/a2ui-node-common.d.ts +40 -0
- package/dist/components/Chat/A2UIView/a2ui-node-common.js +72 -0
- package/dist/components/Chat/A2UIView/a2ui-prompt.d.ts +21 -10
- package/dist/components/Chat/A2UIView/a2ui-prompt.js +170 -24
- package/dist/components/Chat/A2UIView/a2ui-registry.d.ts +1 -1
- package/dist/components/Chat/A2UIView/a2ui-registry.js +2 -1
- package/dist/components/Chat/A2UIView/a2ui-render.d.ts +30 -0
- package/dist/components/Chat/A2UIView/a2ui-render.js +61 -23
- package/dist/components/Chat/A2UIView/a2ui-router.d.ts +136 -0
- package/dist/components/Chat/A2UIView/a2ui-router.js +264 -0
- package/dist/components/Chat/A2UIView/a2ui-schema.d.ts +45 -0
- package/dist/components/Chat/A2UIView/a2ui-schema.js +133 -0
- package/dist/components/Chat/A2UIView/a2ui-stream.d.ts +120 -0
- package/dist/components/Chat/A2UIView/a2ui-stream.js +406 -0
- package/dist/components/Chat/A2UIView/a2ui-transcript.d.ts +65 -0
- package/dist/components/Chat/A2UIView/a2ui-transcript.js +101 -0
- package/dist/components/Chat/A2UIView/a2ui-validate.d.ts +33 -1
- package/dist/components/Chat/A2UIView/a2ui-validate.js +167 -63
- package/dist/components/Chat/A2UIView/a2ui.types.d.ts +50 -0
- package/dist/components/Chat/A2UIView/a2ui.types.js +38 -1
- package/dist/components/Chat/A2UIView/index.d.ts +35 -2
- package/dist/components/Chat/A2UIView/index.js +13 -0
- package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte +831 -0
- package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte.d.ts +4 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.d.ts +14 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.js +87 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.d.ts +78 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.js +783 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.d.ts +43 -0
- package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.js +33 -0
- package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.d.ts +7 -0
- package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.js +86 -0
- package/dist/components/Chat/ChatMessage/ChatMessage.svelte +93 -67
- package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +26 -19
- package/dist/components/Chat/ChatMessage/chat-message.variants.js +45 -12
- package/dist/components/Chat/ChatMessage/index.d.ts +20 -5
- package/dist/components/Chat/CodeBlock/CodeBlock.svelte +59 -33
- package/dist/components/Chat/CodeBlock/code-block.variants.d.ts +7 -0
- package/dist/components/Chat/CodeBlock/code-block.variants.js +28 -13
- package/dist/components/Chat/CodeBlock/index.d.ts +39 -4
- package/dist/components/Chat/PromptInput/PromptInput.svelte +9 -16
- package/dist/components/Chat/PromptInput/prompt-input.variants.js +16 -15
- package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte +9 -4
- package/dist/components/Chat/ToolCallCard/index.d.ts +4 -3
- package/dist/components/Chat/ToolCallCard/tool-call-card.variants.d.ts +0 -3
- package/dist/components/Chat/ToolCallCard/tool-call-card.variants.js +4 -4
- package/dist/components/CopyButton/CopyButton.svelte +9 -19
- package/dist/components/CopyButton/copy-button.variants.d.ts +4 -4
- package/dist/components/FileUpload/fileUpload.variants.js +15 -4
- package/dist/components/PinInput/PinInput.svelte +1 -0
- package/dist/components/PinInput/pin-input.variants.js +14 -1
- package/dist/components/TimeInput/time-input.variants.js +16 -1
- package/dist/i18n/index.d.ts +398 -2
- package/dist/internal/copy-state.svelte.d.ts +60 -0
- package/dist/internal/copy-state.svelte.js +76 -0
- package/dist/internal/field-chrome.d.ts +18 -1
- package/dist/internal/field-chrome.js +19 -2
- package/dist/primitives/Alert/alert.variants.d.ts +8 -8
- package/dist/primitives/Avatar/avatar.variants.js +4 -1
- package/dist/primitives/Badge/badge.variants.js +11 -2
- package/dist/primitives/Button/button.variants.js +4 -1
- package/dist/primitives/Card/card.variants.js +4 -1
- package/dist/primitives/Checkbox/checkbox.variants.js +7 -1
- package/dist/primitives/Combobox/Combobox.svelte +9 -1
- package/dist/primitives/Combobox/combobox.variants.d.ts +25 -0
- package/dist/primitives/Combobox/combobox.variants.js +43 -7
- package/dist/primitives/Combobox/index.d.ts +8 -2
- package/dist/primitives/Dialog/dialog.variants.js +4 -1
- package/dist/primitives/Drawer/drawer.variants.js +4 -1
- package/dist/primitives/Input/input.variants.js +17 -1
- package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +22 -22
- package/dist/primitives/Menu/menu.variants.js +5 -1
- package/dist/primitives/Progress/progress.variants.js +5 -2
- package/dist/primitives/RadioGroup/radioGroup.variants.js +74 -10
- package/dist/primitives/Select/select.variants.js +14 -5
- package/dist/primitives/Slider/Slider.svelte +4 -1
- package/dist/primitives/Slider/slider.variants.js +9 -2
- package/dist/primitives/Spinner/spinner.variants.d.ts +16 -16
- package/dist/primitives/Stepper/stepper.variants.d.ts +11 -11
- package/dist/primitives/Stepper/stepper.variants.js +2 -1
- package/dist/primitives/Tab/tab.variants.js +4 -1
- package/dist/primitives/Textarea/textarea.variants.js +16 -2
- package/dist/primitives/Toast/toast.variants.d.ts +12 -12
- package/dist/primitives/Toast/toast.variants.js +5 -1
- package/dist/primitives/Toggle/toggle.variants.js +94 -18
- package/dist/primitives/Tooltip/tooltip.variants.d.ts +3 -3
- package/dist/primitives/Tooltip/tooltip.variants.js +5 -1
- package/dist/style/foundation.css +19 -9
- package/dist/style/semantic.css +49 -2
- package/dist/style/themes/forest.css +2 -2
- package/dist/style/themes/neutral.css +2 -2
- package/dist/style/themes/ocean.css +2 -2
- package/dist/style/themes/rose.css +2 -2
- package/dist/style/themes/sunset.css +2 -2
- package/dist/utils/figma-token-export.js +7 -3
- package/dist/utils/persistent-state.svelte.d.ts +25 -1
- package/dist/utils/persistent-state.svelte.js +98 -10
- package/docs/MIGRATION-v5.md +29 -0
- package/package.json +3 -3
|
@@ -8,7 +8,26 @@ export const toggleVariants = tv({
|
|
|
8
8
|
// rectangle switch that reads as inline-toolbar control.
|
|
9
9
|
track: [
|
|
10
10
|
'relative inline-flex items-center',
|
|
11
|
-
'transition-[color,background-color,border-color,box-shadow] duration-[var(--blocks-duration-fast)] ease-out',
|
|
11
|
+
'transition-[color,background-color,border-color,box-shadow,scale] duration-[var(--blocks-duration-fast)] ease-out',
|
|
12
|
+
// Press feedback on the control surface — the same small-element press
|
|
13
|
+
// cue Checkbox took from Badge/Avatar (`scale-95`), with `group-active`
|
|
14
|
+
// so pressing the label squeezes the switch too. It rides the TRACK,
|
|
15
|
+
// not the thumb, for three reasons:
|
|
16
|
+
// 1. the track is the switch's control surface — Checkbox puts the
|
|
17
|
+
// cue on `box`, and the whole subtree (thumb included) squeezes
|
|
18
|
+
// with it, so one class covers the entire control;
|
|
19
|
+
// 2. the `dot` variant *hides* the thumb, so a thumb-mounted cue
|
|
20
|
+
// would silently vanish exactly where the control is smallest;
|
|
21
|
+
// 3. the thumb already drives motion through `translate` (its
|
|
22
|
+
// resting `-translate-y-1/2` plus the per-size `translate-x-*`).
|
|
23
|
+
// Tailwind 4 compiles `scale-*` to the discrete `scale` property,
|
|
24
|
+
// which CSS applies AFTER `translate` about the element's own
|
|
25
|
+
// centre — so a thumb-mounted cue would shrink the knob in place
|
|
26
|
+
// at whichever end of the track it sits, reading as a rendering
|
|
27
|
+
// glitch rather than a press.
|
|
28
|
+
// `scale` is in the transition list above, and reduced motion collapses
|
|
29
|
+
// `--blocks-duration-fast` to 1ms.
|
|
30
|
+
'group-active:scale-95',
|
|
12
31
|
'border border-transparent',
|
|
13
32
|
'peer-focus-visible:ring-2 peer-focus-visible:ring-primary/50',
|
|
14
33
|
'peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface-base'
|
|
@@ -104,7 +123,20 @@ export const toggleVariants = tv({
|
|
|
104
123
|
variant: 'default',
|
|
105
124
|
checked: false,
|
|
106
125
|
class: {
|
|
107
|
-
|
|
126
|
+
// Off-state hover steps the boundary up to `border-emphasis` — the
|
|
127
|
+
// same destination Checkbox's unchecked `outlined` box reaches, on
|
|
128
|
+
// the border the track already reserves (`border border-transparent`
|
|
129
|
+
// in the slot base, so nothing shifts).
|
|
130
|
+
//
|
|
131
|
+
// A fill step is now possible — `surface-interactive-hover` (added
|
|
132
|
+
// 2026-07-25) is the rung the filled fields use, and it reads in both
|
|
133
|
+
// modes. The border step stays anyway, for a Toggle-specific reason:
|
|
134
|
+
// the `dot` variant hides the thumb, so the track IS the control, and a
|
|
135
|
+
// boundary change reads there where a fill wash does not. (The original
|
|
136
|
+
// reason was that no fill token worked at all — `surface-interactive`
|
|
137
|
+
// resolved to `surface-hover` in light and `surface-active` in dark.
|
|
138
|
+
// That is fixed; this is now a choice, not a constraint.)
|
|
139
|
+
track: 'bg-surface-interactive group-hover:border-border-emphasis',
|
|
108
140
|
thumb: 'translate-x-0'
|
|
109
141
|
}
|
|
110
142
|
},
|
|
@@ -113,7 +145,11 @@ export const toggleVariants = tv({
|
|
|
113
145
|
variant: 'default',
|
|
114
146
|
checked: false,
|
|
115
147
|
withBorder: true,
|
|
116
|
-
|
|
148
|
+
// `border-strong` is the top of the border ladder, so the off-state
|
|
149
|
+
// hover above would *weaken* it (strong → emphasis). Pin the hover
|
|
150
|
+
// bucket to strong: an opt-in permanent boundary must not soften when
|
|
151
|
+
// the pointer arrives.
|
|
152
|
+
class: { track: 'border-border-strong group-hover:border-border-strong' }
|
|
117
153
|
},
|
|
118
154
|
{
|
|
119
155
|
variant: 'default',
|
|
@@ -122,41 +158,58 @@ export const toggleVariants = tv({
|
|
|
122
158
|
class: { track: 'ring-1 ring-inset ring-surface-base/40' }
|
|
123
159
|
},
|
|
124
160
|
// ── Checked track per intent (Switch-Pill only) ──
|
|
161
|
+
// Hover/active darken through the intent interaction-layer tokens — the
|
|
162
|
+
// same `bg-<intent>-hover` / `bg-<intent>-active` ladder Button and
|
|
163
|
+
// Checkbox use — via `group-*` so hovering/pressing the label counts too.
|
|
164
|
+
// The border stays on the base intent stop (as in Checkbox), leaving a
|
|
165
|
+
// hairline rim that makes the fill step legible.
|
|
125
166
|
{
|
|
126
167
|
variant: 'default',
|
|
127
168
|
checked: true,
|
|
128
169
|
intent: 'primary',
|
|
129
|
-
class: {
|
|
170
|
+
class: {
|
|
171
|
+
track: 'bg-primary border-primary group-hover:bg-primary-hover group-active:bg-primary-active'
|
|
172
|
+
}
|
|
130
173
|
},
|
|
131
174
|
{
|
|
132
175
|
variant: 'default',
|
|
133
176
|
checked: true,
|
|
134
177
|
intent: 'secondary',
|
|
135
|
-
class: {
|
|
178
|
+
class: {
|
|
179
|
+
track: 'bg-secondary border-secondary group-hover:bg-secondary-hover group-active:bg-secondary-active'
|
|
180
|
+
}
|
|
136
181
|
},
|
|
137
182
|
{
|
|
138
183
|
variant: 'default',
|
|
139
184
|
checked: true,
|
|
140
185
|
intent: 'success',
|
|
141
|
-
class: {
|
|
186
|
+
class: {
|
|
187
|
+
track: 'bg-success border-success group-hover:bg-success-hover group-active:bg-success-active'
|
|
188
|
+
}
|
|
142
189
|
},
|
|
143
190
|
{
|
|
144
191
|
variant: 'default',
|
|
145
192
|
checked: true,
|
|
146
193
|
intent: 'warning',
|
|
147
|
-
class: {
|
|
194
|
+
class: {
|
|
195
|
+
track: 'bg-warning border-warning group-hover:bg-warning-hover group-active:bg-warning-active'
|
|
196
|
+
}
|
|
148
197
|
},
|
|
149
198
|
{
|
|
150
199
|
variant: 'default',
|
|
151
200
|
checked: true,
|
|
152
201
|
intent: 'danger',
|
|
153
|
-
class: {
|
|
202
|
+
class: {
|
|
203
|
+
track: 'bg-danger border-danger group-hover:bg-danger-hover group-active:bg-danger-active'
|
|
204
|
+
}
|
|
154
205
|
},
|
|
155
206
|
{
|
|
156
207
|
variant: 'default',
|
|
157
208
|
checked: true,
|
|
158
209
|
intent: 'neutral',
|
|
159
|
-
class: {
|
|
210
|
+
class: {
|
|
211
|
+
track: 'bg-neutral border-neutral group-hover:bg-neutral-hover group-active:bg-neutral-active'
|
|
212
|
+
}
|
|
160
213
|
},
|
|
161
214
|
// ── Error state: danger boundary on the unchecked track (Switch-Pill) ──
|
|
162
215
|
// Mirrors Checkbox ("error overrides unchecked border"): the off state
|
|
@@ -165,11 +218,16 @@ export const toggleVariants = tv({
|
|
|
165
218
|
// The dot-variant twin lives at the END of the compound list so it
|
|
166
219
|
// folds over the dot-unchecked `border-border-default` (order is
|
|
167
220
|
// semantic — later compounds win conflicting buckets).
|
|
221
|
+
// `group-hover:border-danger` pins the hover bucket as well — without it
|
|
222
|
+
// the off-state hover step above would repaint the error boundary neutral
|
|
223
|
+
// the moment the pointer arrives, which is exactly when the mark matters.
|
|
168
224
|
{
|
|
169
225
|
variant: 'default',
|
|
170
226
|
error: true,
|
|
171
227
|
checked: false,
|
|
172
|
-
class: {
|
|
228
|
+
class: {
|
|
229
|
+
track: 'border-danger group-hover:border-danger peer-focus-visible:ring-danger/40'
|
|
230
|
+
}
|
|
173
231
|
},
|
|
174
232
|
// ── Thumb translation per size when checked (Switch-Pill only) ──
|
|
175
233
|
{ variant: 'default', checked: true, size: 'xs', class: { thumb: 'translate-x-4' } },
|
|
@@ -193,50 +251,68 @@ export const toggleVariants = tv({
|
|
|
193
251
|
{
|
|
194
252
|
variant: 'dot',
|
|
195
253
|
checked: false,
|
|
196
|
-
|
|
254
|
+
// Off-state hover: `border-default` → `border-emphasis`, the exact
|
|
255
|
+
// ladder Checkbox's unchecked `outlined` box walks (the dot is
|
|
256
|
+
// outline-only, so its boundary is the whole control).
|
|
257
|
+
class: { track: 'border-border-default group-hover:border-border-emphasis' }
|
|
197
258
|
},
|
|
259
|
+
// Same intent interaction layer as the Switch-Pill above — the dot is a
|
|
260
|
+
// filled intent surface too, just a 14px one.
|
|
198
261
|
{
|
|
199
262
|
variant: 'dot',
|
|
200
263
|
checked: true,
|
|
201
264
|
intent: 'primary',
|
|
202
|
-
class: {
|
|
265
|
+
class: {
|
|
266
|
+
track: 'border-primary bg-primary group-hover:bg-primary-hover group-active:bg-primary-active'
|
|
267
|
+
}
|
|
203
268
|
},
|
|
204
269
|
{
|
|
205
270
|
variant: 'dot',
|
|
206
271
|
checked: true,
|
|
207
272
|
intent: 'secondary',
|
|
208
|
-
class: {
|
|
273
|
+
class: {
|
|
274
|
+
track: 'border-secondary bg-secondary group-hover:bg-secondary-hover group-active:bg-secondary-active'
|
|
275
|
+
}
|
|
209
276
|
},
|
|
210
277
|
{
|
|
211
278
|
variant: 'dot',
|
|
212
279
|
checked: true,
|
|
213
280
|
intent: 'success',
|
|
214
|
-
class: {
|
|
281
|
+
class: {
|
|
282
|
+
track: 'border-success bg-success group-hover:bg-success-hover group-active:bg-success-active'
|
|
283
|
+
}
|
|
215
284
|
},
|
|
216
285
|
{
|
|
217
286
|
variant: 'dot',
|
|
218
287
|
checked: true,
|
|
219
288
|
intent: 'warning',
|
|
220
|
-
class: {
|
|
289
|
+
class: {
|
|
290
|
+
track: 'border-warning bg-warning group-hover:bg-warning-hover group-active:bg-warning-active'
|
|
291
|
+
}
|
|
221
292
|
},
|
|
222
293
|
{
|
|
223
294
|
variant: 'dot',
|
|
224
295
|
checked: true,
|
|
225
296
|
intent: 'danger',
|
|
226
|
-
class: {
|
|
297
|
+
class: {
|
|
298
|
+
track: 'border-danger bg-danger group-hover:bg-danger-hover group-active:bg-danger-active'
|
|
299
|
+
}
|
|
227
300
|
},
|
|
228
301
|
{
|
|
229
302
|
variant: 'dot',
|
|
230
303
|
checked: true,
|
|
231
304
|
intent: 'neutral',
|
|
232
|
-
class: {
|
|
305
|
+
class: {
|
|
306
|
+
track: 'border-neutral bg-neutral group-hover:bg-neutral-hover group-active:bg-neutral-active'
|
|
307
|
+
}
|
|
233
308
|
},
|
|
234
309
|
// ── Error state, dot variant (must fold over dot-unchecked border) ──
|
|
310
|
+
// Pins the hover bucket too, for the same reason as the Switch-Pill twin.
|
|
235
311
|
{
|
|
236
312
|
variant: 'dot',
|
|
237
313
|
error: true,
|
|
238
314
|
checked: false,
|
|
239
|
-
class: { track: 'border-danger' }
|
|
315
|
+
class: { track: 'border-danger group-hover:border-danger' }
|
|
240
316
|
}
|
|
241
317
|
],
|
|
242
318
|
defaultVariants: {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { type SlotNames, type VariantProps } from '../../utils/variants.js';
|
|
2
2
|
export declare const tooltipVariants: ((props?: {
|
|
3
3
|
open?: boolean | undefined;
|
|
4
|
-
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" |
|
|
4
|
+
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
5
5
|
size?: "sm" | "md" | "lg" | undefined;
|
|
6
6
|
} | undefined) => {
|
|
7
7
|
base: (props?: ({
|
|
8
8
|
open?: boolean | undefined;
|
|
9
|
-
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" |
|
|
9
|
+
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
10
10
|
size?: "sm" | "md" | "lg" | undefined;
|
|
11
11
|
} & {
|
|
12
12
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
13
13
|
}) | undefined) => string;
|
|
14
14
|
arrow: (props?: ({
|
|
15
15
|
open?: boolean | undefined;
|
|
16
|
-
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" |
|
|
16
|
+
intent?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | undefined;
|
|
17
17
|
size?: "sm" | "md" | "lg" | undefined;
|
|
18
18
|
} & {
|
|
19
19
|
class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
|
|
@@ -48,10 +48,14 @@ export const tooltipVariants = tv({
|
|
|
48
48
|
true: { base: 'opacity-100 starting:opacity-0' },
|
|
49
49
|
false: { base: 'opacity-0' }
|
|
50
50
|
},
|
|
51
|
+
// The standard six-value palette. `info` is deliberately absent: it belongs
|
|
52
|
+
// to the feedback family (Alert, Toast), where it means "informational, and
|
|
53
|
+
// stable under a rebrand" — a tooltip is a container, and `info` sat one
|
|
54
|
+
// hue-step from `primary` (220 vs 240), so the two were barely
|
|
55
|
+
// distinguishable while implying a distinction that wasn't there.
|
|
51
56
|
intent: {
|
|
52
57
|
primary: { base: 'bg-primary text-text-on-primary' },
|
|
53
58
|
secondary: { base: 'bg-secondary text-text-on-primary' },
|
|
54
|
-
info: { base: 'bg-info text-text-on-primary' },
|
|
55
59
|
success: { base: 'bg-success text-text-on-primary' },
|
|
56
60
|
warning: { base: 'bg-warning text-text-on-warning' },
|
|
57
61
|
danger: { base: 'bg-danger text-text-on-primary' },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
@theme {
|
|
2
2
|
--color-neutral-0: oklch(1 0 0);
|
|
3
|
-
--color-neutral-25: oklch(0.
|
|
4
|
-
--color-neutral-50: oklch(0.
|
|
3
|
+
--color-neutral-25: oklch(0.985 0.003 240);
|
|
4
|
+
--color-neutral-50: oklch(0.965 0.006 240);
|
|
5
5
|
--color-neutral-100: oklch(0.95 0.008 240);
|
|
6
6
|
--color-neutral-200: oklch(0.89 0.012 240);
|
|
7
7
|
--color-neutral-300: oklch(0.83 0.014 240);
|
|
@@ -179,13 +179,23 @@
|
|
|
179
179
|
--radius-commit: 9999px;
|
|
180
180
|
--radius-modify: var(--radius-sm);
|
|
181
181
|
--radius-contain: var(--radius-xs);
|
|
182
|
-
/* r-bridge — the
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
/* r-bridge — the MIDDLE tier, at r-md (~6 px). Two cases earn it, both
|
|
183
|
+
of them "r-contain is too hard here, r-commit is too soft":
|
|
184
|
+
|
|
185
|
+
1. Adjacency. A floating panel anchored to a commit-tier (pill)
|
|
186
|
+
trigger reads disconnected at r-contain (2 px) and over-sized
|
|
187
|
+
if it mirrors the pill at r-commit. (Menu panel when its
|
|
188
|
+
trigger Button is commit-tier.)
|
|
189
|
+
2. Small tinted content surfaces. Optical radius scales with the
|
|
190
|
+
area it turns: 2 px on a 600 px Card reads as a precise edge,
|
|
191
|
+
the same 2 px on a ~200 px chat bubble reads as a rectangle.
|
|
192
|
+
A bubble is content, not architecture — it takes the middle
|
|
193
|
+
tier. (ChatMessage bubble.)
|
|
194
|
+
|
|
195
|
+
Still not a free-for-all: everything that IS a panel, dialog or
|
|
196
|
+
container belongs on one of the three primary tiers above. Reach
|
|
197
|
+
for bridge only when the surface is demonstrably too small for
|
|
198
|
+
r-contain to read as intentional. */
|
|
189
199
|
--radius-bridge: var(--radius-md);
|
|
190
200
|
|
|
191
201
|
/* Container Query Breakpoints */
|
package/dist/style/semantic.css
CHANGED
|
@@ -18,7 +18,25 @@
|
|
|
18
18
|
above the one below — themes can differentiate them by color (e.g.
|
|
19
19
|
cream → lighter cream → near-white → white) or rely on shadows
|
|
20
20
|
alone when the palette ceiling is reached (as in the neutral default
|
|
21
|
-
where base and overlay are both white).
|
|
21
|
+
where base and overlay are both white).
|
|
22
|
+
|
|
23
|
+
LIGHT-MODE SPREAD (2026-07). In light mode `base` sits at the white
|
|
24
|
+
ceiling, so every other rung has to differentiate DOWNWARD — and the
|
|
25
|
+
ramp gave it almost nothing to work with: quiet/elevated were ΔL 0.01
|
|
26
|
+
and 0.02 from base, against 0.03 and 0.08 in dark mode. A tinted
|
|
27
|
+
in-flow surface with no border and no shadow was therefore effectively
|
|
28
|
+
invisible in light mode, which is why components kept reaching for a
|
|
29
|
+
border just to make a surface visible at all (the chat bubble, the
|
|
30
|
+
table-cell progress track). Fixed at the ramp rather than per token —
|
|
31
|
+
`neutral-25` 0.99 → 0.985 and `neutral-50` 0.98 → 0.965 — so quiet,
|
|
32
|
+
elevated and subtle all gain spread without any semantic moving and
|
|
33
|
+
without a new token landing on an occupied value. All five themes share
|
|
34
|
+
those L values (only the hue differs), so the change reaches every
|
|
35
|
+
theme at once. Measured: text-tertiary on the new elevated is 11.32:1,
|
|
36
|
+
far above AA; style/contrast.test.ts is the gate. Guarded below by
|
|
37
|
+
style/semantic.test.ts, which asserts the reading surfaces stay
|
|
38
|
+
mutually distinct in BOTH modes — the failure mode here is silent, a
|
|
39
|
+
collapsed rung looks exactly like a working one in a screenshot. */
|
|
22
40
|
/* L·1 — base: the page itself. Content, cards, and reading surfaces
|
|
23
41
|
sit at this level. */
|
|
24
42
|
--color-surface-base: light-dark(var(--color-neutral-0), var(--color-neutral-900));
|
|
@@ -31,7 +49,22 @@
|
|
|
31
49
|
/* L·3 — overlay: highest surfaces (Dialog, Drawer, Sheet, Toast).
|
|
32
50
|
Full shadow + backdrop dimming. */
|
|
33
51
|
--color-surface-overlay: light-dark(var(--color-neutral-0), var(--color-neutral-850));
|
|
52
|
+
/* `surface-hover` / `-active` are the hover/press steps for elements resting
|
|
53
|
+
on a READING surface (base/quiet/elevated) — a menu item, a table row.
|
|
54
|
+
`surface-interactive` is a resting FILL one step in from the page (a filled
|
|
55
|
+
Input, a Slider track, the Tab list). Those two ladders start at different
|
|
56
|
+
rungs, so they need their own hover step: `surface-interactive` already sits
|
|
57
|
+
at neutral-100, which is exactly where `surface-hover` lands in light mode —
|
|
58
|
+
`bg-surface-interactive hover:bg-surface-hover` was a silent no-op, and in
|
|
59
|
+
dark mode `surface-active` collapses onto `surface-interactive` the same
|
|
60
|
+
way. `-interactive-hover` continues the fill's own ladder: one step further
|
|
61
|
+
from the page in light, one step toward the light in dark. Guarded by
|
|
62
|
+
style/semantic.test.ts so the pair can never collapse again. */
|
|
34
63
|
--color-surface-interactive: light-dark(var(--color-neutral-100), var(--color-neutral-700));
|
|
64
|
+
--color-surface-interactive-hover: light-dark(
|
|
65
|
+
var(--color-neutral-200),
|
|
66
|
+
var(--color-neutral-650)
|
|
67
|
+
);
|
|
35
68
|
--color-surface-hover: light-dark(var(--color-neutral-100), var(--color-neutral-750));
|
|
36
69
|
--color-surface-active: light-dark(var(--color-neutral-200), var(--color-neutral-700));
|
|
37
70
|
--color-surface-disabled: light-dark(var(--color-neutral-100), var(--color-neutral-800));
|
|
@@ -61,7 +94,21 @@
|
|
|
61
94
|
--color-text-secondary: light-dark(var(--color-neutral-700), var(--color-neutral-300));
|
|
62
95
|
--color-text-tertiary: light-dark(var(--color-neutral-600), var(--color-neutral-300));
|
|
63
96
|
--color-text-quaternary: light-dark(var(--color-neutral-500), var(--color-neutral-400));
|
|
64
|
-
|
|
97
|
+
/* Disabled text — the same stop in both modes. WCAG 1.4.3 exempts inactive
|
|
98
|
+
controls, so the old value was not a violation, but at neutral-300 / -600 it
|
|
99
|
+
measured roughly 1.5:1 in light and 2:1 in dark: a disabled field's label is
|
|
100
|
+
the one part that still has to say what the field IS, and it was effectively
|
|
101
|
+
invisible. Now held to the 3:1 UI floor by style/contrast.test.ts across
|
|
102
|
+
every theme × mode × reading surface plus the disabled fill — that test is
|
|
103
|
+
the number, not this comment.
|
|
104
|
+
|
|
105
|
+
Deliberately still the weakest rung of the text ramp, so "disabled" stays
|
|
106
|
+
legible without stopping being a signal. Two consequences, both accepted:
|
|
107
|
+
it shares its light-mode value with text-quaternary (two roles, not two
|
|
108
|
+
steps of one ladder — but see the Calendar entry in technical-debt, where
|
|
109
|
+
they meet), and the floor covers the token alone, not what a component
|
|
110
|
+
composes on top of it (an `opacity-*` wrapper can undo it). */
|
|
111
|
+
--color-text-disabled: light-dark(var(--color-neutral-500), var(--color-neutral-500));
|
|
65
112
|
/* The on-colors below pair text with a *fill* rather than a surface.
|
|
66
113
|
text-on-dark and text-on-primary answer fills that are dark in light
|
|
67
114
|
mode and light in dark mode, so the label has to travel the opposite
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
* contrast stays unchanged. surface-*, text-* and border-* pick this up
|
|
42
42
|
* automatically through semantic.css.
|
|
43
43
|
*/
|
|
44
|
-
--color-neutral-25: oklch(0.
|
|
45
|
-
--color-neutral-50: oklch(0.
|
|
44
|
+
--color-neutral-25: oklch(0.985 0.003 150);
|
|
45
|
+
--color-neutral-50: oklch(0.965 0.006 150);
|
|
46
46
|
--color-neutral-100: oklch(0.95 0.008 150);
|
|
47
47
|
--color-neutral-200: oklch(0.89 0.012 150);
|
|
48
48
|
--color-neutral-300: oklch(0.83 0.014 150);
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
* contrast stays unchanged. surface-*, text-* and border-* pick this up
|
|
55
55
|
* automatically through semantic.css.
|
|
56
56
|
*/
|
|
57
|
-
--color-neutral-25: oklch(0.
|
|
58
|
-
--color-neutral-50: oklch(0.
|
|
57
|
+
--color-neutral-25: oklch(0.985 0 0);
|
|
58
|
+
--color-neutral-50: oklch(0.965 0 0);
|
|
59
59
|
--color-neutral-100: oklch(0.95 0 0);
|
|
60
60
|
--color-neutral-200: oklch(0.89 0 0);
|
|
61
61
|
--color-neutral-300: oklch(0.83 0 0);
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
* hue shifts — so WCAG contrast stays unchanged. surface-*, text-* and
|
|
46
46
|
* border-* pick this up automatically through semantic.css.
|
|
47
47
|
*/
|
|
48
|
-
--color-neutral-25: oklch(0.
|
|
49
|
-
--color-neutral-50: oklch(0.
|
|
48
|
+
--color-neutral-25: oklch(0.985 0.003 220);
|
|
49
|
+
--color-neutral-50: oklch(0.965 0.006 220);
|
|
50
50
|
--color-neutral-100: oklch(0.95 0.008 220);
|
|
51
51
|
--color-neutral-200: oklch(0.89 0.012 220);
|
|
52
52
|
--color-neutral-300: oklch(0.83 0.014 220);
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
* stays unchanged. surface-*, text-* and border-* pick this up
|
|
42
42
|
* automatically through semantic.css.
|
|
43
43
|
*/
|
|
44
|
-
--color-neutral-25: oklch(0.
|
|
45
|
-
--color-neutral-50: oklch(0.
|
|
44
|
+
--color-neutral-25: oklch(0.985 0.003 350);
|
|
45
|
+
--color-neutral-50: oklch(0.965 0.006 350);
|
|
46
46
|
--color-neutral-100: oklch(0.95 0.008 350);
|
|
47
47
|
--color-neutral-200: oklch(0.89 0.012 350);
|
|
48
48
|
--color-neutral-300: oklch(0.83 0.014 350);
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
* contrast stays unchanged. surface-*, text-* and border-* pick this up
|
|
42
42
|
* automatically through semantic.css; no per-token overrides needed.
|
|
43
43
|
*/
|
|
44
|
-
--color-neutral-25: oklch(0.
|
|
45
|
-
--color-neutral-50: oklch(0.
|
|
44
|
+
--color-neutral-25: oklch(0.985 0.003 50);
|
|
45
|
+
--color-neutral-50: oklch(0.965 0.006 50);
|
|
46
46
|
--color-neutral-100: oklch(0.95 0.008 50);
|
|
47
47
|
--color-neutral-200: oklch(0.89 0.012 50);
|
|
48
48
|
--color-neutral-300: oklch(0.83 0.014 50);
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
const foundationColors = {
|
|
22
22
|
neutral: {
|
|
23
23
|
'0': 'oklch(1 0 0)',
|
|
24
|
-
'25': 'oklch(0.
|
|
25
|
-
'50': 'oklch(0.
|
|
24
|
+
'25': 'oklch(0.985 0.003 240)',
|
|
25
|
+
'50': 'oklch(0.965 0.006 240)',
|
|
26
26
|
'100': 'oklch(0.95 0.008 240)',
|
|
27
27
|
'200': 'oklch(0.89 0.012 240)',
|
|
28
28
|
'300': 'oklch(0.83 0.014 240)',
|
|
@@ -139,6 +139,10 @@ const semanticTokens = {
|
|
|
139
139
|
elevated: { value: '{color.neutral.50}', description: 'L2 — lifted surfaces (popover, menu)' },
|
|
140
140
|
overlay: { value: '{color.neutral.0}', description: 'L3 — dialog/drawer/toast background' },
|
|
141
141
|
interactive: { value: '{color.neutral.100}', description: 'Interactive control background' },
|
|
142
|
+
'interactive-hover': {
|
|
143
|
+
value: '{color.neutral.200}',
|
|
144
|
+
description: 'Hover step for an interactive control fill'
|
|
145
|
+
},
|
|
142
146
|
hover: { value: '{color.neutral.100}', description: 'Hover state background' },
|
|
143
147
|
active: { value: '{color.neutral.200}', description: 'Active/pressed state background' },
|
|
144
148
|
disabled: { value: '{color.neutral.100}', description: 'Disabled surface' },
|
|
@@ -152,7 +156,7 @@ const semanticTokens = {
|
|
|
152
156
|
secondary: { value: '{color.neutral.700}', description: 'Secondary text' },
|
|
153
157
|
tertiary: { value: '{color.neutral.600}', description: 'Tertiary text (WCAG AA on base)' },
|
|
154
158
|
quaternary: { value: '{color.neutral.500}', description: 'Quaternary text (lowest emphasis)' },
|
|
155
|
-
disabled: { value: '{color.neutral.
|
|
159
|
+
disabled: { value: '{color.neutral.500}', description: 'Disabled text' },
|
|
156
160
|
'on-dark': { value: '{color.neutral.0}', description: 'Text on dark surfaces' },
|
|
157
161
|
'on-primary': { value: '{color.neutral.0}', description: 'Text on primary color bg' },
|
|
158
162
|
'on-warning': {
|
|
@@ -13,15 +13,39 @@ export interface PersistentStateConfig<T> {
|
|
|
13
13
|
/**
|
|
14
14
|
* Create a persistent state that automatically syncs with storage
|
|
15
15
|
* Uses Svelte 5 $state() for reactivity
|
|
16
|
+
*
|
|
17
|
+
* Besides `value`, the returned object exposes `hasStoredValue` — whether an
|
|
18
|
+
* entry for this key exists in storage. That is what lets a consumer tell a
|
|
19
|
+
* *stored empty* value (`[]`, `''`, `null` — the user cleared it) from *nothing
|
|
20
|
+
* stored at all*, so a cleared state can win over a default/seed instead of
|
|
21
|
+
* being re-applied on every load. Two rules keep that signal meaningful: a save
|
|
22
|
+
* that would not change the stored bytes is skipped, and an instance nobody
|
|
23
|
+
* wrote to never creates an entry for its own default (so `reset()` is not
|
|
24
|
+
* undone by the auto-save, and untouched state stays out of storage). Writing
|
|
25
|
+
* the default *back* — clearing — is a real write and does create the entry.
|
|
26
|
+
*
|
|
27
|
+
* Values must round-trip through the configured `serialize`/`deserialize`;
|
|
28
|
+
* `Set`/`Map` do not under the `JSON.stringify` default (they serialize to
|
|
29
|
+
* `{}`), so pass converting functions or store plain arrays/objects.
|
|
16
30
|
*/
|
|
17
31
|
export declare function createPersistentState<T>(config: PersistentStateConfig<T>): {
|
|
18
32
|
value: T;
|
|
33
|
+
/**
|
|
34
|
+
* Whether storage currently holds an entry for this key: `true` when
|
|
35
|
+
* construction (or `reload()`) found a parseable entry, and after a write
|
|
36
|
+
* actually reached storage; `false` when the key was absent or corrupt,
|
|
37
|
+
* after `reset()`, and always without a working storage (SSR, private
|
|
38
|
+
* mode). Use it to distinguish a stored empty value from an absent one.
|
|
39
|
+
*/
|
|
40
|
+
readonly hasStoredValue: boolean;
|
|
19
41
|
/**
|
|
20
42
|
* Reset to default value and clear storage
|
|
21
43
|
*/
|
|
22
44
|
reset(): void;
|
|
23
45
|
/**
|
|
24
|
-
* Force immediate save (bypasses debounce)
|
|
46
|
+
* Force immediate save (bypasses debounce). A no-op when storage already
|
|
47
|
+
* holds exactly this value, and — like the auto-save — when the instance
|
|
48
|
+
* was never written to and still holds its default.
|
|
25
49
|
*/
|
|
26
50
|
forceSave(): void;
|
|
27
51
|
/**
|