@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.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/dist/components/Calendar/calendar.variants.d.ts +63 -63
  3. package/dist/components/Chat/A2UIView/A2UINode.svelte +20 -39
  4. package/dist/components/Chat/A2UIView/A2UIView.svelte +47 -50
  5. package/dist/components/Chat/A2UIView/a2ui-basic-catalog.d.ts +17 -0
  6. package/dist/components/Chat/A2UIView/a2ui-basic-catalog.js +69 -0
  7. package/dist/components/Chat/A2UIView/a2ui-catalog.d.ts +95 -0
  8. package/dist/components/Chat/A2UIView/a2ui-catalog.js +87 -0
  9. package/dist/components/Chat/A2UIView/a2ui-node-common.d.ts +40 -0
  10. package/dist/components/Chat/A2UIView/a2ui-node-common.js +72 -0
  11. package/dist/components/Chat/A2UIView/a2ui-prompt.d.ts +21 -10
  12. package/dist/components/Chat/A2UIView/a2ui-prompt.js +170 -24
  13. package/dist/components/Chat/A2UIView/a2ui-registry.d.ts +1 -1
  14. package/dist/components/Chat/A2UIView/a2ui-registry.js +2 -1
  15. package/dist/components/Chat/A2UIView/a2ui-render.d.ts +30 -0
  16. package/dist/components/Chat/A2UIView/a2ui-render.js +61 -23
  17. package/dist/components/Chat/A2UIView/a2ui-router.d.ts +136 -0
  18. package/dist/components/Chat/A2UIView/a2ui-router.js +264 -0
  19. package/dist/components/Chat/A2UIView/a2ui-schema.d.ts +45 -0
  20. package/dist/components/Chat/A2UIView/a2ui-schema.js +133 -0
  21. package/dist/components/Chat/A2UIView/a2ui-stream.d.ts +120 -0
  22. package/dist/components/Chat/A2UIView/a2ui-stream.js +406 -0
  23. package/dist/components/Chat/A2UIView/a2ui-transcript.d.ts +65 -0
  24. package/dist/components/Chat/A2UIView/a2ui-transcript.js +101 -0
  25. package/dist/components/Chat/A2UIView/a2ui-validate.d.ts +33 -1
  26. package/dist/components/Chat/A2UIView/a2ui-validate.js +167 -63
  27. package/dist/components/Chat/A2UIView/a2ui.types.d.ts +50 -0
  28. package/dist/components/Chat/A2UIView/a2ui.types.js +38 -1
  29. package/dist/components/Chat/A2UIView/index.d.ts +35 -2
  30. package/dist/components/Chat/A2UIView/index.js +13 -0
  31. package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte +831 -0
  32. package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte.d.ts +4 -0
  33. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.d.ts +14 -0
  34. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.js +87 -0
  35. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.d.ts +78 -0
  36. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.js +783 -0
  37. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.d.ts +43 -0
  38. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.js +33 -0
  39. package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.d.ts +7 -0
  40. package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.js +86 -0
  41. package/dist/components/Chat/ChatMessage/ChatMessage.svelte +93 -67
  42. package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +26 -19
  43. package/dist/components/Chat/ChatMessage/chat-message.variants.js +45 -12
  44. package/dist/components/Chat/ChatMessage/index.d.ts +20 -5
  45. package/dist/components/Chat/CodeBlock/CodeBlock.svelte +59 -33
  46. package/dist/components/Chat/CodeBlock/code-block.variants.d.ts +7 -0
  47. package/dist/components/Chat/CodeBlock/code-block.variants.js +28 -13
  48. package/dist/components/Chat/CodeBlock/index.d.ts +39 -4
  49. package/dist/components/Chat/PromptInput/PromptInput.svelte +9 -16
  50. package/dist/components/Chat/PromptInput/prompt-input.variants.js +16 -15
  51. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte +9 -4
  52. package/dist/components/Chat/ToolCallCard/index.d.ts +4 -3
  53. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.d.ts +0 -3
  54. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.js +4 -4
  55. package/dist/components/CopyButton/CopyButton.svelte +9 -19
  56. package/dist/components/CopyButton/copy-button.variants.d.ts +4 -4
  57. package/dist/components/FileUpload/fileUpload.variants.js +15 -4
  58. package/dist/components/PinInput/PinInput.svelte +1 -0
  59. package/dist/components/PinInput/pin-input.variants.js +14 -1
  60. package/dist/components/TimeInput/time-input.variants.js +16 -1
  61. package/dist/i18n/index.d.ts +398 -2
  62. package/dist/internal/copy-state.svelte.d.ts +60 -0
  63. package/dist/internal/copy-state.svelte.js +76 -0
  64. package/dist/internal/field-chrome.d.ts +18 -1
  65. package/dist/internal/field-chrome.js +19 -2
  66. package/dist/primitives/Alert/alert.variants.d.ts +8 -8
  67. package/dist/primitives/Avatar/avatar.variants.js +4 -1
  68. package/dist/primitives/Badge/badge.variants.js +11 -2
  69. package/dist/primitives/Button/button.variants.js +4 -1
  70. package/dist/primitives/Card/card.variants.js +4 -1
  71. package/dist/primitives/Checkbox/checkbox.variants.js +7 -1
  72. package/dist/primitives/Combobox/Combobox.svelte +9 -1
  73. package/dist/primitives/Combobox/combobox.variants.d.ts +25 -0
  74. package/dist/primitives/Combobox/combobox.variants.js +43 -7
  75. package/dist/primitives/Combobox/index.d.ts +8 -2
  76. package/dist/primitives/Dialog/dialog.variants.js +4 -1
  77. package/dist/primitives/Drawer/drawer.variants.js +4 -1
  78. package/dist/primitives/Input/input.variants.js +17 -1
  79. package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +22 -22
  80. package/dist/primitives/Menu/menu.variants.js +5 -1
  81. package/dist/primitives/Progress/progress.variants.js +5 -2
  82. package/dist/primitives/RadioGroup/radioGroup.variants.js +74 -10
  83. package/dist/primitives/Select/select.variants.js +14 -5
  84. package/dist/primitives/Slider/Slider.svelte +4 -1
  85. package/dist/primitives/Slider/slider.variants.js +9 -2
  86. package/dist/primitives/Spinner/spinner.variants.d.ts +16 -16
  87. package/dist/primitives/Stepper/stepper.variants.d.ts +11 -11
  88. package/dist/primitives/Stepper/stepper.variants.js +2 -1
  89. package/dist/primitives/Tab/tab.variants.js +4 -1
  90. package/dist/primitives/Textarea/textarea.variants.js +16 -2
  91. package/dist/primitives/Toast/toast.variants.d.ts +12 -12
  92. package/dist/primitives/Toast/toast.variants.js +5 -1
  93. package/dist/primitives/Toggle/toggle.variants.js +94 -18
  94. package/dist/primitives/Tooltip/tooltip.variants.d.ts +3 -3
  95. package/dist/primitives/Tooltip/tooltip.variants.js +5 -1
  96. package/dist/style/foundation.css +19 -9
  97. package/dist/style/semantic.css +49 -2
  98. package/dist/style/themes/forest.css +2 -2
  99. package/dist/style/themes/neutral.css +2 -2
  100. package/dist/style/themes/ocean.css +2 -2
  101. package/dist/style/themes/rose.css +2 -2
  102. package/dist/style/themes/sunset.css +2 -2
  103. package/dist/utils/figma-token-export.js +7 -3
  104. package/dist/utils/persistent-state.svelte.d.ts +25 -1
  105. package/dist/utils/persistent-state.svelte.js +98 -10
  106. package/docs/MIGRATION-v5.md +29 -0
  107. package/package.json +3 -3
