@juspay/svelte-ui-components 4.27.5 → 4.28.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.
Files changed (164) hide show
  1. package/README.md +140 -36
  2. package/dist/Animations/ModalAnimation.svelte +15 -1
  3. package/dist/Animations/OverlayAnimation.svelte +7 -1
  4. package/dist/AreaChart/AreaChart.svelte +366 -85
  5. package/dist/AspectRatio/AspectRatio.svelte +51 -0
  6. package/dist/AspectRatio/AspectRatio.svelte.d.ts +3 -0
  7. package/dist/AspectRatio/properties.d.ts +27 -0
  8. package/dist/AspectRatio/properties.js +1 -0
  9. package/dist/Banner/Banner.svelte +10 -4
  10. package/dist/Banner/properties.d.ts +8 -0
  11. package/dist/BarChart/BarChart.svelte +156 -49
  12. package/dist/Book/Book.svelte +14 -1
  13. package/dist/BrandLoader/BrandLoader.svelte +76 -12
  14. package/dist/Button/Button.svelte +56 -11
  15. package/dist/Calendar/Calendar.svelte +88 -61
  16. package/dist/Card/Card.svelte +1 -1
  17. package/dist/Carousel/Carousel.svelte +6 -3
  18. package/dist/ChatComposer/ChatComposer.svelte +54 -4
  19. package/dist/ChatComposer/dictationState.d.ts +15 -0
  20. package/dist/ChatComposer/dictationState.js +9 -0
  21. package/dist/ChatComposer/properties.d.ts +28 -1
  22. package/dist/ChatMessage/ChatMessage.svelte +27 -1
  23. package/dist/CheckListItem/CheckListItem.svelte +3 -3
  24. package/dist/Checkbox/Checkbox.svelte +84 -3
  25. package/dist/Checkbox/properties.d.ts +45 -13
  26. package/dist/ChipInput/ChipInput.svelte +70 -3
  27. package/dist/ChipInput/properties.d.ts +16 -0
  28. package/dist/Choicebox/Choicebox.svelte +156 -1
  29. package/dist/Choicebox/group.d.ts +63 -0
  30. package/dist/Choicebox/group.js +127 -0
  31. package/dist/Choicebox/properties.d.ts +46 -0
  32. package/dist/ColorPicker/ColorPicker.svelte +169 -20
  33. package/dist/ColorPicker/properties.d.ts +16 -0
  34. package/dist/Combobox/Combobox.svelte +87 -25
  35. package/dist/Combobox/properties.d.ts +23 -0
  36. package/dist/CommandMenu/CommandMenu.svelte +27 -11
  37. package/dist/ContextMenu/ContextMenu.svelte +31 -37
  38. package/dist/DateRangePicker/DateRangePicker.svelte +171 -73
  39. package/dist/DeltaIndicator/DeltaIndicator.svelte +3 -3
  40. package/dist/Draggable/Draggable.svelte +12 -7
  41. package/dist/DualAxisBarChart/DualAxisBarChart.svelte +11 -4
  42. package/dist/EmptyState/EmptyState.svelte +1 -1
  43. package/dist/FileDropzoneTrigger/FileDropzoneTrigger.svelte +1 -1
  44. package/dist/FileInput/FileInput.svelte +50 -1
  45. package/dist/FileInput/properties.d.ts +7 -0
  46. package/dist/FunnelChart/FunnelChart.svelte +40 -12
  47. package/dist/FunnelChart/properties.d.ts +7 -1
  48. package/dist/Gallery/Gallery.svelte +20 -13
  49. package/dist/Gallery/properties.d.ts +8 -0
  50. package/dist/GridItem/GridItem.svelte +31 -1
  51. package/dist/HITL/HITL.svelte +35 -9
  52. package/dist/Icon/Icon.svelte +19 -3
  53. package/dist/Input/Input.svelte +24 -10
  54. package/dist/Input/properties.d.ts +37 -1
  55. package/dist/InputButton/InputButton.svelte +86 -11
  56. package/dist/InputButton/properties.d.ts +10 -1
  57. package/dist/Label/Label.svelte +54 -0
  58. package/dist/Label/Label.svelte.d.ts +3 -0
  59. package/dist/Label/properties.d.ts +26 -0
  60. package/dist/Label/properties.js +1 -0
  61. package/dist/LineChart/LineChart.svelte +385 -94
  62. package/dist/ListItem/ListItem.svelte +121 -15
  63. package/dist/Loader/Loader.svelte +22 -2
  64. package/dist/LoadingDots/LoadingDots.svelte +21 -0
  65. package/dist/MediaUpload/MediaUpload.svelte +13 -3
  66. package/dist/Menu/Menu.svelte +56 -28
  67. package/dist/Modal/Modal.svelte +77 -42
  68. package/dist/Pagination/Pagination.svelte +9 -2
  69. package/dist/Phone/Phone.svelte +4 -0
  70. package/dist/PieChart/PieChart.svelte +282 -38
  71. package/dist/PieChart/properties.d.ts +9 -0
  72. package/dist/Pill/Pill.svelte +14 -5
  73. package/dist/Progress/Progress.svelte +20 -0
  74. package/dist/Radio/Radio.svelte +58 -1
  75. package/dist/Radio/properties.d.ts +23 -0
  76. package/dist/RatingGroup/RatingGroup.svelte +283 -0
  77. package/dist/RatingGroup/RatingGroup.svelte.d.ts +4 -0
  78. package/dist/RatingGroup/properties.d.ts +73 -0
  79. package/dist/RatingGroup/properties.js +1 -0
  80. package/dist/RelativeTime/RelativeTime.svelte +1 -1
  81. package/dist/SankeyChart/SankeyChart.svelte +272 -50
  82. package/dist/SankeyChart/properties.d.ts +9 -0
  83. package/dist/Scroller/Scroller.svelte +37 -2
  84. package/dist/Select/Select.svelte +166 -46
  85. package/dist/Select/properties.d.ts +7 -0
  86. package/dist/Separator/Separator.svelte +48 -0
  87. package/dist/Separator/Separator.svelte.d.ts +3 -0
  88. package/dist/Separator/properties.d.ts +22 -0
  89. package/dist/Separator/properties.js +1 -0
  90. package/dist/Sheet/Sheet.svelte +60 -21
  91. package/dist/Shimmer/Shimmer.svelte +15 -0
  92. package/dist/Slider/Slider.svelte +87 -6
  93. package/dist/Slider/properties.d.ts +26 -0
  94. package/dist/Snippet/Snippet.svelte +4 -0
  95. package/dist/Snippet/copyState.svelte.d.ts +13 -1
  96. package/dist/Snippet/copyState.svelte.js +27 -1
  97. package/dist/Snippet/properties.d.ts +7 -0
  98. package/dist/SoundKit/SoundKit.js +9 -8
  99. package/dist/SplitInput/SplitInput.svelte +68 -2
  100. package/dist/SplitInput/properties.d.ts +23 -0
  101. package/dist/StatCard/StatCard.svelte +6 -6
  102. package/dist/Status/Status.svelte +2 -2
  103. package/dist/Stepper/Step.svelte +19 -6
  104. package/dist/Stepper/Stepper.svelte +108 -35
  105. package/dist/Table/BuiltinCell.svelte +2 -2
  106. package/dist/Table/Table.svelte +373 -55
  107. package/dist/Table/properties.d.ts +134 -3
  108. package/dist/Tabs/Tabs.svelte +263 -65
  109. package/dist/Tabs/properties.d.ts +23 -0
  110. package/dist/TaskList/TaskList.svelte +14 -5
  111. package/dist/ThemeSwitcher/ThemeSwitcher.svelte +23 -0
  112. package/dist/ThinkingIndicator/ThinkingIndicator.svelte +13 -5
  113. package/dist/Toast/Toast.svelte +27 -2
  114. package/dist/Toggle/Toggle.svelte +79 -7
  115. package/dist/Toggle/Toggle.svelte.d.ts +1 -1
  116. package/dist/Toggle/properties.d.ts +30 -7
  117. package/dist/ToolCallLog/ToolCallLog.svelte +59 -21
  118. package/dist/ToolCallLog/properties.d.ts +10 -0
  119. package/dist/Toolbar/Toolbar.svelte +1 -1
  120. package/dist/Tooltip/Tooltip.svelte +20 -2
  121. package/dist/Tooltip/tooltip-action.js +39 -4
  122. package/dist/TypewriterText/TypewriterText.svelte +34 -2
  123. package/dist/TypewriterText/properties.d.ts +18 -5
  124. package/dist/_chart/ChartContainer.svelte +12 -0
  125. package/dist/_chart/ChartTooltip.svelte +18 -4
  126. package/dist/_chart/Legend.svelte +21 -0
  127. package/dist/_chart/Legend.svelte.d.ts +15 -0
  128. package/dist/_chart/geometry.d.ts +54 -1
  129. package/dist/_chart/geometry.js +122 -14
  130. package/dist/_chart/interactions.d.ts +5 -1
  131. package/dist/_chart/interactions.js +9 -5
  132. package/dist/_chart/scales.js +16 -0
  133. package/dist/_chart/temporal.d.ts +45 -0
  134. package/dist/_chart/temporal.js +116 -0
  135. package/dist/_field/description.d.ts +50 -0
  136. package/dist/_field/description.js +51 -0
  137. package/dist/_interaction/dismissal.d.ts +67 -0
  138. package/dist/_interaction/dismissal.js +137 -0
  139. package/dist/_interaction/focus.d.ts +73 -0
  140. package/dist/_interaction/focus.js +143 -0
  141. package/dist/assets/star.svg +5 -0
  142. package/dist/index.d.ts +13 -1
  143. package/dist/index.js +21 -1
  144. package/dist/reduced-motion.svelte.d.ts +37 -0
  145. package/dist/reduced-motion.svelte.js +44 -0
  146. package/dist/styles/theme-dark.css +778 -0
  147. package/dist/types.d.ts +4 -2
  148. package/dist/utils.d.ts +62 -1
  149. package/dist/utils.js +80 -21
  150. package/dist-codemod/{bin.js → codemod/bin.js} +13 -2
  151. package/dist-codemod/{cli.js → codemod/cli.js} +48 -3
  152. package/dist-codemod/codemod/migrate-audit.js +395 -0
  153. package/dist-codemod/codemod/migration-assets/legacy-display.css +120 -0
  154. package/dist-codemod/codemod/migration-assets/legacy-palette.css +106 -0
  155. package/dist-codemod/codemod/migration-assets/wc-display.json +492 -0
  156. package/dist-codemod/migrate/analyze.js +804 -0
  157. package/dist-codemod/migrate/chart-tooltip-selector.js +301 -0
  158. package/dist-wc/index.js +13992 -4323
  159. package/package.json +13 -6
  160. package/dist/Modal/focus-trap.d.ts +0 -42
  161. package/dist/Modal/focus-trap.js +0 -57
  162. /package/dist-codemod/{legacy-pairs.js → codemod/legacy-pairs.js} +0 -0
  163. /package/dist-codemod/{transform.js → codemod/transform.js} +0 -0
  164. /package/dist-codemod/{wc-children.js → codemod/wc-children.js} +0 -0
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { prefersReducedMotion } from '../utils';
3
+ import { getActiveElement } from '../_interaction/focus';
2
4
  import { validateInput } from '../utils';
