@salmexio/ui 1.2.1 → 1.3.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 (78) hide show
  1. package/dist/feedback/Alert/Alert.svelte +4 -1
  2. package/dist/feedback/Alert/Alert.svelte.d.ts +1 -0
  3. package/dist/feedback/Alert/Alert.svelte.d.ts.map +1 -1
  4. package/dist/feedback/Spinner/Spinner.svelte +4 -1
  5. package/dist/feedback/Spinner/Spinner.svelte.d.ts +1 -0
  6. package/dist/feedback/Spinner/Spinner.svelte.d.ts.map +1 -1
  7. package/dist/forms/DatePicker/DatePicker.svelte +725 -0
  8. package/dist/forms/DatePicker/DatePicker.svelte.d.ts +48 -0
  9. package/dist/forms/DatePicker/DatePicker.svelte.d.ts.map +1 -0
  10. package/dist/forms/DatePicker/index.d.ts +2 -0
  11. package/dist/forms/DatePicker/index.d.ts.map +1 -0
  12. package/dist/forms/DatePicker/index.js +1 -0
  13. package/dist/forms/FormField/FormField.svelte +173 -0
  14. package/dist/forms/FormField/FormField.svelte.d.ts +46 -0
  15. package/dist/forms/FormField/FormField.svelte.d.ts.map +1 -0
  16. package/dist/forms/FormField/index.d.ts +2 -0
  17. package/dist/forms/FormField/index.d.ts.map +1 -0
  18. package/dist/forms/FormField/index.js +1 -0
  19. package/dist/forms/MultiSelect/MultiSelect.svelte +820 -0
  20. package/dist/forms/MultiSelect/MultiSelect.svelte.d.ts +69 -0
  21. package/dist/forms/MultiSelect/MultiSelect.svelte.d.ts.map +1 -0
  22. package/dist/forms/MultiSelect/index.d.ts +3 -0
  23. package/dist/forms/MultiSelect/index.d.ts.map +1 -0
  24. package/dist/forms/MultiSelect/index.js +1 -0
  25. package/dist/forms/PhoneInput/PhoneInput.svelte +591 -0
  26. package/dist/forms/PhoneInput/PhoneInput.svelte.d.ts +57 -0
  27. package/dist/forms/PhoneInput/PhoneInput.svelte.d.ts.map +1 -0
  28. package/dist/forms/PhoneInput/index.d.ts +4 -0
  29. package/dist/forms/PhoneInput/index.d.ts.map +1 -0
  30. package/dist/forms/PhoneInput/index.js +2 -0
  31. package/dist/forms/RadioGroup/RadioGroup.svelte +417 -0
  32. package/dist/forms/RadioGroup/RadioGroup.svelte.d.ts +62 -0
  33. package/dist/forms/RadioGroup/RadioGroup.svelte.d.ts.map +1 -0
  34. package/dist/forms/RadioGroup/index.d.ts +3 -0
  35. package/dist/forms/RadioGroup/index.d.ts.map +1 -0
  36. package/dist/forms/RadioGroup/index.js +1 -0
  37. package/dist/forms/SearchInput/SearchInput.svelte +788 -0
  38. package/dist/forms/SearchInput/SearchInput.svelte.d.ts +79 -0
  39. package/dist/forms/SearchInput/SearchInput.svelte.d.ts.map +1 -0
  40. package/dist/forms/SearchInput/index.d.ts +3 -0
  41. package/dist/forms/SearchInput/index.d.ts.map +1 -0
  42. package/dist/forms/SearchInput/index.js +1 -0
  43. package/dist/forms/Select/Select.svelte +14 -8
  44. package/dist/forms/Select/Select.svelte.d.ts +2 -0
  45. package/dist/forms/Select/Select.svelte.d.ts.map +1 -1
  46. package/dist/forms/TextInput/TextInput.svelte +38 -16
  47. package/dist/forms/TextInput/TextInput.svelte.d.ts +6 -0
  48. package/dist/forms/TextInput/TextInput.svelte.d.ts.map +1 -1
  49. package/dist/forms/Textarea/Textarea.svelte +7 -1
  50. package/dist/forms/Textarea/Textarea.svelte.d.ts +2 -0
  51. package/dist/forms/Textarea/Textarea.svelte.d.ts.map +1 -1
  52. package/dist/forms/TimePicker/TimePicker.svelte +417 -0
  53. package/dist/forms/TimePicker/TimePicker.svelte.d.ts +53 -0
  54. package/dist/forms/TimePicker/TimePicker.svelte.d.ts.map +1 -0
  55. package/dist/forms/TimePicker/index.d.ts +2 -0
  56. package/dist/forms/TimePicker/index.d.ts.map +1 -0
  57. package/dist/forms/TimePicker/index.js +1 -0
  58. package/dist/forms/index.d.ts +12 -0
  59. package/dist/forms/index.d.ts.map +1 -1
  60. package/dist/forms/index.js +8 -0
  61. package/dist/layout/Container/Container.svelte +3 -0
  62. package/dist/layout/Container/Container.svelte.d.ts +1 -0
  63. package/dist/layout/Container/Container.svelte.d.ts.map +1 -1
  64. package/dist/primitives/Badge/Badge.svelte +5 -1
  65. package/dist/primitives/Badge/Badge.svelte.d.ts +1 -0
  66. package/dist/primitives/Badge/Badge.svelte.d.ts.map +1 -1
  67. package/dist/primitives/Tooltip/Tooltip.svelte +7 -1
  68. package/dist/primitives/Tooltip/Tooltip.svelte.d.ts.map +1 -1
  69. package/dist/utils/accessibility.d.ts +16 -0
  70. package/dist/utils/accessibility.d.ts.map +1 -0
  71. package/dist/utils/accessibility.js +80 -0
  72. package/dist/utils/index.d.ts +2 -1
  73. package/dist/utils/index.d.ts.map +1 -1
  74. package/dist/utils/index.js +2 -1
  75. package/dist/utils/keyboard.d.ts +6 -0
  76. package/dist/utils/keyboard.d.ts.map +1 -1
  77. package/dist/utils/keyboard.js +15 -9
  78. package/package.json +21 -1
