@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,79 @@
1
+ export interface SearchOption {
2
+ value: string;
3
+ label: string;
4
+ description?: string;
5
+ disabled?: boolean;
6
+ }
7
+ export interface SearchGroup {
8
+ label: string;
9
+ options: SearchOption[];
10
+ }
11
+ type SearchSize = 'sm' | 'md' | 'lg';
12
+ interface Props {
13
+ /** Visible label. */
14
+ label: string;
15
+ /** Search query value (bindable). */
16
+ value?: string;
17
+ /** Flat list of options. */
18
+ options?: SearchOption[];
19
+ /** Grouped options. */
20
+ groups?: SearchGroup[];
21
+ /** Placeholder text. */
22
+ placeholder?: string;
23
+ /** Minimum characters before showing suggestions. */
24
+ minChars?: number;
25
+ /** Debounce delay in ms for the onsearch callback. */
26
+ debounceMs?: number;
27
+ /** Show loading spinner in dropdown. */
28
+ loading?: boolean;
29
+ /** Allow clearing the input. */
30
+ clearable?: boolean;
31
+ /** Size variant. */
32
+ size?: SearchSize;
33
+ /** Error message. */
34
+ error?: string;
35
+ /** Hint text. */
36
+ hint?: string;
37
+ /** Disabled state. */
38
+ disabled?: boolean;
39
+ /** Required field. */
40
+ required?: boolean;
41
+ /** Hide the visible label. */
42
+ hideLabel?: boolean;
43
+ /** Reserve footer space even when empty. */
44
+ alwaysShowFooter?: boolean;
45
+ /** Additional CSS class. */
46
+ class?: string;
47
+ /** Called when an option is selected. */
48
+ onselect?: (option: SearchOption) => void;
49
+ /** Called when search query changes (debounced). */
50
+ onsearch?: (query: string) => void;
51
+ /** Called when input value changes. */
52
+ oninput?: (event: Event) => void;
53
+ /** Called when input loses focus. */
54
+ onblur?: (event: FocusEvent) => void;
55
+ /** Test ID. */
56
+ testId?: string;
57
+ }
58
+ /**
59
+ * SearchInput
60
+ *
61
+ * INFRARED — Search input with autocomplete dropdown, match highlighting,
62
+ * grouped results, loading state, and keyboard navigation.
63
+ * Follows WAI-ARIA combobox pattern.
64
+ *
65
+ * @example
66
+ * <SearchInput
67
+ * label="Search models"
68
+ * options={[
69
+ * { value: 'opus', label: 'Claude Opus' },
70
+ * { value: 'sonnet', label: 'Claude Sonnet' },
71
+ * ]}
72
+ * bind:value={query}
73
+ * onselect={(opt) => console.log(opt)}
74
+ * />
75
+ */
76
+ declare const SearchInput: import("svelte").Component<Props, {}, "value">;
77
+ type SearchInput = ReturnType<typeof SearchInput>;
78
+ export default SearchInput;
79
+ //# sourceMappingURL=SearchInput.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/SearchInput/SearchInput.svelte.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB;AAQD,KAAK,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAErC,UAAU,KAAK;IACd,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,uBAAuB;IACvB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,oBAAoB;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,oDAAoD;IACpD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,uCAAuC;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,qCAAqC;IACrC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,eAAe;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAqZD;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,WAAW,gDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { default as SearchInput } from './SearchInput.svelte';
2
+ export type { SearchOption, SearchGroup } from './SearchInput.svelte';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/forms/SearchInput/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as SearchInput } from './SearchInput.svelte';
@@ -71,6 +71,8 @@ interface Props {
71
71
  onchangemulti?: (values: string[]) => void;
72
72
  /** Test ID */
73
73
  testId?: string;
74
+ /** Reserve footer space even when empty (prevents layout shift). */
75
+ alwaysShowFooter?: boolean;
74
76
  }
75
77
 