3
5
  import type { InputProperties } from './properties';
4
6
  import type { ValidationState } from '../types';
@@ -53,6 +55,7 @@
53
55
  leftIconLabel = 'Leading action',
54
56
  rightIconLabel = 'Trailing action',
55
57
  mandatory = false,
58
+ required,
56
59
  forceError = false,
57
60
  rows,
58
61
  autoResize = false,
@@ -62,6 +65,12 @@
62
65
  showCount = false
63
66
  }: InputProperties = $props();
64
67
 
68
+ // `required` decides when it is supplied at all, so a caller migrating one call site
69
+ // at a time gets an unambiguous direction; `mandatory` answers only when `required`
70
+ // was not passed. Checked with typeof rather than a default, so an explicit
71
+ // `required={false}` beside a legacy `mandatory` really does turn the field optional.
72
+ const isRequired = $derived(typeof required === 'boolean' ? required : mandatory);
73
+
65
74
  /* `for` on a <label> resolves against an element's id, never its name. The label
66
75
  was emitted with for={name} while the field itself carried only name={name},
67
76
  so the association never completed for any caller. Derive the id from name so
@@ -75,7 +84,12 @@
75
84
  export function focus() {
76
85
  try {
77
86
  inputElement?.focus();
78
- inputElement?.scrollIntoView({ behavior: 'smooth', block: 'center' });
87
+ // A scrollIntoView `behavior` is not a style, so no media query reaches it.
88
+ // 'auto' still brings the field into view -- it just arrives instead of gliding.
89
+ inputElement?.scrollIntoView({
90
+ behavior: prefersReducedMotion() ? 'auto' : 'smooth',
91
+ block: 'center'
92
+ });
79
93
  } catch (error) {
80
94
  console.error('Error focusing or scrolling inputElement:', error);
81
95
  }
@@ -107,7 +121,7 @@
107
121
  valueValidation === 'InProgress' &&
108
122
  value.length > 0 &&
109
123
  inputElement !== null &&
110
- inputElement !== document.activeElement
124
+ inputElement !== getActiveElement(inputElement)
111
125
  ) {
112
126
  return 'Invalid';
113
127
  }
@@ -302,7 +316,7 @@
302
316
  <div class="input-container {classes ?? ''}" class:input-error={showError && !actionInput}>
303
317
  {#if hasVisibleLabel}
304
318
  <label class="label" for={effectiveId}>
305
- {label}{#if mandatory}<span class="input-mandatory-asterisk" aria-hidden="true">*</span>{/if}
319
+ {label}{#if isRequired}<span class="input-mandatory-asterisk" aria-hidden="true">*</span>{/if}
306
320
  </label>
307
321
  {/if}
308
322
 
@@ -321,10 +335,10 @@
321
335
  aria-autocomplete={ariaAutocomplete}
322
336
  aria-controls={ariaControls}
323
337
  aria-activedescendant={ariaActivedescendant}
324
- aria-required={mandatory || null}
338
+ aria-required={isRequired || null}
325
339
  aria-invalid={showError && !actionInput ? 'true' : null}
326
340
  aria-describedby={describedBy || null}
327
- required={mandatory || null}
341
+ required={isRequired || null}
328
342
  {onfocus}
329
343
  onfocusout={_onFocusOut}
330
344
  oninput={handleOnInput}
@@ -359,10 +373,10 @@
359
373
  aria-autocomplete={ariaAutocomplete}
360
374
  aria-controls={ariaControls}
361
375
  aria-activedescendant={ariaActivedescendant}
362
- aria-required={mandatory || null}
376
+ aria-required={isRequired || null}
363
377
  aria-invalid={showError && !actionInput ? 'true' : null}
364
378
  aria-describedby={describedBy || null}
365
- required={mandatory || null}
379
+ required={isRequired || null}
366
380
  {onfocus}
367
381
  onfocusout={_onFocusOut}
368
382
  oninput={handleOnInput}
@@ -487,7 +501,7 @@
487
501
  visibility: var(--input-visibility, visible);
488
502
  text-align: var(--input-text-align, left);
489
503
  text-transform: var(--input-text-transform, none);
490
- color: var(--input-text-color);
504
+ color: var(--input-text-color, #333333);
491
505
  }
492
506
 
493
507
  textarea:focus,
@@ -619,7 +633,7 @@
619
633
  .input-char-count {
620
634
  align-self: flex-end;
621
635
  font-size: var(--input-char-count-size, 12px);
622
- color: var(--input-char-count-color, #98a2b3);
636
+ color: var(--input-char-count-color, #5c6b7a);
623
637
  margin: var(--input-char-count-margin, 4px 0 0);
624
638
  font-variant-numeric: tabular-nums;
625
639
  }
@@ -629,6 +643,6 @@
629
643
  }
630
644
 
631
645
  ::placeholder {
632
- color: var(--input-placeholder-color);
646
+ color: var(--input-placeholder-color, #5c6b7a);
633
647
  }
634
648
  </style>
@@ -7,6 +7,29 @@ export type MandatoryInputProperties = {
7
7
  };
8
8
  export type OptionalInputProperties = {
9
9
  placeholder?: string | null;
10
+ /**
11
+ * Native input type, and the switch `validateInput` runs on.
12
+ *
13
+ * **Only `text`, `tel`, `password` and `email` are validated.** `number`,
14
+ * `time`, `date`, `search` and `url` have no branch at all: they are always
15
+ * reported Valid, `onErrorMessage` never shows for them, and the browser's own
16
+ * constraint validation (`min`/`max`/`step`, the type's own parsing) is the
17
+ * only thing checking the value. Supply `validators` if you need more.
18
+ *
19
+ * `tel` with no `validationPattern` checks only the characters a phone number
20
+ * may contain -- digits, spaces, `+`, `-`, `.`, `()` -- and nothing about any
21
+ * national format, so `+33 6 12 34 56 78` and `(555) 123-4567` are accepted.
22
+ * Before 4.20 it silently required a ten-digit Indian mobile number. To ask for
23
+ * that rule, or any other market's, pass the preset's patterns:
24
+ *
25
+ * ```svelte
26
+ * <Input
27
+ * dataType="tel"
28
+ * validationPattern={TEL_PRESET_IN_MOBILE.valid}
29
+ * inProgressPattern={TEL_PRESET_IN_MOBILE.inProgress}
30
+ * />
31
+ * ```
32
+ */
10
33
  dataType?: InputDataType;