@@ -0,0 +1,417 @@
1
+ <!--
2
+ @component TimePicker
3
+
4
+ INFRARED — Time input with hour/minute fields, optional seconds,
5
+ 12/24-hour format, and keyboard increment support.
6
+ Output in HH:MM or HH:MM:SS format.
7
+
8
+ @example
9
+ <TimePicker label="Start time" bind:value={time} />
10
+ <TimePicker label="Alarm" format="12h" step={15} />
11
+ -->
12
+ <script lang="ts">
13
+ import { cn } from '../../utils/cn.js';
14
+ import { generateId } from '../../utils/keyboard.js';
15
+
16
+ type TimeSize = 'sm' | 'md' | 'lg';
17
+ type TimeFormat = '12h' | '24h';
18
+
19
+ interface Props {
20
+ /** Visible label. */
21
+ label: string;
22
+ /** Time value in HH:MM or HH:MM:SS format (24h). */
23
+ value?: string;
24
+ /** Time format display. */
25
+ format?: TimeFormat;
26
+ /** Minute step increment. */
27
+ step?: number;
28
+ /** Show seconds field. */
29
+ showSeconds?: boolean;
30
+ /** Minimum time (HH:MM). */
31
+ min?: string;
32
+ /** Maximum time (HH:MM). */
33
+ max?: string;
34
+ /** Size variant. */
35
+ size?: TimeSize;
36
+ /** Required field. */
37
+ required?: boolean;
38
+ /** Disabled state. */
39
+ disabled?: boolean;
40
+ /** Error message. */
41
+ error?: string;
42
+ /** Hint text. */
43
+ hint?: string;
44
+ /** Hide the visible label. */
45
+ hideLabel?: boolean;
46
+ /** Reserve footer space even when empty. */
47
+ alwaysShowFooter?: boolean;
48
+ /** Additional CSS class. */
49
+ class?: string;
50
+ /** Called when time changes. */
51
+ onchange?: (value: string) => void;
52
+ /** Test ID. */
53
+ testId?: string;
54
+ }
55
+
56
+ let {
57
+ label,
58
+ value = $bindable(''),
59
+ format = '24h',
60
+ step = 1,
61
+ showSeconds = false,
62
+ min,
63
+ max,
64
+ size = 'md',
65
+ required = false,
66
+ disabled = false,
67
+ error = '',
68
+ hint = '',
69
+ hideLabel = false,
70
+ alwaysShowFooter = true,
71
+ class: className = '',
72
+ onchange,
73
+ testId
74
+ }: Props = $props();
75
+
76
+ const id = generateId('timepicker');
77
+ const errorId = `${id}-error`;
78
+ const hintId = `${id}-hint`;
79
+
80
+ // Guard: step must be a positive integer
81
+ const safeStep = $derived(Math.max(1, Math.round(step)));
82
+
83
+ let hasInteracted = $state(false);
84
+
85
+ const showError = $derived(hasInteracted && !!error);
86
+ const ariaDescribedBy = $derived(
87
+ [showError && errorId, hint && hintId].filter(Boolean).join(' ') || undefined
88
+ );
89
+ const hasFooterContent = $derived(showError || !!hint);
90
+
91
+ // Parse value into parts
92
+ const parsed = $derived.by(() => {
93
+ const parts = (value || '00:00:00').split(':');
94
+ return {
95
+ hours: parseInt(parts[0] || '0', 10),
96
+ minutes: parseInt(parts[1] || '0', 10),
97
+ seconds: parseInt(parts[2] || '0', 10)
98
+ };
99
+ });
100
+
101
+ const displayHours = $derived.by(() => {
102
+ if (format === '12h') {
103
+ const h = parsed.hours % 12;
104
+ return h === 0 ? 12 : h;
105
+ }
106
+ return parsed.hours;
107
+ });
108
+
109
+ const period = $derived<'AM' | 'PM'>(parsed.hours >= 12 ? 'PM' : 'AM');
110
+
111
+ function pad(n: number): string {
112
+ return String(n).padStart(2, '0');
113
+ }
114
+
115
+ function buildValue(h: number, m: number, s: number): string {
116
+ h = Math.max(0, Math.min(23, h));
117
+ m = Math.max(0, Math.min(59, m));
118
+ s = Math.max(0, Math.min(59, s));
119
+ return showSeconds ? `${pad(h)}:${pad(m)}:${pad(s)}` : `${pad(h)}:${pad(m)}`;
120
+ }
121
+
122
+ function normalizeTimeStr(t: string): string {
123
+ const parts = t.split(':');
124
+ const h = (parts[0] || '00').padStart(2, '0');
125
+ const m = (parts[1] || '00').padStart(2, '0');
126
+ const s = (parts[2] || '00').padStart(2, '0');
127
+ return showSeconds ? `${h}:${m}:${s}` : `${h}:${m}`;
128
+ }
129
+
130
+ function clampToRange(val: string): string {
131
+ if (min) {
132
+ const normMin = normalizeTimeStr(min);
133
+ if (val < normMin) return normMin;
134
+ }
135
+ if (max) {
136
+ const normMax = normalizeTimeStr(max);
137
+ if (val > normMax) return normMax;
138
+ }
139
+ return val;
140
+ }
141
+
142
+ function updateValue(h: number, m: number, s: number) {
143
+ const newVal = clampToRange(buildValue(h, m, s));
144
+ value = newVal;
145
+ hasInteracted = true;
146
+ onchange?.(newVal);
147
+ }
148
+
149
+ function handleHourInput(e: Event) {
150
+ const target = e.target as HTMLInputElement;
151
+ let h = parseInt(target.value, 10);
152
+ if (isNaN(h)) return;
153
+ if (format === '12h') {
154
+ if (period === 'PM' && h < 12) h += 12;
155
+ else if (period === 'AM' && h === 12) h = 0;
156
+ }
157
+ updateValue(h, parsed.minutes, parsed.seconds);
158
+ }
159
+
160
+ function handleMinuteInput(e: Event) {
161
+ const target = e.target as HTMLInputElement;
162
+ let m = parseInt(target.value, 10);
163
+ if (isNaN(m)) return;
164
+ m = Math.round(m / safeStep) * safeStep;
165
+ updateValue(parsed.hours, m, parsed.seconds);
166
+ }
167
+
168
+ function handleSecondInput(e: Event) {
169
+ const target = e.target as HTMLInputElement;
170
+ const s = parseInt(target.value, 10);
171
+ if (isNaN(s)) return;
172
+ updateValue(parsed.hours, parsed.minutes, s);
173
+ }
174
+
175
+ function togglePeriod() {
176
+ const h = parsed.hours >= 12 ? parsed.hours - 12 : parsed.hours + 12;
177
+ updateValue(h, parsed.minutes, parsed.seconds);
178
+ }
179
+
180
+ function handleKeydown(e: KeyboardEvent, field: 'hour' | 'minute' | 'second') {
181
+ const delta = e.key === 'ArrowUp' ? 1 : e.key === 'ArrowDown' ? -1 : 0;
182
+ if (!delta) return;
183
+ e.preventDefault();
184
+
185
+ let { hours, minutes, seconds } = parsed;
186
+ if (field === 'hour') {
187
+ hours = (hours + delta + 24) % 24;
188
+ } else if (field === 'minute') {
189
+ minutes = (minutes + delta * safeStep + 60) % 60;
190
+ } else {
191
+ seconds = (seconds + delta + 60) % 60;
192
+ }
193
+ updateValue(hours, minutes, seconds);
194
+ }
195
+ </script>
196
+
197
+ <div
198
+ class={cn('sx-timepicker', `sx-timepicker-${size}`, disabled && 'sx-timepicker-disabled', className)}
199
+ data-testid={testId}
200
+ >
201
+ <label for={`${id}-hour`} class={cn('sx-timepicker-label', hideLabel && 'sx-sr-only')}>
202
+ {label}
203
+ {#if required}
204
+ <span class="sx-timepicker-required" aria-hidden="true">*</span>
205
+ {/if}
206
+ </label>
207
+
208
+ <div
209
+ class={cn(
210
+ 'sx-timepicker-field',
211
+ showError && 'sx-timepicker-field-error'
212
+ )}
213
+ role="group"
214
+ aria-label={label}
215
+ aria-describedby={ariaDescribedBy}
216
+ >
217
+ <span class="sx-timepicker-icon" aria-hidden="true">
218
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
219
+ <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
220
+ </svg>
221
+ </span>
222
+
223
+ <input
224
+ id={`${id}-hour`}
225
+ type="number"
226
+ class="sx-timepicker-input"
227
+ value={pad(displayHours)}
228
+ min={format === '12h' ? 1 : 0}
229
+ max={format === '12h' ? 12 : 23}
230
+ {disabled}
231
+ aria-label="Hours"
232
+ oninput={handleHourInput}
233
+ onkeydown={(e) => handleKeydown(e, 'hour')}
234
+ />
235
+
236
+ <span class="sx-timepicker-sep" aria-hidden="true">:</span>
237
+
238
+ <input
239
+ id={`${id}-minute`}
240
+ type="number"
241
+ class="sx-timepicker-input"
242
+ value={pad(parsed.minutes)}
243
+ min={0}
244
+ max={59}
245
+ step={safeStep}
246
+ {disabled}
247
+ aria-label="Minutes"
248
+ oninput={handleMinuteInput}
249
+ onkeydown={(e) => handleKeydown(e, 'minute')}
250
+ />
251
+
252
+ {#if showSeconds}
253
+ <span class="sx-timepicker-sep" aria-hidden="true">:</span>
254
+ <input
255
+ id={`${id}-second`}
256
+ type="number"
257
+ class="sx-timepicker-input"
258
+ value={pad(parsed.seconds)}
259
+ min={0}
260
+ max={59}
261
+ {disabled}
262
+ aria-label="Seconds"
263
+ oninput={handleSecondInput}
264
+ onkeydown={(e) => handleKeydown(e, 'second')}
265
+ />
266
+ {/if}
267
+
268
+ {#if format === '12h'}
269
+ <button
270
+ type="button"
271
+ class="sx-timepicker-period"
272
+ {disabled}
273
+ aria-label={`Switch to ${period === 'AM' ? 'PM' : 'AM'}`}
274
+ onclick={togglePeriod}
275
+ >
276
+ {period}
277
+ </button>
278
+ {/if}
279
+ </div>
280
+
281
+ {#if alwaysShowFooter || hasFooterContent}
282
+ <div class="sx-timepicker-footer">
283
+ {#if showError}
284
+ <p id={errorId} class="sx-timepicker-error" role="alert" aria-live="assertive">{error}</p>
285
+ {:else if hint}
286
+ <p id={hintId} class="sx-timepicker-hint">{hint}</p>
287
+ {/if}
288
+ </div>
289
+ {/if}
290
+ </div>
291
+
292
+ <style>
293
+ .sx-timepicker {
294
+ display: flex;
295
+ flex-direction: column;
296
+ gap: var(--sx-space-1);
297
+ font-family: var(--sx-font-body);
298
+ }
299
+
300
+ .sx-timepicker-disabled { opacity: 0.5; }
301
+
302
+ .sx-timepicker-label {
303
+ font-size: var(--sx-text-sm);
304
+ font-weight: 500;
305
+ color: var(--sx-color-text-secondary);
306
+ }
307
+
308
+ .sx-timepicker-required { color: var(--sx-color-red); margin-left: 2px; }
309
+
310
+ .sx-sr-only {
311
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
312
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
313
+ }
314
+
315
+ .sx-timepicker-field {
316
+ display: inline-flex;
317
+ align-items: center;
318
+ gap: var(--sx-space-1);
319
+ border: 1px solid var(--sx-color-border-strong);
320
+ border-radius: var(--sx-radius-md);
321
+ background: var(--sx-color-surface);
322
+ transition: border-color var(--sx-transition-fast), box-shadow var(--sx-transition-fast);
323
+ box-shadow:
324
+ inset 0 1px 3px rgba(0, 0, 0, 0.3),
325
+ inset 0 0 0 1px rgba(0, 0, 0, 0.06);
326
+ }
327
+
328
+ .sx-timepicker-field:focus-within {
329
+ border-color: var(--sx-color-primary);
330
+ box-shadow:
331
+ inset 0 1px 2px rgba(0, 0, 0, 0.2),
332
+ 0 0 0 3px var(--sx-color-primary-ring);
333
+ }
334
+
335
+ .sx-timepicker-field-error {
336
+ border-color: var(--sx-color-red);
337
+ box-shadow:
338
+ inset 0 1px 2px rgba(0, 0, 0, 0.2),
339
+ 0 0 0 3px var(--sx-color-red-ring);
340
+ }
341
+
342
+ .sx-timepicker-sm .sx-timepicker-field { min-height: 32px; padding: 0 var(--sx-space-2); }
343
+ .sx-timepicker-md .sx-timepicker-field { min-height: 40px; padding: 0 var(--sx-space-3); }
344
+ .sx-timepicker-lg .sx-timepicker-field { min-height: 48px; padding: 0 var(--sx-space-4); }
345
+
346
+ .sx-timepicker-icon {
347
+ flex-shrink: 0;
348
+ display: flex;
349
+ align-items: center;
350
+ color: var(--sx-color-text-secondary);
351
+ }
352
+
353
+ .sx-timepicker-input {
354
+ width: 32px;
355
+ border: none;
356
+ background: transparent;
357
+ color: var(--sx-color-text);
358
+ font-family: var(--sx-font-mono);
359
+ font-size: var(--sx-text-sm);
360
+ font-weight: 600;
361
+ text-align: center;
362
+ outline: none;
363
+ appearance: textfield;
364
+ -moz-appearance: textfield;
365
+ padding: var(--sx-space-1) 0;
366
+ }
367
+
368
+ .sx-timepicker-input::-webkit-inner-spin-button,
369
+ .sx-timepicker-input::-webkit-outer-spin-button {
370
+ -webkit-appearance: none;
371
+ margin: 0;
372
+ }
373
+
374
+ .sx-timepicker-input:focus {
375
+ background: var(--sx-color-surface-2);
376
+ border-radius: var(--sx-radius-sm);
377
+ }
378
+
379
+ .sx-timepicker-sep {
380
+ color: var(--sx-color-text-disabled);
381
+ font-family: var(--sx-font-mono);
382
+ font-size: var(--sx-text-sm);
383
+ font-weight: 600;
384
+ }
385
+
386
+ .sx-timepicker-period {
387
+ border: none;
388
+ background: var(--sx-color-surface-2);
389
+ border-radius: var(--sx-radius-sm);
390
+ color: var(--sx-color-primary);
391
+ font-family: var(--sx-font-mono);
392
+ font-size: var(--sx-text-xs);
393
+ font-weight: 700;
394
+ padding: var(--sx-space-1) var(--sx-space-2);
395
+ cursor: pointer;
396
+ transition: background var(--sx-transition-fast);
397
+ margin-left: var(--sx-space-1);
398
+ }
399
+
400
+ .sx-timepicker-period:hover {
401
+ background: var(--sx-color-primary-subtle);
402
+ }
403
+
404
+ .sx-timepicker-period:focus-visible {
405
+ outline: 2px solid var(--sx-color-primary);
406
+ outline-offset: 1px;
407
+ }
408
+
409
+ /* Footer */
410
+ .sx-timepicker-footer { min-height: 1.25rem; }
411
+ .sx-timepicker-error { font-size: var(--sx-text-xs); font-weight: 500; color: var(--sx-color-red); margin: 0; }
412
+ .sx-timepicker-hint { font-size: var(--sx-text-xs); color: var(--sx-color-text-secondary); margin: 0; }
413
+
414
+ @media (prefers-reduced-motion: reduce) {
415
+ .sx-timepicker-field, .sx-timepicker-period { transition: none; }
416
+ }
417
+ </style>
@@ -0,0 +1,53 @@
1
+ type TimeSize = 'sm' | 'md' | 'lg';
2
+ type TimeFormat = '12h' | '24h';
3
+ interface Props {
4
+ /** Visible label. */
5
+ label: string;
6
+ /** Time value in HH:MM or HH:MM:SS format (24h). */
7
+ value?: string;
8
+ /** Time format display. */
9
+ format?: TimeFormat;
10
+ /** Minute step increment. */
11
+ step?: number;
12
+ /** Show seconds field. */
13
+ showSeconds?: boolean;
14
+ /** Minimum time (HH:MM). */
15
+ min?: string;
16
+ /** Maximum time (HH:MM). */
17
+ max?: string;
18
+ /** Size variant. */
19
+ size?: TimeSize;
20
+ /** Required field. */
21
+ required?: boolean;
22
+ /** Disabled state. */
23
+ disabled?: boolean;
24
+ /** Error message. */
25
+ error?: string;
26
+ /** Hint text. */
27
+ hint?: string;
28
+ /** Hide the visible label. */
29
+ hideLabel?: boolean;
30
+ /** Reserve footer space even when empty. */
31
+ alwaysShowFooter?: boolean;
32
+ /** Additional CSS class. */
33
+ class?: string;
34
+ /** Called when time changes. */
35
+ onchange?: (value: string) => void;
36
+ /** Test ID. */
37
+ testId?: string;
38
+ }
39
+ /**
40
+ * TimePicker
41
+ *
42
+ * INFRARED — Time input with hour/minute fields, optional seconds,
43
+ * 12/24-hour format, and keyboard increment support.
44
+ * Output in HH:MM or HH:MM:SS format.
45
+ *
46
+ * @example
47
+ * <TimePicker label="Start time" bind:value={time} />
48
+ * <TimePicker label="Alarm" format="12h" step={15} />
49
+ */
50
+ declare const TimePicker: import("svelte").Component<Props, {}, "value">;
51
+ type TimePicker = ReturnType<typeof TimePicker>;
52
+ export default TimePicker;
53
+ //# sourceMappingURL=TimePicker.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimePicker.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/TimePicker/TimePicker.svelte.ts"],"names":[],"mappings":"AAOA,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACnC,KAAK,UAAU,GAAG,KAAK,GAAG,KAAK,CAAC;AAEhC,UAAU,KAAK;IACd,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,eAAe;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAyMD;;;;;;;;;;GAUG;AACH,QAAA,MAAM,UAAU,gDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default as TimePicker } from './TimePicker.svelte';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/forms/TimePicker/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as TimePicker } from './TimePicker.svelte';
@@ -5,4 +5,16 @@ export type { SelectOption, SelectGroup } from './Select/index.js';
5
5
  export { Textarea } from './Textarea/index.js';
6
6
  export { Slider } from './Slider/index.js';
7
7
  export { Toggle } from './Toggle/index.js';
8
+ export { FormField } from './FormField/index.js';
9
+ export { RadioGroup } from './RadioGroup/index.js';
10
+ export type { RadioOption } from './RadioGroup/index.js';
11
+ export { SearchInput } from './SearchInput/index.js';
12
+ export type { SearchOption, SearchGroup } from './SearchInput/index.js';
13
+ export { MultiSelect } from './MultiSelect/index.js';
14
+ export type { MultiSelectOption, MultiSelectGroup } from './MultiSelect/index.js';
15
+ export { DatePicker } from './DatePicker/index.js';
16
+ export { TimePicker } from './TimePicker/index.js';
17
+ export { PhoneInput } from './PhoneInput/index.js';
18
+ export type { Country } from './PhoneInput/index.js';
19
+ export { DEFAULT_COUNTRIES } from './PhoneInput/index.js';
8
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,YAAY,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -4,3 +4,11 @@ export { Select } from './Select/index.js';
4
4
  export { Textarea } from './Textarea/index.js';
5
5
  export { Slider } from './Slider/index.js';
6
6
  export { Toggle } from './Toggle/index.js';
7
+ export { FormField } from './FormField/index.js';
8
+ export { RadioGroup } from './RadioGroup/index.js';
9
+ export { SearchInput } from './SearchInput/index.js';
10
+ export { MultiSelect } from './MultiSelect/index.js';
11
+ export { DatePicker } from './DatePicker/index.js';
12
+ export { TimePicker } from './TimePicker/index.js';
13
+ export { PhoneInput } from './PhoneInput/index.js';
14
+ export { DEFAULT_COUNTRIES } from './PhoneInput/index.js';
@@ -22,6 +22,7 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'class'> {
22
22
  id?: string;
23
23
  class?: string;
24
24
  children?: Snippet;
25
+ testId?: string;
25
26
  }
26
27
 
27
28
  let {
@@ -34,6 +35,7 @@ let {
34
35
  id,
35
36
  class: className = '',
36
37
  children,
38
+ testId,
37
39
  ...restProps
38
40
  }: Props = $props();
39
41
 
@@ -52,6 +54,7 @@ const resolvedPaddingY = $derived(paddingY ?? 'none');
52
54
  centered && 'sx-container-centered',
53
55
  className
54
56
  )}
57
+ data-testid={testId}
55
58
  {...restProps}
56
59
  >
57
60
  {#if children}
@@ -12,6 +12,7 @@ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'class'> {
12
12
  id?: string;
13
13
  class?: string;
14
14
  children?: Snippet;
15
+ testId?: string;
15
16
  }
16
17
  /**
17
18
  * Container
@@ -1 +1 @@
1
- {"version":3,"file":"Container.svelte.d.ts","sourceRoot":"","sources":["../../../src/layout/Container/Container.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD,KAAK,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;AACjF,KAAK,gBAAgB,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3D,UAAU,KAAM,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACpE,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IAC5C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AA4CD;;;;;GAKG;AACH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"Container.svelte.d.ts","sourceRoot":"","sources":["../../../src/layout/Container/Container.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD,KAAK,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;AACjF,KAAK,gBAAgB,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3D,UAAU,KAAM,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACpE,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IAC5C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AA6CD;;;;;GAKG;AACH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -30,6 +30,7 @@ interface Props {
30
30
  children?: Snippet;
31
31
  /** Accessible label when dot (required when dot for a11y) */
32
32
  'aria-label'?: string;
33
+ testId?: string;
33
34
  }
34
35
 
35
36
  let {
@@ -39,7 +40,8 @@ let {
39
40
  icon,
40
41
  class: className = '',
41
42
  children,
42
- 'aria-label': ariaLabel
43
+ 'aria-label': ariaLabel,
44
+ testId
43
45
  }: Props = $props();
44
46
  </script>
45
47
 
@@ -54,6 +56,7 @@ let {
54
56
  )}
55
57
  role="status"
56
58
  aria-label={ariaLabel ?? status}
59
+ data-testid={testId}
57
60
  >
58
61
  <span class="sx-sr-only">{ariaLabel ?? status}</span>
59
62
  </span>
@@ -66,6 +69,7 @@ let {
66
69
  className
67
70
  )}
68
71
  role="status"
72
+ data-testid={testId}
69
73
  >
70
74
  {#if icon}
71
75
  <span class="sx-badge-icon" aria-hidden="true">
@@ -16,6 +16,7 @@ interface Props {
16
16
  children?: Snippet;
17
17
  /** Accessible label when dot (required when dot for a11y) */
18
18
  'aria-label'?: string;
19
+ testId?: string;
19
20
  }
20
21
  /**
21
22
  * Badge
@@ -1 +1 @@
1
- {"version":3,"file":"Badge.svelte.d.ts","sourceRoot":"","sources":["../../../src/primitives/Badge/Badge.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAItC,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACpC,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhG,UAAU,KAAK;IACd,gCAAgC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW;IACX,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,iEAAiE;IACjE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAoDD;;;;;;;;;GASG;AACH,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Badge.svelte.d.ts","sourceRoot":"","sources":["../../../src/primitives/Badge/Badge.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAItC,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACpC,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAEhG,UAAU,KAAK;IACd,gCAAgC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW;IACX,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,iEAAiE;IACjE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAqDD;;;;;;;;;GASG;AACH,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -147,7 +147,13 @@ function positionTooltip() {
147
147
  $effect(() => {
148
148
  if (tooltipEl && visible) {
149
149
  document.body.appendChild(tooltipEl);
150
- tick().then(() => positionTooltip());
150
+ // Position only after the node is in body and laid out (showcase-style behavior)
151
+ const run = () => {
152
+ tick().then(() => {
153
+ positionTooltip();
154
+ });
155
+ };
156
+ requestAnimationFrame(run);
151
157
  return () => {
152
158
  if (tooltipEl?.parentNode === document.body) {
153
159
  document.body.removeChild(tooltipEl);
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.svelte.d.ts","sourceRoot":"","sources":["../../../src/primitives/Tooltip/Tooltip.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAMtC,KAAK,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAErD,UAAU,KAAK;IACd,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAgKD;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"Tooltip.svelte.d.ts","sourceRoot":"","sources":["../../../src/primitives/Tooltip/Tooltip.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAMtC,KAAK,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAErD,UAAU,KAAK;IACd,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAsKD;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Accessibility utilities for WCAG 2.1 AA compliance.
3
+ * Focus management, screen reader announcements, media query helpers.
4
+ */
5
+ /** Check whether an element can receive focus. */
6
+ export declare function isElementFocusable(el: HTMLElement): boolean;
7
+ /** Get all focusable elements within a container, in DOM order. */
8
+ export declare function getFocusableElements(container: HTMLElement): HTMLElement[];
9
+ /**
10
+ * Announce a message to screen readers via an ARIA live region.
11
+ * Creates the region lazily and reuses it across calls.
12
+ */
13
+ export declare function announce(message: string, priority?: 'polite' | 'assertive'): void;
14
+ /** Check if the user prefers reduced motion. */
15
+ export declare function prefersReducedMotion(): boolean;
16
+ //# sourceMappingURL=accessibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../src/utils/accessibility.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH,kDAAkD;AAClD,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,CAe3D;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,WAAW,GAAG,WAAW,EAAE,CAI1E;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACvB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,QAAQ,GAAG,WAAsB,GACzC,IAAI,CAgCN;AAED,gDAAgD;AAChD,wBAAgB,oBAAoB,IAAI,OAAO,CAG9C"}