76
78
  let {
@@ -90,7 +92,8 @@ let {
90
92
  class: className = '',
91
93
  onchange,
92
94
  onchangemulti,
93
- testId
95
+ testId,
96
+ alwaysShowFooter = true
94
97
  }: Props = $props();
95
98
 
96
99
  const id = `select-${Math.random().toString(36).slice(2, 9)}`;
@@ -150,6 +153,7 @@ const showError = $derived(hasInteracted && !!error);
150
153
  const ariaDescribedBy = $derived(
151
154
  [showError && errorId, hint && hintId].filter(Boolean).join(' ') || undefined
152
155
  );
156
+ const hasFooterContent = $derived(showError || !!hint);
153
157
 
154
158
  function positionDropdown() {
155
159
  if (!triggerEl) return;
@@ -478,13 +482,15 @@ onMount(() => {
478
482
  </button>
479
483
 
480
484
  <!-- Footer: error / hint -->
481
- <div class="sx-select-footer">
482
- {#if showError}
483
- <p id={errorId} class="sx-select-error" role="alert" aria-live="assertive">{error}</p>
484
- {:else if hint}
485
- <p id={hintId} class="sx-select-hint">{hint}</p>
486
- {/if}
487
- </div>
485
+ {#if alwaysShowFooter || hasFooterContent}
486
+ <div class="sx-select-footer">
487
+ {#if showError}
488
+ <p id={errorId} class="sx-select-error" role="alert" aria-live="assertive">{error}</p>
489
+ {:else if hint}
490
+ <p id={hintId} class="sx-select-hint">{hint}</p>
491
+ {/if}
492
+ </div>
493
+ {/if}
488
494
  </div>
489
495
 
490
496
  <!-- Dropdown panel -- fixed positioning to escape overflow/stacking contexts -->
@@ -43,6 +43,8 @@ interface Props {
43
43
  onchangemulti?: (values: string[]) => void;
44
44
  /** Test ID */
45
45
  testId?: string;
46
+ /** Reserve footer space even when empty (prevents layout shift). */
47
+ alwaysShowFooter?: boolean;
46
48
  }
47
49
  /**
48
50
  * Select
@@ -1 +1 @@
1
- {"version":3,"file":"Select.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/Select/Select.svelte.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB;AAQD,KAAK,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAErC,UAAU,KAAK;IACd,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,wDAAwD;IACxD,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,6CAA6C;IAC7C,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAudD;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,MAAM,2DAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Select.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/Select/Select.svelte.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB;AAQD,KAAK,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAErC,UAAU,KAAK;IACd,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,wDAAwD;IACxD,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,6CAA6C;IAC7C,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC3C,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B;AA2dD;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,MAAM,2DAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -7,6 +7,7 @@
7
7
  <script lang="ts">
8
8
  import type { Snippet } from 'svelte';
9
9
  import type { HTMLInputAttributes } from 'svelte/elements';
10
+ import { onDestroy } from 'svelte';
10
11
  import { cn } from '../../utils/cn.js';
11
12
 
12
13
  type InputSize = 'sm' | 'md' | 'lg';
@@ -47,6 +48,12 @@ interface Props extends Omit<HTMLInputAttributes, 'class' | 'size' | 'inputmode'
47
48
  onclear?: () => void;
48
49
  testId?: string;
49
50
  validateOnMount?: boolean;
51
+ /** Debounce delay in ms. Fires ondebounce after typing pauses. */
52
+ debounceMs?: number;
53
+ /** Called after debounce delay with current value. */
54
+ ondebounce?: (value: string) => void;
55
+ /** Reserve footer space even when empty (prevents layout shift). */
56
+ alwaysShowFooter?: boolean;
50
57
  }
51
58
 
52
59
  let {
@@ -84,6 +91,9 @@ let {
84
91
  onclear,
85
92
  testId,
86
93
  validateOnMount = false,
94
+ debounceMs,
95
+ ondebounce,
96
+ alwaysShowFooter = true,
87
97
  ...restProps
88
98
  }: Props = $props();
89
99
 
@@ -91,6 +101,9 @@ let inputRef = $state<HTMLInputElement | undefined>();
91
101
  let isFocused = $state(false);
92
102
  let hasInteracted = $state(false);
93
103
  let passwordVisible = $state(false);
104
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined;
105
+
106
+ onDestroy(() => clearTimeout(debounceTimer));
94
107
 
95
108
  const shouldValidate = $derived(hasInteracted || validateOnMount);
96
109
  const showError = $derived(shouldValidate && !!error);
@@ -109,11 +122,18 @@ const hasRightContent = $derived(
109
122
  const ariaDescribedBy = $derived(
110
123
  [error && errorId, hint && hintId].filter(Boolean).join(' ') || undefined
111
124
  );
125
+ const hasFooterContent = $derived(
126
+ showError || showSuccess || !!hint || (showCharCount && maxLength !== undefined)
127
+ );
112
128
 
113
129
  function handleInput(e: Event) {
114
130
  const target = e.target as HTMLInputElement;
115
131
  value = target.value;
116
132
  oninput?.(e);
133
+ if (debounceMs && ondebounce) {
134
+ clearTimeout(debounceTimer);
135
+ debounceTimer = setTimeout(() => ondebounce!(value), debounceMs);
136
+ }
117
137
  }
118
138
 
119
139
  function handleBlur(e: FocusEvent) {
@@ -272,24 +292,26 @@ function handleKeyDown(e: KeyboardEvent) {
272
292
  {/if}
273
293
  </div>
274
294
 
275
- <div class="sx-input-footer">
276
- <div class="sx-input-messages">
277
- {#if showError}
278
- <p id={errorId} class="sx-input-message sx-input-message-error" role="alert" aria-live="assertive">
279
- {error}
280
- </p>
281
- {:else if showSuccess}
282
- <p class="sx-input-message sx-input-message-success" role="status" aria-live="polite">{successMessage}</p>
283
- {:else if hint}
284
- <p id={hintId} class="sx-input-message sx-input-message-hint">{hint}</p>
295
+ {#if alwaysShowFooter || hasFooterContent}
296
+ <div class="sx-input-footer" class:sx-input-footer-empty={!hasFooterContent}>
297
+ <div class="sx-input-messages">
298
+ {#if showError}
299
+ <p id={errorId} class="sx-input-message sx-input-message-error" role="alert" aria-live="assertive">
300
+ {error}
301
+ </p>
302
+ {:else if showSuccess}
303
+ <p class="sx-input-message sx-input-message-success" role="status" aria-live="polite">{successMessage}</p>
304
+ {:else if hint}
305
+ <p id={hintId} class="sx-input-message sx-input-message-hint">{hint}</p>
306
+ {/if}
307
+ </div>
308
+ {#if showCharCount && maxLength !== undefined}
309
+ <span class="sx-input-charcount" class:sx-input-charcount-warn={value.length > maxLength * 0.9}>
310
+ {value.length}/{maxLength}
311
+ </span>
285
312
  {/if}
286
313
  </div>
287
- {#if showCharCount && maxLength !== undefined}
288
- <span class="sx-input-charcount" class:sx-input-charcount-warn={value.length > maxLength * 0.9}>
289
- {value.length}/{maxLength}
290
- </span>
291
- {/if}
292
- </div>
314
+ {/if}
293
315
  </div>
294
316
 
295
317
  <style>
@@ -37,6 +37,12 @@ interface Props extends Omit<HTMLInputAttributes, 'class' | 'size' | 'inputmode'
37
37
  onclear?: () => void;
38
38
  testId?: string;
39
39
  validateOnMount?: boolean;
40
+ /** Debounce delay in ms. Fires ondebounce after typing pauses. */
41
+ debounceMs?: number;
42
+ /** Called after debounce delay with current value. */
43
+ ondebounce?: (value: string) => void;
44
+ /** Reserve footer space even when empty (prevents layout shift). */
45
+ alwaysShowFooter?: boolean;
40
46
  }
41
47
  /**
42
48
  * TextInput
@@ -1 +1 @@
1
- {"version":3,"file":"TextInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/TextInput/TextInput.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAI3D,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACpC,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9F,UAAU,KAAM,SAAQ,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,GAAG,cAAc,CAAC;IACjG,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAuND;;;;;GAKG;AACH,QAAA,MAAM,SAAS,gDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"TextInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/TextInput/TextInput.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAK3D,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACpC,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9F,UAAU,KAAM,SAAQ,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,GAAG,cAAc,CAAC;IACjG,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B;AAuOD;;;;;GAKG;AACH,QAAA,MAAM,SAAS,gDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -45,6 +45,8 @@ interface Props {
45
45
  onblur?: (event: FocusEvent) => void;
46
46
  onfocus?: (event: FocusEvent) => void;
47
47
  testId?: string;
48
+ /** Reserve footer space even when empty (prevents layout shift). */
49
+ alwaysShowFooter?: boolean;
48
50
  }
49
51
 
50
52
  let {
@@ -73,7 +75,8 @@ let {
73
75
  oninput,
74
76
  onblur,
75
77
  onfocus,
76
- testId
78
+ testId,
79
+ alwaysShowFooter = true
77
80
  }: Props = $props();
78
81
 
79
82
  let textareaEl = $state<HTMLTextAreaElement | null>(null);
@@ -95,6 +98,7 @@ const showHandle = $derived(effectiveResize !== 'none' && !disabled);
95
98
  const resizeVertical = $derived(effectiveResize === 'vertical' || effectiveResize === 'both');
96
99
  const resizeHorizontal = $derived(effectiveResize === 'horizontal' || effectiveResize === 'both');
97
100
 
101
+ const hasFooterContent = $derived(!!error || !!successMessage || !!hint || showCharCount);
98
102
  const describedBy = $derived(
99
103
  [
100
104
  error ? errorId : null,
@@ -297,6 +301,7 @@ onMount(() => {
297
301
  {/if}
298
302
  </div>
299
303
 
304
+ {#if alwaysShowFooter || hasFooterContent}
300
305
  <div class="sx-textarea-footer">
301
306
  <div class="sx-textarea-messages">
302
307
  {#if error}
@@ -332,6 +337,7 @@ onMount(() => {
332
337
  </span>
333
338
  {/if}
334
339
  </div>
340
+ {/if}
335
341
  </div>
336
342
 
337
343
  <style>
@@ -28,6 +28,8 @@ interface Props {
28
28
  onblur?: (event: FocusEvent) => void;
29
29
  onfocus?: (event: FocusEvent) => void;
30
30
  testId?: string;
31
+ /** Reserve footer space even when empty (prevents layout shift). */
32
+ alwaysShowFooter?: boolean;
31
33
  }
32
34
  /**
33
35
  * Textarea
@@ -1 +1 @@
1
- {"version":3,"file":"Textarea.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/Textarea/Textarea.svelte.ts"],"names":[],"mappings":"AAOA,KAAK,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC;AAE9D,UAAU,KAAK;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAuQD;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,QAAQ,gDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Textarea.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/Textarea/Textarea.svelte.ts"],"names":[],"mappings":"AAOA,KAAK,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC;AAE9D,UAAU,KAAK;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B;AA2QD;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,QAAQ,gDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}