@urbicon-ui/blocks 6.43.2 → 6.44.0

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.
@@ -120,8 +120,13 @@ export const fileUploadVariants = tv({
120
120
  },
121
121
  disabled: {
122
122
  true: {
123
+ // No `hover:` neutralisers here: `pointer-events-none` on the root
124
+ // already makes the dropzone unhoverable, so any hover class on it is
125
+ // unreachable. The two that used to sit here (`hover:border-border-default
126
+ // hover:bg-surface-base`) were dead, and `bg-surface-base` would have
127
+ // *lightened* a disabled dropzone resting on an elevated card.
123
128
  root: 'opacity-50 pointer-events-none',
124
- dropzone: 'cursor-not-allowed hover:border-border-default hover:bg-surface-base'
129
+ dropzone: 'cursor-not-allowed'
125
130
  },
126
131
  false: {}
127
132
  }
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
3
  import { useBlocksI18n } from '../..';
4
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
4
5
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
5
6
  import { getTierContext } from '../../utils';
6
7
  import type { PinInputProps } from './index';
@@ -299,26 +300,17 @@
299
300
  {/each}
300
301
  </div>
301
302
 
302
- {#if error}
303
- <div
304
- id={messageId}
305
- role="alert"
306
- class={unstyled
307
- ? (slotClasses?.message ?? '')
308
- : styles.message({ class: slotClasses?.message })}
309
- >
310
- {error}
311
- </div>
312
- {:else if helper}
313
- <div
314
- id={messageId}
315
- class={unstyled
316
- ? (slotClasses?.message ?? '')
317
- : styles.message({ class: slotClasses?.message })}
318
- >
319
- {helper}
320
- </div>
321
- {/if}
303
+ <!-- Both arms share one id: `describedBy` points at `messageId` regardless of
304
+ which arm renders, since only one ever does. -->
305
+ <CoreFieldMessage
306
+ {error}
307
+ {helper}
308
+ errorId={messageId}
309
+ helperId={messageId}
310
+ class={unstyled
311
+ ? (slotClasses?.message ?? '')
312
+ : styles.message({ class: slotClasses?.message })}
313
+ />
322
314
 
323
315
  {#if name}
324
316
  <input type="hidden" {name} {value} />
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
3
  import { useBlocksI18n } from '../..';
4
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
4
5
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
5
6
  import { getTierContext } from '../../utils';
6
7
  import { resolveIcon } from '../../icons';
@@ -509,26 +510,17 @@
509
510
  {/if}
510
511
  </div>
511
512
 
512
- {#if error}
513
- <div
514
- id={messageId}
515
- role="alert"
516
- class={unstyled
517
- ? (slotClasses?.message ?? '')
518
- : styles.message({ class: slotClasses?.message })}
519
- >
520
- {error}
521
- </div>
522
- {:else if helper}
523
- <div
524
- id={messageId}
525
- class={unstyled
526
- ? (slotClasses?.message ?? '')
527
- : styles.message({ class: slotClasses?.message })}
528
- >
529
- {helper}
530
- </div>
531
- {/if}
513
+ <!-- Both arms share one id: `describedBy` points at `messageId` regardless of
514
+ which arm renders, since only one ever does. -->
515
+ <CoreFieldMessage
516
+ {error}
517
+ {helper}
518
+ errorId={messageId}
519
+ helperId={messageId}
520
+ class={unstyled
521
+ ? (slotClasses?.message ?? '')
522
+ : styles.message({ class: slotClasses?.message })}
523
+ />
532
524
 
533
525
  {#if name}
534
526
  <input type="hidden" {name} value={value ?? ''} />
@@ -0,0 +1,81 @@
1
+ <!--
2
+ CoreFieldMessage — the helper/error line under a form field, shared by every
3
+ component in the Form family WITHOUT any of them importing another public
4
+ component: Input, Textarea, Select, Combobox, Checkbox, Toggle, RadioGroup,
5
+ Slider, PinInput, TimeInput.
6
+
7
+ INTERNAL — never exported from the package barrel, no docs/MCP entry.
8
+
9
+ This is part (b) of the field-chrome de-duplication: part (a) moved the shared
10
+ tv() class STRINGS into `internal/field-chrome.ts` (debt-fix-wave-5), and the
11
+ same block of MARKUP stayed hand-copied in every `.svelte` file. The first cut
12
+ covered the three components the debt entry named; the block turned out to be
13
+ verbatim in seven more, which is where most of the copies actually were.
14
+
15
+ What it owns: the error-beats-helper precedence, `role="alert"` on the error
16
+ arm only, and wiring the right id onto the right arm. What it does NOT own:
17
+ the look. The call site resolves its own `message` slot (respecting `unstyled`
18
+ and `slotClasses`) and passes the finished class string, exactly as
19
+ CoreIconButton and CoreSpinner take theirs — a core carries behaviour, the
20
+ embedding component carries visual identity.
21
+
22
+ Only the error arm is a live region. The helper text is static descriptive
23
+ content that is already reachable through `aria-describedby`; announcing it
24
+ via `role="alert"` would interrupt the user for text that did not change.
25
+
26
+ Deliberately NOT extracted alongside it: the label. It looks like the same
27
+ kind of copy but it is not — the element is dictated by what the field is.
28
+ Input, Textarea and Combobox render a real `<label for={fieldId}>` because
29
+ each has exactly one focusable element; Select adds an `id` so its trigger
30
+ BUTTON can also be named by `aria-labelledby`; PinInput, TimeInput and
31
+ RadioGroup render a `<span id>` behind `aria-labelledby` on a `role="group"`,
32
+ having many focusable segments and no single field a `for` could point at;
33
+ Checkbox and Toggle put the text inside the wrapping `<label>` next to the
34
+ control, and Slider's sits in a header row beside the value read-out. Those
35
+ are five different a11y shapes, so unifying them would be a regression rather
36
+ than a de-duplication. The label stays at the call sites.
37
+
38
+ The one call site that wraps this component in markup of its own is Textarea:
39
+ its message shares a `justify-between` footer row with the character counter,
40
+ so it keeps an empty `<span>` for the no-message case to hold the counter
41
+ right. That spacer is layout, not scaffolding, and stays there.
42
+ -->
43
+ <script lang="ts">
44
+ let {
45
+ error,
46
+ helper,
47
+ errorId,
48
+ helperId,
49
+ class: className = '',
50
+ helperClass
51
+ }: {
52
+ /** Error text. Wins over `helper` when both are set. */
53
+ error?: string;
54
+ /** Helper text, shown only when there is no error. */
55
+ helper?: string;
56
+ /** Id for the error arm — must match what `aria-describedby` points at. */
57
+ errorId?: string;
58
+ /** Id for the helper arm — must match what `aria-describedby` points at. */
59
+ helperId?: string;
60
+ /** The resolved `message` slot class from the call site. */
61
+ class?: string;
62
+ /**
63
+ * Resolved class for the helper arm, when the call site styles it through a
64
+ * slot of its own instead of reusing `message`. Only Combobox does — it
65
+ * exposes `slotClasses.helper` next to `slotClasses.message`, so the two
66
+ * tones are slot constants there rather than one `messageType` axis.
67
+ * Defaults to `class`, which is what every other field passes.
68
+ */
69
+ helperClass?: string;
70
+ } = $props();
71
+ </script>
72
+
73
+ {#if error}
74
+ <div id={errorId} class={className} role="alert">
75
+ {error}
76
+ </div>
77
+ {:else if helper}
78
+ <div id={helperId} class={helperClass ?? className}>
79
+ {helper}
80
+ </div>
81
+ {/if}
@@ -0,0 +1,23 @@
1
+ type $$ComponentProps = {
2
+ /** Error text. Wins over `helper` when both are set. */
3
+ error?: string;
4
+ /** Helper text, shown only when there is no error. */
5
+ helper?: string;
6
+ /** Id for the error arm — must match what `aria-describedby` points at. */
7
+ errorId?: string;
8
+ /** Id for the helper arm — must match what `aria-describedby` points at. */
9
+ helperId?: string;
10
+ /** The resolved `message` slot class from the call site. */
11
+ class?: string;
12
+ /**
13
+ * Resolved class for the helper arm, when the call site styles it through a
14
+ * slot of its own instead of reusing `message`. Only Combobox does — it
15
+ * exposes `slotClasses.helper` next to `slotClasses.message`, so the two
16
+ * tones are slot constants there rather than one `messageType` axis.
17
+ * Defaults to `class`, which is what every other field passes.
18
+ */
19
+ helperClass?: string;
20
+ };
21
+ declare const CoreFieldMessage: import("svelte").Component<$$ComponentProps, {}, "">;
22
+ type CoreFieldMessage = ReturnType<typeof CoreFieldMessage>;
23
+ export default CoreFieldMessage;
@@ -58,7 +58,7 @@ export const fieldFocusRing = (f) => `${f}:border-primary ${f}:ring-2 ${f}:ring-
58
58
  export const fieldSurfaceVariants = (f) => ({
59
59
  outlined: 'border-border-subtle',
60
60
  filled: `bg-surface-interactive border-transparent hover:bg-surface-interactive-hover ${f}:bg-surface-base`,
61
- ghost: `bg-transparent hover:bg-surface-subtle ${f}:bg-surface-base ${f}:border-border-subtle`
61
+ ghost: `bg-transparent hover:bg-surface-hover ${f}:bg-surface-base ${f}:border-border-subtle`
62
62
  });
63
63
  /**
64
64
  * The intent frame values (border + focus ring tint) for success/warning/danger,
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { mintRegistry } from '../..';
3
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
3
4
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
4
5
  import { getTierContext, useFormField } from '../../utils';
5
6
  import { checkboxVariants, type CheckboxVariants } from '..';
@@ -186,24 +187,13 @@
186
187
  {/if}
187
188
  </label>
188
189
 
189
- {#if ff.errorId}
190
- <div
191
- id={ff.errorId}
192
- class={unstyled
193
- ? (slotClasses?.message ?? '')
194
- : styles.message({ class: slotClasses?.message })}
195
- role="alert"
196
- >
197
- {error}
198
- </div>
199
- {:else if ff.helperId}
200
- <div
201
- id={ff.helperId}
202
- class={unstyled
203
- ? (slotClasses?.message ?? '')
204
- : styles.message({ class: slotClasses?.message })}
205
- >
206
- {helper}
207
- </div>
208
- {/if}
190
+ <CoreFieldMessage
191
+ {error}
192
+ {helper}
193
+ errorId={ff.errorId}
194
+ helperId={ff.helperId}
195
+ class={unstyled
196
+ ? (slotClasses?.message ?? '')
197
+ : styles.message({ class: slotClasses?.message })}
198
+ />
209
199
  </div>
@@ -115,7 +115,7 @@ export const checkboxVariants = tv({
115
115
  checked: false,
116
116
  indeterminate: false,
117
117
  variant: 'ghost',
118
- class: { box: 'bg-transparent border-transparent group-hover:bg-surface-subtle' }
118
+ class: { box: 'bg-transparent border-transparent group-hover:bg-surface-hover' }
119
119
  },
120
120
  // ── Checked intent colors (identical across all variants) ──
121
121
  // Hover/active darken through the intent interaction-layer tokens —
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" generics="T extends string | number | boolean = string">
2
2
  import { useBlocksI18n, mintRegistry } from '../..';
3
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
3
4
  import { tick } from 'svelte';
4
5
  import { SvelteMap } from 'svelte/reactivity';
5
6
  import { comboboxVariants, type ComboboxVariants } from './combobox.variants';
@@ -841,24 +842,21 @@
841
842
  </div>
842
843
  </div>
843
844
 
844
- {#if ff.errorId}
845
- <div
846
- id={ff.errorId}
847
- role="alert"
848
- class={unstyled
849
- ? (slotClasses?.message ?? '')
850
- : styles.message({ class: slotClasses?.message })}
851
- >
852
- {error}
853
- </div>
854
- {:else if ff.helperId}
855
- <div
856
- id={ff.helperId}
857
- class={unstyled ? (slotClasses?.helper ?? '') : styles.helper({ class: slotClasses?.helper })}
858
- >
859
- {helper}
860
- </div>
861
- {/if}
845
+ <!-- Combobox is the one field whose helper has a slot of its own: the two
846
+ tones are slot constants here, not a `messageType` axis, so the helper
847
+ arm keeps reading quiet while the field is invalid. -->
848
+ <CoreFieldMessage
849
+ {error}
850
+ {helper}
851
+ errorId={ff.errorId}
852
+ helperId={ff.helperId}
853
+ class={unstyled
854
+ ? (slotClasses?.message ?? '')
855
+ : styles.message({ class: slotClasses?.message })}
856
+ helperClass={unstyled
857
+ ? (slotClasses?.helper ?? '')
858
+ : styles.helper({ class: slotClasses?.helper })}
859
+ />
862
860
  </div>
863
861
 
864
862
  <!--
@@ -146,7 +146,7 @@ export const comboboxVariants = tv({
146
146
  control: 'bg-surface-interactive border-transparent hover:bg-surface-interactive-hover focus-within:bg-surface-base'
147
147
  },
148
148
  ghost: {
149
- input: 'bg-transparent border-transparent hover:bg-surface-subtle focus-visible:bg-surface-base focus-visible:border-border-subtle',
149
+ input: 'bg-transparent border-transparent hover:bg-surface-hover focus-visible:bg-surface-base focus-visible:border-border-subtle',
150
150
  control: 'bg-transparent border-transparent focus-within:bg-surface-base focus-within:border-border-subtle'
151
151
  },
152
152
  underline: {
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { useBlocksI18n, mintRegistry, createPersistentState } from '../..';
3
3
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
4
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
4
5
  import { useFormField, getTierContext } from '../../utils';
5
6
  import type { InputProps } from '.';
6
7
  import { inputVariants, type InputVariants } from './input.variants';
@@ -289,26 +290,15 @@
289
290
  {/if}
290
291
  </div>
291
292
 
292
- {#if ff.errorId}
293
- <div
294
- id={ff.errorId}
295
- class={unstyled
296
- ? (slotClasses?.message ?? '')
297
- : styles.message({ class: slotClasses?.message })}
298
- role="alert"
299
- >
300
- {error}
301
- </div>
302
- {:else if ff.helperId}
303
- <div
304
- id={ff.helperId}
305
- class={unstyled
306
- ? (slotClasses?.message ?? '')
307
- : styles.message({ class: slotClasses?.message })}
308
- >
309
- {helper}
310
- </div>
311
- {/if}
293
+ <CoreFieldMessage
294
+ {error}
295
+ {helper}
296
+ errorId={ff.errorId}
297
+ helperId={ff.helperId}
298
+ class={unstyled
299
+ ? (slotClasses?.message ?? '')
300
+ : styles.message({ class: slotClasses?.message })}
301
+ />
312
302
 
313
303
  {#if children}
314
304
  <div class="mt-1.5">
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
2
3
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
3
4
  import { getTierContext, useFormField } from '../../utils';
4
5
  import type { RadioGroupProps } from './index';
@@ -177,24 +178,13 @@
177
178
  {@render children()}
178
179
  </div>
179
180
 
180
- {#if ff.errorId}
181
- <div
182
- id={ff.errorId}
183
- class={unstyled
184
- ? (slotClasses?.message ?? '')
185
- : styles.message({ class: slotClasses?.message })}
186
- role="alert"
187
- >
188
- {error}
189
- </div>
190
- {:else if ff.helperId}
191
- <div
192
- id={ff.helperId}
193
- class={unstyled
194
- ? (slotClasses?.message ?? '')
195
- : styles.message({ class: slotClasses?.message })}
196
- >
197
- {helper}
198
- </div>
199
- {/if}
181
+ <CoreFieldMessage
182
+ {error}
183
+ {helper}
184
+ errorId={ff.errorId}
185
+ helperId={ff.helperId}
186
+ class={unstyled
187
+ ? (slotClasses?.message ?? '')
188
+ : styles.message({ class: slotClasses?.message })}
189
+ />
200
190
  </div>
@@ -160,7 +160,7 @@ export const radioItemVariants = tv({
160
160
  {
161
161
  checked: false,
162
162
  variant: 'ghost',
163
- class: { indicator: 'bg-transparent border-transparent group-hover:bg-surface-subtle' }
163
+ class: { indicator: 'bg-transparent border-transparent group-hover:bg-surface-hover' }
164
164
  },
165
165
  // Checked intent colors
166
166
  // Hover/active darken through the intent interaction-layer tokens — the
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" generics="T extends string | number | boolean = string">
2
2
  import { useBlocksI18n, mintRegistry } from '../..';
3
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
3
4
  import { useFormField, getTierContext, useFloatingPanel, floatingPanelHidden } from '../../utils';
4
5
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
5
6
  import { resolveIcon } from '../../icons';
@@ -759,24 +760,13 @@
759
760
  {/if}
760
761
  {/if}
761
762
 
762
- {#if ff.errorId}
763
- <div
764
- id={ff.errorId}
765
- class={unstyled
766
- ? (slotClasses?.message ?? '')
767
- : styles.message({ class: slotClasses?.message })}
768
- role="alert"
769
- >
770
- {error}
771
- </div>
772
- {:else if ff.helperId}
773
- <div
774
- id={ff.helperId}
775
- class={unstyled
776
- ? (slotClasses?.message ?? '')
777
- : styles.message({ class: slotClasses?.message })}
778
- >
779
- {helper}
780
- </div>
781
- {/if}
763
+ <CoreFieldMessage
764
+ {error}
765
+ {helper}
766
+ errorId={ff.errorId}
767
+ helperId={ff.helperId}
768
+ class={unstyled
769
+ ? (slotClasses?.message ?? '')
770
+ : styles.message({ class: slotClasses?.message })}
771
+ />
782
772
  </div>
@@ -81,7 +81,7 @@ export const selectVariants = tv({
81
81
  trigger: 'bg-surface-interactive border-transparent hover:bg-surface-interactive-hover focus-visible:bg-surface-base'
82
82
  },
83
83
  ghost: {
84
- trigger: 'bg-transparent border-transparent hover:bg-surface-subtle focus-visible:bg-surface-base focus-visible:border-border-subtle'
84
+ trigger: 'bg-transparent border-transparent hover:bg-surface-hover focus-visible:bg-surface-base focus-visible:border-border-subtle'
85
85
  },
86
86
  underline: {
87
87
  trigger: 'bg-transparent border-0 border-b-2 border-border-subtle rounded-none focus-visible:ring-0'
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useBlocksI18n, mintRegistry } from '../..';
3
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
3
4
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
4
5
  import { useFormField } from '../../utils';
5
6
  import type { SliderProps } from './index';
@@ -558,24 +559,13 @@
558
559
  </div>
559
560
  {/if}
560
561
 
561
- {#if ff.errorId}
562
- <div
563
- id={ff.errorId}
564
- class={unstyled
565
- ? (slotClasses?.message ?? '')
566
- : styles.message({ class: slotClasses?.message })}
567
- role="alert"
568
- >
569
- {error}
570
- </div>
571
- {:else if ff.helperId}
572
- <div
573
- id={ff.helperId}
574
- class={unstyled
575
- ? (slotClasses?.message ?? '')
576
- : styles.message({ class: slotClasses?.message })}
577
- >
578
- {helper}
579
- </div>
580
- {/if}
562
+ <CoreFieldMessage
563
+ {error}
564
+ {helper}
565
+ errorId={ff.errorId}
566
+ helperId={ff.helperId}
567
+ class={unstyled
568
+ ? (slotClasses?.message ?? '')
569
+ : styles.message({ class: slotClasses?.message })}
570
+ />
581
571
  </div>
@@ -60,7 +60,7 @@ export const tabVariants = tv({
60
60
  trigger: [
61
61
  'px-4 py-2 border border-b-0 -mb-px',
62
62
  'text-text-tertiary border-transparent',
63
- 'hover:text-text-primary hover:bg-surface-subtle',
63
+ 'hover:text-text-primary hover:bg-surface-hover',
64
64
  'data-[state=active]:bg-surface-base data-[state=active]:text-primary',
65
65
  'data-[state=active]:border-border-subtle data-[state=active]:border-b-surface-base'
66
66
  ]
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { mintRegistry } from '../..';
3
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
3
4
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
4
5
  import { useFormField, getTierContext } from '../../utils';
5
6
  import type { TextareaProps } from './index';
@@ -165,26 +166,20 @@
165
166
  <div
166
167
  class={unstyled ? (slotClasses?.footer ?? '') : styles.footer({ class: slotClasses?.footer })}
167
168
  >
168
- {#if ff.errorId}
169
- <div
170
- id={ff.errorId}
171
- class={unstyled
172
- ? (slotClasses?.message ?? '')
173
- : styles.message({ class: slotClasses?.message })}
174
- role="alert"
175
- >
169
+ {#if error || helper}
170
+ <CoreFieldMessage
176
171
  {error}
177
- </div>
178
- {:else if ff.helperId}
179
- <div
180
- id={ff.helperId}
172
+ {helper}
173
+ errorId={ff.errorId}
174
+ helperId={ff.helperId}
181
175
  class={unstyled
182
176
  ? (slotClasses?.message ?? '')
183
177
  : styles.message({ class: slotClasses?.message })}
184
- >
185
- {helper}
186
- </div>
178
+ />
187
179
  {:else}
180
+ <!-- The footer is `justify-between`: without a first child the counter
181
+ would slide to the left edge. Unlike every other field, Textarea's
182
+ message shares a row, so the empty slot has to stay here. -->
188
183
  <span></span>
189
184
  {/if}
190
185
 
@@ -33,7 +33,7 @@ export const textareaVariants = tv({
33
33
  base: 'bg-surface-interactive border-transparent hover:bg-surface-interactive-hover focus-visible:bg-surface-base'
34
34
  },
35
35
  ghost: {
36
- base: 'bg-transparent border-transparent hover:bg-surface-subtle focus-visible:bg-surface-base focus-visible:border-border-subtle'
36
+ base: 'bg-transparent border-transparent hover:bg-surface-hover focus-visible:bg-surface-base focus-visible:border-border-subtle'
37
37
  },
38
38
  underline: {
39
39
  base: 'bg-transparent border-0 border-b-2 border-border-subtle rounded-none focus-visible:ring-0'
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useBlocksI18n, mintRegistry } from '../..';
3
+ import CoreFieldMessage from '../../internal/core/CoreFieldMessage.svelte';
3
4
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
4
5
  import { getTierContext, useFormField } from '../../utils';
5
6
  import { toggleVariants, type ToggleVariants } from './toggle.variants';
@@ -152,24 +153,13 @@
152
153
  {/if}
153
154
  </label>
154
155
 
155
- {#if ff.errorId}
156
- <div
157
- id={ff.errorId}
158
- class={unstyled
159
- ? (slotClasses?.message ?? '')
160
- : styles.message({ class: slotClasses?.message })}
161
- role="alert"
162
- >
163
- {error}
164
- </div>
165
- {:else if ff.helperId}
166
- <div
167
- id={ff.helperId}
168
- class={unstyled
169
- ? (slotClasses?.message ?? '')
170
- : styles.message({ class: slotClasses?.message })}
171
- >
172
- {helper}
173
- </div>
174
- {/if}
156
+ <CoreFieldMessage
157
+ {error}
158
+ {helper}
159
+ errorId={ff.errorId}
160
+ helperId={ff.helperId}
161
+ class={unstyled
162
+ ? (slotClasses?.message ?? '')
163
+ : styles.message({ class: slotClasses?.message })}
164
+ />
175
165
  </div>
@@ -69,6 +69,16 @@
69
69
  --color-surface-active: light-dark(var(--color-neutral-200), var(--color-neutral-700));
70
70
  --color-surface-disabled: light-dark(var(--color-neutral-100), var(--color-neutral-800));
71
71
  --color-surface-selected: light-dark(var(--color-primary-50), var(--color-primary-900));
72
+ /* A RESTING tint only — never a hover step. It resolves to exactly
73
+ `surface-elevated`, so `hover:bg-surface-subtle` is invisible on every
74
+ elevated surface (Popover, Menu, Select dropdown, elevated Card). That
75
+ shipped across 8 components until 2026-07-26; the hover idiom moved to
76
+ `surface-hover`, and variants-lint now errors on any hover fill naming a
77
+ reading surface. Its in-page-zone role was superseded by `surface-quiet`
78
+ in v5 (MIGRATION-v5 "New tokens"), which is why it has no rung of its own
79
+ left — prefer `surface-quiet` for a tinted zone and `surface-elevated`
80
+ for a raised one. Kept for the resting uses that read correctly against
81
+ a border (readonly/disabled fields, list rows, code chips). */
72
82
  --color-surface-subtle: light-dark(var(--color-neutral-50), var(--color-neutral-800));
73
83
  --color-surface-inverted: light-dark(var(--color-neutral-900), var(--color-neutral-100));
74
84
  /* Skeleton wave-shimmer highlight (Skeleton animation="wave"). A translucent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urbicon-ui/blocks",
3
- "version": "6.43.2",
3
+ "version": "6.44.0",
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": {
@@ -93,8 +93,8 @@
93
93
  "@sveltejs/package": "^2.5.8",
94
94
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
95
95
  "@tailwindcss/vite": "^4.3.1",
96
- "@urbicon-ui/i18n": "6.43.2",
97
- "@urbicon-ui/shared-types": "6.43.2",
96
+ "@urbicon-ui/i18n": "6.44.0",
97
+ "@urbicon-ui/shared-types": "6.44.0",
98
98
  "prettier": "^3.8.4",
99
99
  "prettier-plugin-svelte": "^4.1.1",
100
100
  "prettier-plugin-tailwindcss": "^0.8.0",