@rkosafo/cai.components 0.0.75 → 0.0.78

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 (127) hide show
  1. package/README.md +8 -8
  2. package/dist/baseEditor/index.svelte +32 -32
  3. package/dist/builders/filters/FilterBuilder.svelte +641 -641
  4. package/dist/forms/FormCheckbox/FormCheckbox.svelte +53 -53
  5. package/dist/forms/FormClEditor/ClEdito.svelte +68 -68
  6. package/dist/forms/FormDatepicker/FormDatepicker.svelte +159 -159
  7. package/dist/forms/FormFileUpload/FormFileUplad.svelte +134 -134
  8. package/dist/forms/FormInput/FormInput.svelte +87 -87
  9. package/dist/forms/FormRadio/FormRadio.svelte +53 -53
  10. package/dist/forms/FormSelect/FormSelect.svelte +88 -88
  11. package/dist/forms/FormTextarea/FormTextarea.svelte +78 -78
  12. package/dist/forms/button-toggle/ButtonToggle.svelte +119 -0
  13. package/dist/forms/button-toggle/ButtonToggle.svelte.d.ts +139 -0
  14. package/dist/forms/button-toggle/ButtonToggleGroup.svelte +0 -0
  15. package/dist/forms/button-toggle/ButtonToggleGroup.svelte.d.ts +26 -0
  16. package/dist/forms/button-toggle/CheckIcon.svelte +28 -0
  17. package/dist/forms/button-toggle/CheckIcon.svelte.d.ts +4 -0
  18. package/dist/forms/button-toggle/index.d.ts +4 -0
  19. package/dist/forms/button-toggle/index.js +4 -0
  20. package/dist/forms/button-toggle/theme.d.ts +347 -0
  21. package/dist/forms/button-toggle/theme.js +129 -0
  22. package/dist/forms/checkbox/Checkbox.svelte +82 -82
  23. package/dist/forms/checkbox/CheckboxButton.svelte +92 -92
  24. package/dist/forms/datepicker/Datepicker.svelte +707 -707
  25. package/dist/forms/form/Form.svelte +69 -69
  26. package/dist/forms/input/Input.svelte +363 -363
  27. package/dist/forms/label/Label.svelte +38 -38
  28. package/dist/forms/radio/Radio.svelte +48 -48
  29. package/dist/forms/radio/RadioButton.svelte +22 -22
  30. package/dist/forms/select/Select.svelte +56 -56
  31. package/dist/forms/textarea/Textarea.svelte +165 -165
  32. package/dist/forms/toggle/Toggle.svelte +70 -0
  33. package/dist/forms/toggle/Toggle.svelte.d.ts +3 -0
  34. package/dist/forms/toggle/index.d.ts +2 -0
  35. package/dist/forms/toggle/index.js +2 -0
  36. package/dist/forms/toggle/theme.d.ts +280 -0
  37. package/dist/forms/toggle/theme.js +97 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.js +3 -0
  40. package/dist/layout/Chat/CategorySelector.svelte +52 -52
  41. package/dist/layout/Chat/ChatEntry.svelte +246 -246
  42. package/dist/layout/Chat/ChatEntrySkeleton.svelte +81 -81
  43. package/dist/layout/Chat/ChatHeader.svelte +172 -172
  44. package/dist/layout/Chat/ChatInput.svelte +207 -207
  45. package/dist/layout/Chat/DraggableWindow.svelte +230 -230
  46. package/dist/layout/Chat/PreviewPage.svelte +182 -182
  47. package/dist/layout/Chat/RichText.svelte +216 -216
  48. package/dist/layout/ComponentCanvas/Canvas.svelte +40 -40
  49. package/dist/layout/ComponentCanvas/ComponentRenderer.svelte +85 -85
  50. package/dist/layout/TF/Content/Content.svelte +21 -21
  51. package/dist/layout/TF/Header/Header.svelte +166 -166
  52. package/dist/layout/TF/Sidebar/Sidebar.svelte +148 -148
  53. package/dist/layout/TF/Wrapper/Wrapper.svelte +17 -17
  54. package/dist/layout/mailing/MailPaginator.svelte +36 -36
  55. package/dist/layout/mailing/MailSidebar.svelte +39 -39
  56. package/dist/layout/mailing/MailToolBar.svelte +174 -174
  57. package/dist/layout/mailing/MailingContent.svelte +10 -10
  58. package/dist/layout/mailing/MailingHeader.svelte +55 -55
  59. package/dist/layout/mailing/MailingMessageCard.svelte +112 -112
  60. package/dist/layout/mailing/MailingMessageViewer.svelte +87 -87
  61. package/dist/layout/mailing/MailingModule.svelte +448 -448
  62. package/dist/styles/docs.css +615 -615
  63. package/dist/styles/tf-layout.css +185 -185
  64. package/dist/themes/ThemeProvider.svelte +20 -20
  65. package/dist/themes/themes.d.ts +3 -0
  66. package/dist/themes/themes.js +3 -0
  67. package/dist/types/index.d.ts +57 -1
  68. package/dist/typography/heading/Heading.svelte +35 -35
  69. package/dist/ui/accordion/Accordion.svelte +49 -49
  70. package/dist/ui/accordion/AccordionItem.svelte +173 -173
  71. package/dist/ui/alert/Alert.svelte +83 -83
  72. package/dist/ui/alertDialog/AlertDialog.svelte +40 -40
  73. package/dist/ui/avatar/Avatar.svelte +77 -77
  74. package/dist/ui/box/Box.svelte +28 -28
  75. package/dist/ui/breadcrumb/Breadcrumb.svelte +39 -39
  76. package/dist/ui/buttons/ActionButton.svelte +234 -234
  77. package/dist/ui/buttons/Button.svelte +102 -102
  78. package/dist/ui/buttons/GradientButton.svelte +59 -59
  79. package/dist/ui/datatable/Datatable.svelte +525 -525
  80. package/dist/ui/drawer/Drawer.svelte +300 -300
  81. package/dist/ui/dropdown/Dropdown.svelte +36 -36
  82. package/dist/ui/dropdown/DropdownDivider.svelte +11 -11
  83. package/dist/ui/dropdown/DropdownGroup.svelte +14 -14
  84. package/dist/ui/dropdown/DropdownHeader.svelte +14 -14
  85. package/dist/ui/dropdown/DropdownItem.svelte +52 -52
  86. package/dist/ui/footer/Footer.svelte +15 -15
  87. package/dist/ui/footer/FooterBrand.svelte +37 -37
  88. package/dist/ui/footer/FooterCopyright.svelte +45 -45
  89. package/dist/ui/footer/FooterIcon.svelte +22 -22
  90. package/dist/ui/footer/FooterLink.svelte +33 -33
  91. package/dist/ui/footer/FooterLinkGroup.svelte +13 -13
  92. package/dist/ui/icons/IconifyIcon.svelte +7 -7
  93. package/dist/ui/indicator/Indicator.svelte +42 -42
  94. package/dist/ui/modal/Modal.svelte +265 -265
  95. package/dist/ui/modal/theme.d.ts +26 -26
  96. package/dist/ui/modal/theme.js +25 -25
  97. package/dist/ui/notificationList/NotificationList.svelte +123 -123
  98. package/dist/ui/pageLoader/PageLoader.svelte +14 -14
  99. package/dist/ui/paginate/Paginate.svelte +96 -96
  100. package/dist/ui/speedDial/SpeedDial.svelte +77 -0
  101. package/dist/ui/speedDial/SpeedDial.svelte.d.ts +21 -0
  102. package/dist/ui/speedDial/SpeedDialButton.svelte +75 -0
  103. package/dist/ui/speedDial/SpeedDialButton.svelte.d.ts +20 -0
  104. package/dist/ui/speedDial/SpeedDialTrigger.svelte +79 -0
  105. package/dist/ui/speedDial/SpeedDialTrigger.svelte.d.ts +18 -0
  106. package/dist/ui/speedDial/index.d.ts +4 -0
  107. package/dist/ui/speedDial/index.js +4 -0
  108. package/dist/ui/speedDial/theme.d.ts +75 -0
  109. package/dist/ui/speedDial/theme.js +35 -0
  110. package/dist/ui/tab/Tab.svelte +67 -67
  111. package/dist/ui/table/Table.svelte +396 -396
  112. package/dist/ui/tableLoader/TableLoader.svelte +24 -24
  113. package/dist/ui/toast/Toast.svelte +337 -337
  114. package/dist/ui/toast/Toast.svelte.d.ts +10 -10
  115. package/dist/ui/toast/index.d.ts +1 -2
  116. package/dist/ui/toast/index.js +3 -1
  117. package/dist/ui/toolbar/Toolbar.svelte +59 -59
  118. package/dist/ui/toolbar/ToolbarButton.svelte +56 -56
  119. package/dist/ui/toolbar/ToolbarGroup.svelte +43 -43
  120. package/dist/ui/tooltip/Tooltip.svelte +51 -51
  121. package/dist/utils/Popper.svelte +257 -257
  122. package/dist/utils/closeButton/CloseButton.svelte +88 -88
  123. package/dist/utils/index.d.ts +3 -2
  124. package/dist/utils/index.js +13 -3
  125. package/dist/utils/singleSelection.svelte.js +48 -48
  126. package/dist/youtube/index.svelte +12 -12
  127. package/package.json +2 -1