11
34
  label?: string | null;
12
35
  onErrorMessage?: string | null;
@@ -94,7 +117,20 @@ export type OptionalInputProperties = {
94
117
  leftIconLabel?: string;
95
118
  /** Accessible label for the clickable rightIcon button (defaults to a generic label). */
96
119
  rightIconLabel?: string;
97
- /** Appends a required asterisk beside the label and sets aria-required on the field. */
120
+ /**
121
+ * Appends a required asterisk beside the label and sets `aria-required` and the
122
+ * native `required` attribute on the field. This is the platform's own name and the
123
+ * one every other form control in this library uses, so it is the spelling to reach
124
+ * for. Wins over `mandatory` when both are supplied.
125
+ */
126
+ required?: boolean;
127
+ /**
128
+ * @deprecated Use `required`. This is the original spelling of the same concept and
129
+ * keeps working unchanged, but the library settled on the platform's name once the
130
+ * other form controls gained it. The `--input-mandatory-*` tokens and the
131
+ * `.input-mandatory-asterisk` class keep their names: those are a separate public
132
+ * surface, and renaming them would break every consumer theming the asterisk.
133
+ */
98
134
  mandatory?: boolean;
99
135
  /** Forces the error border independent of validationPattern (server/runtime-driven errors). */
100
136
  forceError?: boolean;
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { describeField } from '../_field/description';
2
3
  import Button from '../Button/Button.svelte';
3
4
  import Input from '../Input/Input.svelte';
4
5
  import type { InputButtonProperties } from './properties';
@@ -18,6 +19,7 @@
18
19
  rightIcon,
19
20
  classes,
20
21
  mandatory,
22
+ required,
21
23
  size,
22
24
  error,
23
25
  testId
@@ -53,6 +55,59 @@
53
55
  inputEventProperties?.onstatechange?.(state);
54
56
  }
