@rkosafo/cai.components 0.0.78 → 0.0.80

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 (103) 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 -119
  13. package/dist/forms/button-toggle/CheckIcon.svelte +28 -28
  14. package/dist/forms/checkbox/Checkbox.svelte +82 -82
  15. package/dist/forms/checkbox/CheckboxButton.svelte +92 -92
  16. package/dist/forms/datepicker/Datepicker.svelte +707 -707
  17. package/dist/forms/form/Form.svelte +69 -69
  18. package/dist/forms/input/Input.svelte +363 -363
  19. package/dist/forms/label/Label.svelte +38 -38
  20. package/dist/forms/radio/Radio.svelte +48 -48
  21. package/dist/forms/radio/RadioButton.svelte +22 -22
  22. package/dist/forms/select/Select.svelte +56 -56
  23. package/dist/forms/textarea/Textarea.svelte +165 -165
  24. package/dist/forms/toggle/Toggle.svelte +70 -70
  25. package/dist/layout/Chat/CategorySelector.svelte +52 -52
  26. package/dist/layout/Chat/ChatEntry.svelte +246 -246
  27. package/dist/layout/Chat/ChatEntrySkeleton.svelte +81 -81
  28. package/dist/layout/Chat/ChatHeader.svelte +172 -172
  29. package/dist/layout/Chat/ChatInput.svelte +207 -207
  30. package/dist/layout/Chat/DraggableWindow.svelte +230 -230
  31. package/dist/layout/Chat/PreviewPage.svelte +182 -182
  32. package/dist/layout/Chat/RichText.svelte +216 -216
  33. package/dist/layout/ComponentCanvas/Canvas.svelte +40 -40
  34. package/dist/layout/ComponentCanvas/ComponentRenderer.svelte +85 -85
  35. package/dist/layout/TF/Content/Content.svelte +21 -21
  36. package/dist/layout/TF/Header/Header.svelte +166 -166
  37. package/dist/layout/TF/Sidebar/Sidebar.svelte +148 -148
  38. package/dist/layout/TF/Wrapper/Wrapper.svelte +17 -17
  39. package/dist/layout/mailing/MailPaginator.svelte +36 -36
  40. package/dist/layout/mailing/MailSidebar.svelte +39 -39
  41. package/dist/layout/mailing/MailToolBar.svelte +174 -174
  42. package/dist/layout/mailing/MailingContent.svelte +10 -10
  43. package/dist/layout/mailing/MailingHeader.svelte +55 -55
  44. package/dist/layout/mailing/MailingMessageCard.svelte +112 -112
  45. package/dist/layout/mailing/MailingMessageViewer.svelte +87 -87
  46. package/dist/layout/mailing/MailingModule.svelte +448 -448
  47. package/dist/styles/docs.css +615 -615
  48. package/dist/styles/tf-layout.css +185 -185
  49. package/dist/themes/ThemeProvider.svelte +20 -20
  50. package/dist/types/index.d.ts +2 -0
  51. package/dist/typography/heading/Heading.svelte +35 -35
  52. package/dist/ui/accordion/Accordion.svelte +49 -49
  53. package/dist/ui/accordion/AccordionItem.svelte +173 -173
  54. package/dist/ui/alert/Alert.svelte +83 -83
  55. package/dist/ui/alertDialog/AlertDialog.svelte +40 -40
  56. package/dist/ui/avatar/Avatar.svelte +77 -77
  57. package/dist/ui/box/Box.svelte +28 -28
  58. package/dist/ui/breadcrumb/Breadcrumb.svelte +39 -39
  59. package/dist/ui/buttons/ActionButton.svelte +234 -234
  60. package/dist/ui/buttons/Button.svelte +102 -102
  61. package/dist/ui/buttons/GradientButton.svelte +59 -59
  62. package/dist/ui/datatable/Datatable.svelte +525 -525
  63. package/dist/ui/drawer/Drawer.svelte +300 -300
  64. package/dist/ui/dropdown/Dropdown.svelte +36 -36
  65. package/dist/ui/dropdown/DropdownDivider.svelte +11 -11
  66. package/dist/ui/dropdown/DropdownGroup.svelte +14 -14
  67. package/dist/ui/dropdown/DropdownHeader.svelte +14 -14
  68. package/dist/ui/dropdown/DropdownItem.svelte +52 -52
  69. package/dist/ui/footer/Footer.svelte +15 -15
  70. package/dist/ui/footer/FooterBrand.svelte +37 -37
  71. package/dist/ui/footer/FooterCopyright.svelte +45 -45
  72. package/dist/ui/footer/FooterIcon.svelte +22 -22
  73. package/dist/ui/footer/FooterLink.svelte +33 -33
  74. package/dist/ui/footer/FooterLinkGroup.svelte +13 -13
  75. package/dist/ui/icons/IconifyIcon.svelte +7 -7
  76. package/dist/ui/indicator/Indicator.svelte +42 -42
  77. package/dist/ui/modal/Modal.svelte +265 -265
  78. package/dist/ui/notificationList/NotificationList.svelte +123 -123
  79. package/dist/ui/pageLoader/PageLoader.svelte +14 -14
  80. package/dist/ui/pageLoader/PageLoader2.svelte +99 -0
  81. package/dist/ui/pageLoader/PageLoader2.svelte.d.ts +24 -0
  82. package/dist/ui/pageLoader/index.d.ts +2 -1
  83. package/dist/ui/pageLoader/index.js +2 -1
  84. package/dist/ui/paginate/Paginate.svelte +96 -96
  85. package/dist/ui/speedDial/SpeedDial.svelte +77 -77
  86. package/dist/ui/speedDial/SpeedDialButton.svelte +75 -75
  87. package/dist/ui/speedDial/SpeedDialTrigger.svelte +79 -79
  88. package/dist/ui/tab/Tab.svelte +93 -67
  89. package/dist/ui/table/Table.svelte +396 -396
  90. package/dist/ui/tableLoader/TableLoader.svelte +24 -24
  91. package/dist/ui/toast/Toast.svelte +337 -337
  92. package/dist/ui/toast/Toast.svelte.d.ts +10 -10
  93. package/dist/ui/toolbar/Toolbar.svelte +59 -59
  94. package/dist/ui/toolbar/ToolbarButton.svelte +56 -56
  95. package/dist/ui/toolbar/ToolbarGroup.svelte +43 -43
  96. package/dist/ui/tooltip/Tooltip.svelte +51 -51
  97. package/dist/utils/Popper.svelte +257 -257
  98. package/dist/utils/closeButton/CloseButton.svelte +88 -88
  99. package/dist/utils/index.d.ts +2 -2
  100. package/dist/utils/index.js +3 -3
  101. package/dist/utils/singleSelection.svelte.js +48 -48
  102. package/dist/youtube/index.svelte +12 -12
  103. package/package.json +1 -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
+