@@ -1,363 +1,363 @@
1
- <script lang="ts">
2
- import { getContext } from 'svelte';
3
- import clsx from 'clsx';
4
- import { input, clampSize } from './index.js';
5
- import { getTheme, warnThemeDeprecation } from '../../themes/themeUtils.js';
6
- import { CloseButton, type InputProps, type InputValue, type SizeType } from '../../index.js';
7
- import { createDismissableContext } from '../../utils/dismissable.js';
8
-
9
- let {
10
- children,
11
- left,
12
- right,
13
- value = $bindable(),
14
- elementRef = $bindable(),
15
- clearable = false,
16
- size,
17
- color = 'default',
18
- class: className,
19
- classes,
20
- wrapperClass,
21
- leftClass,
22
- rightClass,
23
- divClass,
24
- clearableSvgClass,
25
- clearableColor = 'none',
26
- clearableClass,
27
- clearableOnClick,
28
- data = [],
29
- maxSuggestions = 5,
30
- onSelect,
31
- comboClass,
32
- comboItemClass,
33
- oninput,
34
- onfocus,
35
- onblur,
36
- onkeydown,
37
- ...restProps
38
- }: InputProps<InputValue> = $props();
39
-
40
- // input, left, right, close, combo, comboItem, div, svg
41
- warnThemeDeprecation(
42
- 'Input',
43
- {
44
- wrapperClass,
45
- leftClass,
46
- rightClass,
47
- divClass,
48
- clearableSvgClass,
49
- clearableClass,
50
- comboClass
51
- },
52
- {
53
- wrapperClass: 'wrapper',
54
- leftClass: 'left',
55
- rightClass: 'right',
56
- divClass: 'div',
57
- clearableSvgClass: 'svg',
58
- clearableClass: 'close',
59
- comboClass: 'comboItem'
60
- }
61
- );
62
-
63
- const styling = $derived(
64
- classes ?? {
65
- wrapper: wrapperClass,
66
- left: leftClass,
67
- right: rightClass,
68
- div: divClass,
69
- svg: clearableSvgClass,
70
- close: clearableClass,
71
- combo: comboClass,
72
- comboItem: comboItemClass
73
- }
74
- );
75
-
76
- const theme = getTheme('input');
77
-
78
- // onSelect is a custom combobox selection handler that takes a string
79
- // standard DOM events, onInput, onFocus, onBlur, onKeydown will be deprecated in the next minor version
80
- const resolvedOnInput = $derived(oninput);
81
- const resolvedOnFocus = $derived(onfocus);
82
- const resolvedOnBlur = $derived(onblur);
83
- const resolvedOnKeydown = $derived(onkeydown);
84
-
85
- // Automatically enable combobox when data is provided
86
- const isCombobox = $derived(Array.isArray(data) && data.length > 0);
87
-
88
- // tinted if put in component having its own background
89
- let background: boolean = getContext('background');
90
-
91
- // svelte-ignore non_reactive_update
92
- let dummyFocusDiv: HTMLDivElement;
93
-
94
- let group: { size: SizeType } = getContext('group');
95
- let isGroup = !!group;
96
- let _size = $derived(size || clampSize(group?.size) || 'md');
97
- const _color = $derived(color === 'default' && background ? 'tinted' : color);
98
-
99
- const {
100
- base,
101
- input: inputCls,
102
- left: leftCls,
103
- right: rightCls,
104
- close,
105
- combo,
106
- comboItem
107
- } = $derived(input({ size: _size, color: _color, grouped: isGroup }));
108
-
109
- const clearAll = () => {
110
- if (elementRef) {
111
- // in order to avoid type error in setTimeout()
112
- const input = elementRef;
113
- input.value = '';
114
- value = '';
115
-
116
- backspaceUsed = false;
117
- updateSuggestions();
118
- // hack to focus outside
119
- dummyFocusDiv?.focus();
120
- setTimeout(() => {
121
- input.focus();
122
- }, 100);
123
- }
124
-
125
- if (clearableOnClick) clearableOnClick();
126
- };
127
-
128
- createDismissableContext(clearAll);
129
-
130
- // Combobox functionality
131
- let isFocused = $state(false);
132
- let filteredSuggestions: string[] = $state([]);
133
- let selectedIndex = $state(-1);
134
- let backspaceUsed = $state(false); // Track if backspace was used to clear
135
-
136
- function updateSuggestions() {
137
- if (!isCombobox || !isFocused) {
138
- filteredSuggestions = [];
139
- return;
140
- }
141
-
142
- const fullSearchTerm = ((value as string) || '').toLowerCase();
143
- const lastSpaceIndex = fullSearchTerm.lastIndexOf(' ');
144
- const searchTerm =
145
- lastSpaceIndex === -1 ? fullSearchTerm : fullSearchTerm.substring(lastSpaceIndex + 1);
146
-
147
- // Show suggestions if:
148
- // 1. There's actual input text, OR
149
- // 2. The input is empty but backspace was just used to clear it
150
- if (searchTerm === '' && !backspaceUsed) {
151
- filteredSuggestions = [];
152
- } else {
153
- // If there's text, filter suggestions
154
- if (searchTerm) {
155
- filteredSuggestions = data
156
- .filter((item) => item.toLowerCase().includes(searchTerm))
157
- .slice(0, maxSuggestions);
158
- }
159
- // If empty but backspace was used, show all suggestions
160
- else if (backspaceUsed) {
161
- filteredSuggestions = [...data].slice(0, maxSuggestions);
162
- }
163
- }
164
-
165
- selectedIndex = -1;
166
- }
167
-
168
- // Watch for value changes
169
- $effect(() => {
170
- if (isCombobox) {
171
- updateSuggestions();
172
- }
173
- });
174
-
175
- function defaultHandleInput(event: Event) {
176
- // Ensure value is treated as a string to safely check its length
177
- const currentValueAsString = String(value || '');
178
- if (currentValueAsString.length > 0) {
179
- backspaceUsed = false;
180
- }
181
- updateSuggestions();
182
- }
183
-
184
- function defaultHandleFocus(event: FocusEvent) {
185
- isFocused = true;
186
- updateSuggestions();
187
- }
188
-
189
- function defaultHandleBlur(event: FocusEvent) {
190
- // Small delay to allow click on suggestion to fire first
191
- setTimeout(() => {
192
- isFocused = false;
193
- backspaceUsed = false; // Reset flag when focus is lost
194
- filteredSuggestions = [];
195
- }, 200);
196
- }
197
-
198
- function defaultHandleKeydown(event: KeyboardEvent) {
199
- if (!isCombobox) return;
200
-
201
- // Special handling for backspace/delete - track when it's used to clear the input
202
- if (event.key === 'Backspace' || event.key === 'Delete') {
203
- const currentValue = value as string;
204
- // If this keypress will make the input empty
205
- if (currentValue.length <= 1) {
206
- backspaceUsed = true;
207
- }
208
- }
209
-
210
- if (!filteredSuggestions.length) return;
211
-
212
- switch (event.key) {
213
- case 'ArrowDown':
214
- event.preventDefault();
215
- selectedIndex = (selectedIndex + 1) % filteredSuggestions.length;
216
- break;
217
- case 'ArrowUp':
218
- event.preventDefault();
219
- selectedIndex = selectedIndex <= 0 ? filteredSuggestions.length - 1 : selectedIndex - 1;
220
- break;
221
- case 'Enter':
222
- if (selectedIndex >= 0) {
223
- event.preventDefault();
224
- selectItem(filteredSuggestions[selectedIndex]);
225
- }
226
- break;
227
- case 'Escape':
228
- event.preventDefault();
229
- filteredSuggestions = [];
230
- break;
231
- }
232
- }
233
-
234
- // Combined event handlers that call custom handlers first, then default behavior
235
- function handleInput(event: Event) {
236
- if (resolvedOnInput) {
237
- resolvedOnInput(event);
238
- }
239
- defaultHandleInput(event);
240
- }
241
-
242
- function handleFocus(event: FocusEvent) {
243
- if (resolvedOnFocus) {
244
- resolvedOnFocus(event);
245
- }
246
- defaultHandleFocus(event);
247
- }
248
-
249
- function handleBlur(event: FocusEvent) {
250
- if (resolvedOnBlur) {
251
- resolvedOnBlur(event);
252
- }
253
- defaultHandleBlur(event);
254
- }
255
-
256
- function handleKeydown(event: KeyboardEvent) {
257
- if (resolvedOnKeydown) {
258
- resolvedOnKeydown(event);
259
- }
260
- defaultHandleKeydown(event);
261
- }
262
-
263
- function selectItem(item: string) {
264
- const currentValue = (value as string) || '';
265
- const lastSpaceIndex = currentValue.lastIndexOf(' ');
266
-
267
- if (lastSpaceIndex === -1) {
268
- value = item + ' '; // Replace the whole value if no space, add trailing space
269
- } else {
270
- value = currentValue.substring(0, lastSpaceIndex + 1) + item + ' '; // Replace last word, add trailing space
271
- }
272
-
273
- if (onSelect) {
274
- onSelect(item);
275
- }
276
-
277
- filteredSuggestions = [];
278
- selectedIndex = -1;
279
-
280
- if (elementRef) {
281
- elementRef.focus();
282
- }
283
- }
284
- </script>
285
-
286
- {#if clearable}
287
- <div tabindex="-1" bind:this={dummyFocusDiv} class="sr-only"></div>
288
- {/if}
289
-
290
- {#if isCombobox}
291
- <div class={clsx(isCombobox ? 'relative w-full' : '', theme?.wrapper, styling.wrapper)}>
292
- {#if right || left || clearable}
293
- <div class={base({ class: clsx(theme?.base, styling.div) })}>
294
- {@render inputContent()}
295
- </div>
296
- {:else}
297
- {@render inputContent()}
298
- {/if}
299
-
300
- {#if isCombobox && isFocused && filteredSuggestions.length > 0}
301
- <div class={combo({ class: clsx(theme?.combo, styling.combo) })}>
302
- {#each filteredSuggestions as item, i}
303
- <button
304
- type="button"
305
- class="w-full px-3 py-2 text-left {i === selectedIndex
306
- ? 'bg-gray-100 dark:bg-gray-700'
307
- : 'hover:bg-gray-50 dark:hover:bg-gray-700'} focus:outline-none"
308
- onclick={() => selectItem(item)}
309
- onmouseenter={() => (selectedIndex = i)}
310
- >
311
- <p class={comboItem({ class: clsx(theme?.comboItem, styling.comboItem) })}>{item}</p>
312
- </button>
313
- {/each}
314
- </div>
315
- {/if}
316
- </div>
317
- {:else if group}
318
- {@render inputContent()}
319
- {:else if right || left || clearable}
320
- <div class={base({ class: clsx(theme?.base, styling.div) })}>
321
- {@render inputContent()}
322
- </div>
323
- {:else}
324
- {@render inputContent()}
325
- {/if}
326
-
327
- {#snippet inputContent()}
328
- {#if left}
329
- <div class={leftCls({ class: clsx(theme?.left, styling.left) })}>
330
- {@render left()}
331
- </div>
332
- {/if}
333
- {#if children}
334
- {@render children({ ...restProps, class: inputCls() })}
335
- {:else}
336
- <input
337
- {...restProps}
338
- bind:value
339
- bind:this={elementRef}
340
- oninput={handleInput}
341
- onfocus={handleFocus}
342
- onblur={handleBlur}
343
- onkeydown={handleKeydown}
344
- class={inputCls({ class: clsx(theme?.input, className) })}
345
- />
346
- {#if value !== undefined && value !== '' && clearable}
347
- <CloseButton
348
- class={close({ class: clsx(theme?.close, styling.close) })}
349
- color={clearableColor}
350
- aria-label="Clear search value"
351
- svgClass={clsx(styling.svg)}
352
- />
353
- {/if}
354
- {/if}
355
- {#if right}
356
- <div class={rightCls({ class: clsx(theme?.right, styling.right) })}>
357
- {@render right()}
358
- </div>
359
- {/if}
360
- {/snippet}
361
-
362
-
363
-
1
+ <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import clsx from 'clsx';
4
+ import { input, clampSize } from './index.js';
5
+ import { getTheme, warnThemeDeprecation } from '../../themes/themeUtils.js';
6
+ import { CloseButton, type InputProps, type InputValue, type SizeType } from '../../index.js';
7
+ import { createDismissableContext } from '../../utils/dismissable.js';
8
+
9
+ let {
10
+ children,
11
+ left,
12
+ right,
13
+ value = $bindable(),
14
+ elementRef = $bindable(),
15
+ clearable = false,
16
+ size,
17
+ color = 'default',
18
+ class: className,
19
+ classes,
20
+ wrapperClass,
21
+ leftClass,
22
+ rightClass,
23
+ divClass,
24
+ clearableSvgClass,
25
+ clearableColor = 'none',
26
+ clearableClass,
27
+ clearableOnClick,
28
+ data = [],
29
+ maxSuggestions = 5,
30
+ onSelect,
31
+ comboClass,
32
+ comboItemClass,
33
+ oninput,
34
+ onfocus,
35
+ onblur,
36
+ onkeydown,
37
+ ...restProps
38
+ }: InputProps<InputValue> = $props();
39
+
40
+ // input, left, right, close, combo, comboItem, div, svg
41
+ warnThemeDeprecation(
42
+ 'Input',
43
+ {
44
+ wrapperClass,
45
+ leftClass,
46
+ rightClass,
47
+ divClass,
48
+ clearableSvgClass,
49
+ clearableClass,
50
+ comboClass
51
+ },
52
+ {
53
+ wrapperClass: 'wrapper',
54
+ leftClass: 'left',
55
+ rightClass: 'right',
56
+ divClass: 'div',
57
+ clearableSvgClass: 'svg',
58
+ clearableClass: 'close',
59
+ comboClass: 'comboItem'
60
+ }
61
+ );
62
+
63
+ const styling = $derived(
64
+ classes ?? {
65
+ wrapper: wrapperClass,
66
+ left: leftClass,
67
+ right: rightClass,
68
+ div: divClass,
69
+ svg: clearableSvgClass,
70
+ close: clearableClass,
71
+ combo: comboClass,
72
+ comboItem: comboItemClass
73
+ }
74
+ );
75
+
76
+ const theme = getTheme('input');
77
+
78
+ // onSelect is a custom combobox selection handler that takes a string
79
+ // standard DOM events, onInput, onFocus, onBlur, onKeydown will be deprecated in the next minor version
80
+ const resolvedOnInput = $derived(oninput);
81
+ const resolvedOnFocus = $derived(onfocus);
82
+ const resolvedOnBlur = $derived(onblur);
83
+ const resolvedOnKeydown = $derived(onkeydown);
84
+
85
+ // Automatically enable combobox when data is provided
86
+ const isCombobox = $derived(Array.isArray(data) && data.length > 0);
87
+
88
+ // tinted if put in component having its own background
89
+ let background: boolean = getContext('background');
90
+
91
+ // svelte-ignore non_reactive_update
92
+ let dummyFocusDiv: HTMLDivElement;
93
+
94
+ let group: { size: SizeType } = getContext('group');
95
+ let isGroup = !!group;
96
+ let _size = $derived(size || clampSize(group?.size) || 'md');
97
+ const _color = $derived(color === 'default' && background ? 'tinted' : color);
98
+
99
+ const {
100
+ base,
101
+ input: inputCls,
102
+ left: leftCls,
103
+ right: rightCls,
104
+ close,
105
+ combo,
106
+ comboItem
107
+ } = $derived(input({ size: _size, color: _color, grouped: isGroup }));
108
+
109
+ const clearAll = () => {
110
+ if (elementRef) {
111
+ // in order to avoid type error in setTimeout()
112
+ const input = elementRef;
113
+ input.value = '';
114
+ value = '';
115
+
116
+ backspaceUsed = false;
117
+ updateSuggestions();
118
+ // hack to focus outside
119
+ dummyFocusDiv?.focus();
120
+ setTimeout(() => {
121
+ input.focus();
122
+ }, 100);
123
+ }
124
+
125
+ if (clearableOnClick) clearableOnClick();
126
+ };
127
+
128
+ createDismissableContext(clearAll);
129
+
130
+ // Combobox functionality
131
+ let isFocused = $state(false);
132
+ let filteredSuggestions: string[] = $state([]);
133
+ let selectedIndex = $state(-1);
134
+ let backspaceUsed = $state(false); // Track if backspace was used to clear
135
+
136
+ function updateSuggestions() {
137
+ if (!isCombobox || !isFocused) {
138
+ filteredSuggestions = [];
139
+ return;
140
+ }
141
+
142
+ const fullSearchTerm = ((value as string) || '').toLowerCase();
143
+ const lastSpaceIndex = fullSearchTerm.lastIndexOf(' ');
144
+ const searchTerm =
145
+ lastSpaceIndex === -1 ? fullSearchTerm : fullSearchTerm.substring(lastSpaceIndex + 1);
146
+
147
+ // Show suggestions if:
148
+ // 1. There's actual input text, OR
149
+ // 2. The input is empty but backspace was just used to clear it
150
+ if (searchTerm === '' && !backspaceUsed) {
151
+ filteredSuggestions = [];
152
+ } else {
153
+ // If there's text, filter suggestions
154
+ if (searchTerm) {
155
+ filteredSuggestions = data
156
+ .filter((item) => item.toLowerCase().includes(searchTerm))
157
+ .slice(0, maxSuggestions);
158
+ }
159
+ // If empty but backspace was used, show all suggestions
160
+ else if (backspaceUsed) {
161
+ filteredSuggestions = [...data].slice(0, maxSuggestions);
162
+ }
163
+ }
164
+
165
+ selectedIndex = -1;
166
+ }
167
+
168
+ // Watch for value changes
169
+ $effect(() => {
170
+ if (isCombobox) {
171
+ updateSuggestions();
172
+ }
173
+ });
174
+
175
+ function defaultHandleInput(event: Event) {
176
+ // Ensure value is treated as a string to safely check its length
177
+ const currentValueAsString = String(value || '');
178
+ if (currentValueAsString.length > 0) {
179
+ backspaceUsed = false;
180
+ }
181
+ updateSuggestions();
182
+ }
183
+
184
+ function defaultHandleFocus(event: FocusEvent) {
185
+ isFocused = true;
186
+ updateSuggestions();
187
+ }
188
+
189
+ function defaultHandleBlur(event: FocusEvent) {
190
+ // Small delay to allow click on suggestion to fire first
191
+ setTimeout(() => {
192
+ isFocused = false;
193
+ backspaceUsed = false; // Reset flag when focus is lost
194
+ filteredSuggestions = [];
195
+ }, 200);
196
+ }
197
+
198
+ function defaultHandleKeydown(event: KeyboardEvent) {
199
+ if (!isCombobox) return;
200
+
201
+ // Special handling for backspace/delete - track when it's used to clear the input
202
+ if (event.key === 'Backspace' || event.key === 'Delete') {
203
+ const currentValue = value as string;
204
+ // If this keypress will make the input empty
205
+ if (currentValue.length <= 1) {
206
+ backspaceUsed = true;
207
+ }
208
+ }
209
+
210
+ if (!filteredSuggestions.length) return;
211
+
212
+ switch (event.key) {
213
+ case 'ArrowDown':
214
+ event.preventDefault();
215
+ selectedIndex = (selectedIndex + 1) % filteredSuggestions.length;
216
+ break;
217
+ case 'ArrowUp':
218
+ event.preventDefault();
219
+ selectedIndex = selectedIndex <= 0 ? filteredSuggestions.length - 1 : selectedIndex - 1;
220
+ break;
221
+ case 'Enter':
222
+ if (selectedIndex >= 0) {
223
+ event.preventDefault();
224
+ selectItem(filteredSuggestions[selectedIndex]);
225
+ }
226
+ break;
227
+ case 'Escape':
228
+ event.preventDefault();
229
+ filteredSuggestions = [];
230
+ break;
231
+ }
232
+ }
233
+
234
+ // Combined event handlers that call custom handlers first, then default behavior
235
+ function handleInput(event: Event) {
236
+ if (resolvedOnInput) {
237
+ resolvedOnInput(event);
238
+ }
239
+ defaultHandleInput(event);
240
+ }
241
+
242
+ function handleFocus(event: FocusEvent) {
243
+ if (resolvedOnFocus) {
244
+ resolvedOnFocus(event);
245
+ }
246
+ defaultHandleFocus(event);
247
+ }
248
+
249
+ function handleBlur(event: FocusEvent) {
250
+ if (resolvedOnBlur) {
251
+ resolvedOnBlur(event);
252
+ }
253
+ defaultHandleBlur(event);
254
+ }
255
+
256
+ function handleKeydown(event: KeyboardEvent) {
257
+ if (resolvedOnKeydown) {
258
+ resolvedOnKeydown(event);
259
+ }
260
+ defaultHandleKeydown(event);
261
+ }
262
+
263
+ function selectItem(item: string) {
264
+ const currentValue = (value as string) || '';
265
+ const lastSpaceIndex = currentValue.lastIndexOf(' ');
266
+
267
+ if (lastSpaceIndex === -1) {
268
+ value = item + ' '; // Replace the whole value if no space, add trailing space
269
+ } else {
270
+ value = currentValue.substring(0, lastSpaceIndex + 1) + item + ' '; // Replace last word, add trailing space
271
+ }
272
+
273
+ if (onSelect) {
274
+ onSelect(item);
275
+ }
276
+
277
+ filteredSuggestions = [];
278
+ selectedIndex = -1;
279
+
280
+ if (elementRef) {
281
+ elementRef.focus();
282
+ }
283
+ }
284
+ </script>
285
+
286
+ {#if clearable}
287
+ <div tabindex="-1" bind:this={dummyFocusDiv} class="sr-only"></div>
288
+ {/if}
289
+
290
+ {#if isCombobox}
291
+ <div class={clsx(isCombobox ? 'relative w-full' : '', theme?.wrapper, styling.wrapper)}>
292
+ {#if right || left || clearable}
293
+ <div class={base({ class: clsx(theme?.base, styling.div) })}>
294
+ {@render inputContent()}
295
+ </div>
296
+ {:else}
297
+ {@render inputContent()}
298
+ {/if}
299
+
300
+ {#if isCombobox && isFocused && filteredSuggestions.length > 0}
301
+ <div class={combo({ class: clsx(theme?.combo, styling.combo) })}>
302
+ {#each filteredSuggestions as item, i}
303
+ <button
304
+ type="button"
305
+ class="w-full px-3 py-2 text-left {i === selectedIndex
306
+ ? 'bg-gray-100 dark:bg-gray-700'
307
+ : 'hover:bg-gray-50 dark:hover:bg-gray-700'} focus:outline-none"
308
+ onclick={() => selectItem(item)}
309
+ onmouseenter={() => (selectedIndex = i)}
310
+ >
311
+ <p class={comboItem({ class: clsx(theme?.comboItem, styling.comboItem) })}>{item}</p>
312
+ </button>
313
+ {/each}
314
+ </div>
315
+ {/if}
316
+ </div>
317
+ {:else if group}
318
+ {@render inputContent()}
319
+ {:else if right || left || clearable}
320
+ <div class={base({ class: clsx(theme?.base, styling.div) })}>
321
+ {@render inputContent()}
322
+ </div>
323
+ {:else}
324
+ {@render inputContent()}
325
+ {/if}
326
+
327
+ {#snippet inputContent()}
328
+ {#if left}
329
+ <div class={leftCls({ class: clsx(theme?.left, styling.left) })}>
330
+ {@render left()}
331
+ </div>
332
+ {/if}
333
+ {#if children}
334
+ {@render children({ ...restProps, class: inputCls() })}
335
+ {:else}
336
+ <input
337
+ {...restProps}
338
+ bind:value
339
+ bind:this={elementRef}
340
+ oninput={handleInput}
341
+ onfocus={handleFocus}
342
+ onblur={handleBlur}
343
+ onkeydown={handleKeydown}
344
+ class={inputCls({ class: clsx(theme?.input, className) })}
345
+ />
346
+ {#if value !== undefined && value !== '' && clearable}
347
+ <CloseButton
348
+ class={close({ class: clsx(theme?.close, styling.close) })}
349
+ color={clearableColor}
350
+ aria-label="Clear search value"
351
+ svgClass={clsx(styling.svg)}
352
+ />
353
+ {/if}
354
+ {/if}
355
+ {#if right}
356
+ <div class={rightCls({ class: clsx(theme?.right, styling.right) })}>
357
+ {@render right()}
358
+ </div>
359
+ {/if}
360
+ {/snippet}
361
+
362
+
363
+