55
57
 
58
+ /* `for` resolves against an element's id and never its name, so `for={name}`
59
+ never completed the association for any caller -- the same defect Input
60
+ fixed for itself. InputButton cannot read the id Input derives internally,
61
+ so it derives the identical one here and passes it down, which both
62
+ completes the label and keeps the two components agreeing on one id.
63
+ An explicit id still wins, for repeated rows that need uniqueness. */
64
+ const uid = $props.id();
65
+ const fieldId = $derived(
66
+ inputProperties.id ||
67
+ (typeof inputProperties.name === 'string' && inputProperties.name !== ''
68
+ ? `${inputProperties.name}-${uid}`
69
+ : uid)
70
+ );
71
+
72
+ /* `mandatory` drew an asterisk and stopped there: the Input underneath never
73
+ received it, so the field was not actually required and announced nothing.
74
+ Input maps both spellings to native `required` plus `aria-required`, and a
75
+ component whose own marker contradicts the control inside it is the worse
76
+ of the two behaviours to keep. `required` is the settled name; `mandatory`
77
+ stays as the deprecated alias. */
78
+ const isRequired = $derived(typeof required === 'boolean' ? required : mandatory === true);
79
+
80
+ /* InputButton renders the error and helper text ITSELF -- it passes
81
+ `actionInput={true}`, which is what suppresses Input's own copies -- and
82
+ those two divs carried no id, so nothing referenced them. A screen-reader
83
+ user heard the field and never the sentence explaining why it was rejected.
84
+
85
+ `describeField` composes the reference from the messages that are actually
86
+ rendered, so aria-describedby never points at an id that is not in the DOM.
87
+ It is keyed on `fieldId`, the id Input is given below, and that cannot
88
+ collide with Input's own `${id}-error` / `${id}-info`: `actionInput` is
89
+ hard-coded true here, so Input renders neither.
90
+
91
+ The existing precedence is preserved exactly -- an external `error` wins
92
+ over the internal validation message, which itself only shows once
93
+ validation has actually failed. */
94
+ const resolvedError = $derived(
95
+ typeof error === 'string' && error.length > 0
96
+ ? error
97
+ : validationState === 'Invalid' &&
98
+ typeof inputProperties.onErrorMessage === 'string' &&
99
+ inputProperties.onErrorMessage !== ''
100
+ ? inputProperties.onErrorMessage
101
+ : null
102
+ );
103
+ const field = $derived(
104
+ describeField(fieldId, {
105
+ error: resolvedError,
106
+ info: inputProperties.infoMessage,
107
+ invalid: validationState === 'Invalid'
108
+ })
109
+ );
110
+
56
111
  export function focus() {
57
112
  inputRef?.focus();
58
113
  }