@@ -153,20 +153,25 @@
153
153
  {#if children}
154
154
  {@render children(toolCall)}
155
155
  {:else}
156
+ <!--
157
+ The payloads render as `plain` CodeBlocks: this card already IS the frame,
158
+ so a bordered child card would stack a second outline at the same radius
159
+ inside the first. The section caption moved into the block's own header
160
+ too — "Input" above a header reading "json" was one chrome row and one
161
+ label too many for a single payload.
162
+ -->
156
163
  <div class={cls('body')}>
157
164
  {#if toolCall.errorMessage}
158
165
  <p class={cls('errorMessage')}>{toolCall.errorMessage}</p>
159
166
  {/if}
160
167
  {#if toolCall.input !== undefined}
161
168
  <div class={cls('section')}>
162
- <span class={cls('sectionLabel')}>{inputLabel}</span>
163
- <CodeBlock lang="json" code={stringify(toolCall.input)} />
169
+ <CodeBlock variant="plain" label={inputLabel} code={stringify(toolCall.input)} />
164
170
  </div>
165
171
  {/if}
166
172
  {#if toolCall.output !== undefined}
167
173
  <div class={cls('section')}>
168
- <span class={cls('sectionLabel')}>{outputLabel}</span>
169
- <CodeBlock lang="json" code={stringify(toolCall.output)} />
174
+ <CodeBlock variant="plain" label={outputLabel} code={stringify(toolCall.output)} />
170
175
  </div>
171
176
  {/if}
172
177
  </div>
@@ -59,9 +59,9 @@ export interface ToolCallCardProps extends Omit<HTMLAttributes<HTMLDivElement>,
59
59
  completeLabel?: string;
60
60
  /** Header badge label for the `error` state. @default 'Failed' */
61
61
  errorLabel?: string;
62
- /** Heading above the input code block. @default 'Input' */
62
+ /** Caption in the input payload's header. @default 'Input' */
63
63
  inputLabel?: string;
64
- /** Heading above the output code block. @default 'Output' */
64
+ /** Caption in the output payload's header. @default 'Output' */
65
65
  outputLabel?: string;
66
66
  /** Extra classes merged onto the root card element. */
67
67
  class?: string;
@@ -70,7 +70,8 @@ export interface ToolCallCardProps extends Omit<HTMLAttributes<HTMLDivElement>,
70
70
  /**
71
71
  * Per-slot class overrides. Slots: `trigger` (header button), `triggerLeft`,
72
72
  * `triggerRight`, `spinner`, `toolName`, `chevron`, `body`, `section`,
73
- * `sectionLabel`, `errorMessage`.
73
+ * `errorMessage`. The payloads render as `variant="plain"` CodeBlocks — style
74
+ * those through `<BlocksProvider presets={{ CodeBlock: {...} }}>`.
74
75
  */
75
76
  slotClasses?: Partial<Record<ToolCallCardSlots, string>>;
76
77
  /**
@@ -24,9 +24,6 @@ export declare const toolCallCardVariants: ((props?: {} | undefined) => {
24
24
  section: (props?: ({} & {
25
25
  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;
26
26
  }) | undefined) => string;
27
- sectionLabel: (props?: ({} & {
28
- 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;
29
- }) | undefined) => string;
30
27
  errorMessage: (props?: ({} & {
31
28
  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;
32
29
  }) | undefined) => string;
@@ -28,10 +28,10 @@ export const toolCallCardVariants = tv({
28
28
  ],
29
29
  // Default body wrapper (input/output sections + error line).
30
30
  body: 'flex flex-col gap-3',
31
- // One labelled block (heading + CodeBlock).
32
- section: 'flex flex-col gap-1',
33
- // Small tertiary heading above each code block.
34
- sectionLabel: 'text-xs font-medium text-text-tertiary',
31
+ // One payload block. The caption lives in the CodeBlock's own header (it is
32
+ // passed as `label`), so this is a plain positioning wrapper — no heading row
33
+ // of its own, which is what removed a whole chrome level from the card.
34
+ section: 'flex flex-col',
35
35
  // Error message line shown above the sections.
36
36
  errorMessage: 'text-sm text-danger'
37
37
  }
@@ -3,6 +3,7 @@
3
3
  import { resolveIcon } from '../../icons';
4
4
  import CheckIconDefault from '../../icons/CheckIcon.svelte';
5
5
  import CopyIconDefault from '../../icons/CopyIcon.svelte';
6
+ import { createCopyState } from '../../internal/copy-state.svelte';
6
7
  import { Button } from '../../primitives/Button';
7
8
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
8
9
  import { copyButtonVariants } from './copy-button.variants';
@@ -40,8 +41,11 @@
40
41
  const blocksConfig = getBlocksConfig();
41
42
  const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
42
43
 
43
- let state = $state<'idle' | 'copied' | 'error'>('idle');
44
- let timer: ReturnType<typeof setTimeout> | undefined;
44
+ // Shares the clipboard state machine with CodeBlock and ChatMessage — see
45
+ // internal/copy-state.svelte.ts for why the outcome is returned rather than
46
+ // delivered through callbacks captured at init.
47
+ const copyState = createCopyState({ timeout: () => timeout });
48
+ const state = $derived(copyState.phase);
45
49
 
46
50
  const copyText = $derived(label ?? bt('accessibility.copy'));
47
51
  const copiedText = $derived(copiedLabel ?? bt('accessibility.copied'));
@@ -91,25 +95,11 @@
91
95
  // visible text be the name (WCAG 2.5.3), keeping any consumer aria-label.
92
96
  const ariaLabel = $derived(label ? ariaLabelProp : (ariaLabelProp ?? copyText));
93
97
 
94
- function scheduleReset() {
95
- clearTimeout(timer);
96
- if (timeout > 0) timer = setTimeout(() => (state = 'idle'), timeout);
97
- }
98
-
99
98
  async function handleCopy() {
100
- try {
101
- if (!navigator.clipboard?.writeText) throw new Error('Clipboard API unavailable');
102
- await navigator.clipboard.writeText(value);
103
- state = 'copied';
104
- onCopy?.(value);
105
- } catch (error) {
106
- state = 'error';
107
- onError?.(error);
108
- }
109
- scheduleReset();
99
+ const result = await copyState.copy(value);
100
+ if (result.ok) onCopy?.(value);
101
+ else onError?.(result.error);
110
102
  }
111
-
112
- $effect(() => () => clearTimeout(timer));
113
103
  </script>
114
104
 
115
105
  <span class="contents">
@@ -1,23 +1,23 @@
1
1
  import { type SlotNames, type VariantProps } from '../../utils/variants.js';
2
2
  export declare const copyButtonVariants: ((props?: {
3
3
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xs" | undefined;
4
- state?: "error" | "idle" | "copied" | undefined;
4
+ state?: "copied" | "error" | "idle" | undefined;
5
5
  } | undefined) => {
6
6
  base: (props?: ({
7
7
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xs" | undefined;
8
- state?: "error" | "idle" | "copied" | undefined;
8
+ state?: "copied" | "error" | "idle" | undefined;
9
9
  } & {
10
10
  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;
11
11
  }) | undefined) => string;
12
12
  icon: (props?: ({
13
13
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xs" | undefined;
14
- state?: "error" | "idle" | "copied" | undefined;
14
+ state?: "copied" | "error" | "idle" | undefined;
15
15
  } & {
16
16
  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;
17
17
  }) | undefined) => string;
18
18
  label: (props?: ({
19
19
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xs" | undefined;
20
- state?: "error" | "idle" | "copied" | undefined;
20
+ state?: "copied" | "error" | "idle" | undefined;
21
21
  } & {
22
22
  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;
23
23
  }) | undefined) => string;
@@ -8,14 +8,19 @@ export const fileUploadVariants = tv({
8
8
  'rounded-contain border-2 border-dashed',
9
9
  'border-border-default bg-surface-base',
10
10
  'cursor-pointer select-none',
11
- 'transition-[color,background-color,border-color,box-shadow,transform]',
11
+ // `scale`, NOT `transform`: Tailwind 4 emits `scale-*` as the discrete
12
+ // `scale:` property — the `dragging` compounds below lift the zone with
13
+ // `scale-[1.01]`, which a `transform` entry never animated.
14
+ 'transition-[color,background-color,border-color,box-shadow,scale]',
12
15
  'duration-[var(--blocks-duration-fast)]',
13
16
  'ease-[var(--blocks-ease-gentle)]',
14
17
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2'
15
18
  ],
16
19
  dropzoneIcon: [
17
20
  'text-text-quaternary',
18
- 'transition-[color,transform] duration-[var(--blocks-duration-fast)]'
21
+ // `scale`, NOT `transform` — the `dragging` compounds pop the icon with
22
+ // `scale-110` (discrete `scale:` property in Tailwind 4).
23
+ 'transition-[color,scale] duration-[var(--blocks-duration-fast)]'
19
24
  ],
20
25
  dropzoneTitle: ['font-medium text-text-primary'],
21
26
  dropzoneDescription: ['text-text-tertiary text-center max-w-xs'],
@@ -24,7 +29,10 @@ export const fileUploadVariants = tv({
24
29
  fileItem: [
25
30
  'group relative flex items-center gap-3',
26
31
  'rounded-contain border border-border-hairline bg-surface-quiet',
27
- 'transition-[background-color,border-color,box-shadow,opacity,transform]',
32
+ // No transform entry: nothing on this slot sets a scale/translate/rotate
33
+ // utility, and Tailwind 4 would emit those as discrete properties anyway —
34
+ // `transform` was dead weight.
35
+ 'transition-[background-color,border-color,box-shadow,opacity]',
28
36
  'duration-[var(--blocks-duration-fast)]',
29
37
  'hover:border-border-default hover:shadow-[var(--blocks-shadow-sm)]'
30
38
  ],
@@ -49,7 +57,10 @@ export const fileUploadVariants = tv({
49
57
  fileItemError: ['text-danger'],
50
58
  fileItemStatusIcon: [
51
59
  'flex-shrink-0',
52
- 'transition-[color,transform] duration-[var(--blocks-duration-fast)]'
60
+ // Colour only: the icon is swapped per status, never scaled/rotated, so a
61
+ // `transform` entry animated nothing (Tailwind 4 emits transforms as the
62
+ // discrete scale/translate/rotate properties).
63
+ 'transition-[color] duration-[var(--blocks-duration-fast)]'
53
64
  ]
54
65
  },
55
66
  variants: {
@@ -261,6 +261,7 @@
261
261
  role="group"
262
262
  aria-labelledby={label ? labelId : undefined}
263
263
  aria-label={label ? undefined : ariaLabel}
264
+ aria-disabled={disabled ? 'true' : undefined}
264
265
  class={unstyled ? (slotClasses?.group ?? '') : styles.group({ class: slotClasses?.group })}
265
266
  >
266
267
  {#each cellKeys as key, i (key)}
@@ -74,9 +74,11 @@ export const pinInputVariants = tv({
74
74
  error: { message: FIELD_MESSAGE_TONES.error },
75
75
  helper: { message: FIELD_MESSAGE_TONES.helper }
76
76
  },
77
+ // The error FRAME is not declared here — it lives in the compound stage
78
+ // below, where it beats `intent` by construction. See the precedence note
79
+ // on the first compound entry.
77
80
  error: {
78
81
  true: {
79
- cell: fieldErrorFrame(focus),
80
82
  message: FIELD_MESSAGE_TONES.error
81
83
  }
82
84
  },
@@ -85,6 +87,17 @@ export const pinInputVariants = tv({
85
87
  }
86
88
  },
87
89
  compoundVariants: [
90
+ // ── Validation precedence: `error` beats `intent`, explicitly ────────────
91
+ // Both axes paint the SAME three buckets (border-color plus the focused
92
+ // border/ring tint), so exactly one of them can win. Emitting the error
93
+ // frame here rather than on the `error` axis makes that rule structural:
94
+ // compounds always fold after every axis, so `error: true` overrides
95
+ // whatever `intent` painted regardless of how the axes are ordered above
96
+ // (it used to hinge purely on `error` being DECLARED after `intent`).
97
+ {
98
+ error: true,
99
+ class: { cell: fieldErrorFrame(focus) }
100
+ },
88
101
  // Ghost keeps a transparent border at rest — even under an intent. The error
89
102
  // state drops this so validation feedback (border-danger) stays visible.
90
103
  {
@@ -89,9 +89,11 @@ export const timeInputVariants = tv({
89
89
  error: { message: FIELD_MESSAGE_TONES.error },
90
90
  helper: { message: FIELD_MESSAGE_TONES.helper }
91
91
  },
92
+ // The error FRAME is not declared here — it lives in the compound stage
93
+ // below, where it beats `intent` by construction. See the precedence note
94
+ // on the first compound entry.
92
95
  error: {
93
96
  true: {
94
- field: fieldErrorFrame(focus),
95
97
  message: FIELD_MESSAGE_TONES.error
96
98
  }
97
99
  },
@@ -103,6 +105,19 @@ export const timeInputVariants = tv({
103
105
  }
104
106
  },
105
107
  compoundVariants: [
108
+ // ── Validation precedence: `error` beats `intent`, explicitly ────────────
109
+ // Both axes paint the SAME three buckets (border-color plus the focused
110
+ // border/ring tint), so exactly one of them can win. Emitting the error
111
+ // frame here rather than on the `error` axis makes that rule structural:
112
+ // compounds always fold after every axis, so `error: true` overrides
113
+ // whatever `intent` painted regardless of how the axes are ordered above
114
+ // (it used to hinge purely on `error` being DECLARED after `intent`).
115
+ // Folding after `fullWidth` too is harmless: that axis only paints layout
116
+ // buckets (`w`, `justify-content`), which never collide with the frame.
117
+ {
118
+ error: true,
119
+ class: { field: fieldErrorFrame(focus) }
120
+ },
106
121
  {
107
122
  variant: 'ghost',
108
123
  error: false,