@@ -62,15 +117,22 @@
62
117
  }
63
118
  </script>
64
119
 
120
+ <!-- The described element has to carry a role for the description to reach the
121
+ accessibility tree, and the field itself is inside `Input`, which exposes no
122
+ `aria-describedby` seam. The role is conditional so an InputButton with no
123
+ messages keeps the accessibility tree it had before. -->
65
124
  <div
66
125
  class="container {classes ?? ''}"
126
+ role={field.describedBy !== null || field.ariaInvalid !== null ? 'group' : null}
127
+ aria-describedby={field.describedBy}
128
+ aria-invalid={field.ariaInvalid}
67
129
  data-pw={typeof testId === 'string' ? testId : null}
68
130
  testID={typeof testId === 'string' ? testId : null}
69
131
  >
70
132
  {#if inputProperties.label && inputProperties.label !== ''}
71
- <label class="label" for={inputProperties.name}>
72
- {inputProperties.label}{#if mandatory}<span class="mandatory-marker" aria-label="required">
73
- *</span
133
+ <label class="label" for={fieldId}>
134
+ {inputProperties.label}{#if isRequired}<span class="mandatory-marker" aria-hidden="true"
135
+ >*</span
74
136
  >{/if}
75
137
  </label>
76
138
  {/if}
@@ -87,6 +149,8 @@
87
149
  <Input
88
150
  {...inputProperties}
89
151
  {...inputEventProperties}
152
+ id={fieldId}
153
+ required={isRequired}
90
154
  bind:value
91
155
  bind:this={inputRef}
92
156
  onstatechange={handleStateChange}
@@ -110,15 +174,26 @@
110
174
  </div>
111
175
  {/if}
112
176
  </div>
113
- {#if typeof error === 'string' && error.length > 0}
114
- <div class="external-error-message">{error}</div>
115
- {:else if typeof inputProperties.onErrorMessage === 'string' && inputProperties.onErrorMessage !== '' && validationState === 'Invalid'}
116
- <div class="error-message">
117
- {inputProperties.onErrorMessage}
177
+ {#if field.showsError}
178
+ <div
179
+ id={field.errorId}
180
+ role="alert"
181
+ class={typeof error === 'string' && error.length > 0
182
+ ? 'external-error-message'
183
+ : 'error-message'}
184
+ data-pw={typeof testId === 'string' ? `${testId}-error-message` : null}
185
+ testID={typeof testId === 'string' ? `${testId}-error-message` : null}
186
+ >
187
+ {resolvedError}
118
188
  </div>
119
189
  {/if}
120
- {#if typeof inputProperties.infoMessage === 'string' && inputProperties.infoMessage !== ''}
121
- <div class="info-message">
190
+ {#if field.showsInfo}
191
+ <div
192
+ id={field.infoId}
193
+ class="info-message"
194
+ data-pw={typeof testId === 'string' ? `${testId}-info-message` : null}
195
+ testID={typeof testId === 'string' ? `${testId}-info-message` : null}
196
+ >
122
197
  {inputProperties.infoMessage}
123
198
  </div>
124
199
  {/if}
@@ -182,7 +257,7 @@
182
257
  font-weight: var(--input-label-msg-text-weight, 400);
183
258
  font-size: var(--input-label-msg-text-size, 12px);
184
259
  color: var(--input-label-msg-text-color, #4d6174);
185
- line-height: var(--input-label-msg-text-line-height);
260
+ line-height: var(--input-label-msg-text-line-height, inherit);
186
261
  margin: var(--input-label-msg-text-margin, 0px 0px 6px 0px);
187
262
  }
188
263
 
@@ -22,7 +22,16 @@ export type OptionalInputButtonProperties = {
22
22
  leftIcon?: Snippet;
23
23
  rightIcon?: Snippet;
24
24
  classes?: string;
25
- /** When true, renders a red asterisk (*) next to the label to signal the field is required. */
25
+ /**
26
+ * Marks the field required: native `required` and `aria-required` on the
27
+ * input the user reaches, plus a decorative asterisk beside the label.
28
+ */
29
+ required?: boolean;
30
+ /**
31
+ * @deprecated Use `required`, the platform's name for this. Kept working as
32
+ * an alias. It previously drew the asterisk without making the field
33
+ * required; it now does both, matching the `Input` it wraps.
34
+ */
26
35
  mandatory?: boolean;
27
36
  /** Preset height/padding size variant. Overridable per-size via CSS variables. */
28
37
  size?: InputButtonSize;
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import type { LabelProperties } from './properties';
3
+
4
+ // `for` can't be a destructured binding name -- it's a reserved word -- so
5
+ // it's pulled in under `htmlFor` and re-emitted as the real `for` attribute
6
+ // below, which is what actually wires up the native label/control
7
+ // association.
8
+ let { for: htmlFor, required = false, children, testId, classes }: LabelProperties = $props();
9
+ </script>
10
+
11
+ <label
12
+ class="label {classes ?? ''}"
13
+ for={htmlFor}
14
+ data-pw={typeof testId === 'string' ? testId : null}
15
+ testID={typeof testId === 'string' ? testId : null}
16
+ >
17
+ {@render children?.()}
18
+ {#if required}
19
+ <span class="required-marker" aria-hidden="true">*</span>
20
+ <span class="sr-only">required</span>
21
+ {/if}
22
+ </label>
23
+
24
+ <style>
25
+ .label {
26
+ display: var(--label-display, inline-flex);
27
+ align-items: var(--label-align-items, center);
28
+ gap: var(--label-gap, 4px);
29
+ font-size: var(--label-font-size, 14px);
30
+ font-weight: var(--label-font-weight, 500);
31
+ color: var(--label-color, #333);
32
+ font-family: var(--label-font-family, inherit);
33
+ cursor: var(--label-cursor, pointer);
34
+ }
35
+
36
+ .required-marker {
37
+ color: var(--label-required-color, #d32f2f);
38
+ font-size: var(--label-required-font-size, inherit);
39
+ }
40
+
41
+ /* Visually hidden but still read by assistive technology -- same pattern
42
+ Table's caption uses. */
43
+ .sr-only {
44
+ position: absolute;
45
+ width: 1px;
46
+ height: 1px;
47
+ padding: 0;
48
+ overflow: hidden;
49
+ clip: rect(0, 0, 0, 0);
50
+ clip-path: inset(50%);
51
+ white-space: nowrap;
52
+ border-width: 0;
53
+ }
54
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const Label: import("svelte").Component<import("./properties").OptionalLabelProperties, {}, "">;
2
+ type Label = ReturnType<typeof Label>;
3
+ export default Label;
@@ -0,0 +1,26 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type LabelProperties = OptionalLabelProperties;
3
+ export type MandatoryLabelProperties = Record<string, never>;
4
+ export type OptionalLabelProperties = {
5
+ /**
6
+ * Id of the form control this label names. Rendered as the real `for`
7
+ * attribute on a real `<label>` element, so a click anywhere on the label
8
+ * focuses (and, for a checkbox/radio, activates) the control with this id
9
+ * -- the native association a `<span>` styled to look like a label can
10
+ * never give you. Omit it when the label instead wraps its control
11
+ * (implicit association), or has no associated control at all.
12
+ */
13
+ for?: string;
14
+ /**
15
+ * Marks the named field as required. Shown as a visual asterisk, but the
16
+ * asterisk alone is `aria-hidden` -- a screen reader would otherwise either
17
+ * skip it silently or read the bare glyph "asterisk", neither of which says
18
+ * "required". A visually-hidden `"required"` string is rendered alongside
19
+ * it so the requirement is actually announced, not just conveyed by shape.
20
+ */
21
+ required?: boolean;
22
+ /** The label's text/content. */
23
+ children?: Snippet;
24
+ testId?: string;
25
+ classes?: string;
26
+ };
@@ -0,0 +1 @@
1
+ export {};