flowbite-svelte 1.10.1 → 1.10.2
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.
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts" generics="T">
|
|
2
2
|
import { Badge, CloseButton, type MultiSelectProps, type SelectOptionType, cn } from "../..";
|
|
3
|
-
import {
|
|
3
|
+
import { multiSelect, type MultiSelectTheme } from ".";
|
|
4
4
|
import { getTheme } from "../../theme/themeUtils";
|
|
5
|
+
import { onMount, onDestroy } from "svelte"; // Import onMount and onDestroy
|
|
5
6
|
|
|
6
7
|
// Consider reusing that component - https://svelecte.vercel.app/
|
|
7
8
|
|
|
@@ -33,7 +34,12 @@
|
|
|
33
34
|
let activeIndex: number | null = $state(null);
|
|
34
35
|
let activeItem = $derived(activeIndex !== null ? items[((activeIndex % items.length) + items.length) % items.length] : null);
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
let multiSelectContainer: HTMLDivElement; // Reference to the main div
|
|
38
|
+
|
|
39
|
+
const selectOption = (select: SelectOptionType<any>, event: MouseEvent) => {
|
|
40
|
+
// Prevent the click from propagating to the parent div
|
|
41
|
+
event.stopPropagation();
|
|
42
|
+
|
|
37
43
|
if (disabled) return;
|
|
38
44
|
if (select.disabled) return;
|
|
39
45
|
|
|
@@ -49,6 +55,7 @@
|
|
|
49
55
|
if (JSON.stringify(oldValue) !== JSON.stringify(value)) {
|
|
50
56
|
triggerChange();
|
|
51
57
|
}
|
|
58
|
+
// Don't close the dropdown here
|
|
52
59
|
};
|
|
53
60
|
|
|
54
61
|
const clearAll = (e: MouseEvent) => {
|
|
@@ -94,11 +101,23 @@
|
|
|
94
101
|
};
|
|
95
102
|
|
|
96
103
|
const closeDropdown = () => !disabled && (show = false);
|
|
97
|
-
const toggleDropdown = () =>
|
|
104
|
+
const toggleDropdown = (event: MouseEvent) => {
|
|
105
|
+
if (disabled) return;
|
|
106
|
+
// Prevent immediate closing if the click originated from within the component itself
|
|
107
|
+
// This is useful if the click triggers a re-render and focus is lost momentarily.
|
|
108
|
+
if (multiSelectContainer && multiSelectContainer.contains(event.target as Node)) {
|
|
109
|
+
show = !show;
|
|
110
|
+
} else {
|
|
111
|
+
show = false; // Close if clicked outside
|
|
112
|
+
}
|
|
113
|
+
};
|
|
98
114
|
|
|
99
115
|
// Handle blur event for validation
|
|
100
116
|
const handleBlur = (event: FocusEvent) => {
|
|
101
|
-
|
|
117
|
+
// We'll rely more on the global click listener for closing, but keep this for standard blur behavior
|
|
118
|
+
if (event.currentTarget && (event.currentTarget as HTMLElement).contains && !(event.currentTarget as HTMLElement).contains(event.relatedTarget as Node)) {
|
|
119
|
+
closeDropdown();
|
|
120
|
+
}
|
|
102
121
|
if (onblur) {
|
|
103
122
|
onblur(event);
|
|
104
123
|
}
|
|
@@ -112,7 +131,7 @@
|
|
|
112
131
|
show = true;
|
|
113
132
|
activeIndex = 0;
|
|
114
133
|
} else {
|
|
115
|
-
if (activeItem !== null) selectOption(activeItem);
|
|
134
|
+
if (activeItem !== null) selectOption(activeItem, new MouseEvent("click")); // Pass a dummy MouseEvent
|
|
116
135
|
}
|
|
117
136
|
}
|
|
118
137
|
|
|
@@ -133,8 +152,11 @@
|
|
|
133
152
|
|
|
134
153
|
function handleKeyDown(event: KeyboardEvent) {
|
|
135
154
|
if (disabled) return;
|
|
155
|
+
// Do not prevent default for tab key, allow it to move focus
|
|
156
|
+
if (event.key !== "Tab") {
|
|
157
|
+
event.preventDefault();
|
|
158
|
+
}
|
|
136
159
|
event.stopPropagation();
|
|
137
|
-
event.preventDefault();
|
|
138
160
|
|
|
139
161
|
const actions = {
|
|
140
162
|
Escape: closeDropdown,
|
|
@@ -148,17 +170,30 @@
|
|
|
148
170
|
}
|
|
149
171
|
}
|
|
150
172
|
|
|
151
|
-
|
|
173
|
+
// Global click listener for closing the dropdown when clicking outside
|
|
174
|
+
onMount(() => {
|
|
175
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
176
|
+
if (multiSelectContainer && !multiSelectContainer.contains(event.target as Node)) {
|
|
177
|
+
closeDropdown();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
document.addEventListener("click", handleClickOutside);
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
document.removeEventListener("click", handleClickOutside);
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const { base, dropdown, dropdownitem, closebutton, select } = multiSelect({ disabled });
|
|
152
188
|
</script>
|
|
153
189
|
|
|
154
|
-
<!-- Hidden select for form submission -->
|
|
155
190
|
<select {name} {form} {required} {autocomplete} {value} hidden multiple {onchange}>
|
|
156
191
|
{#each items as item}
|
|
157
192
|
<option value={item.value} disabled={item.disabled}>{item.name}</option>
|
|
158
193
|
{/each}
|
|
159
194
|
</select>
|
|
160
195
|
|
|
161
|
-
<div {...restProps} onclick={toggleDropdown} onblur={handleBlur} onkeydown={handleKeyDown} tabindex="0" role="listbox" class={cn(base({ size }), className, (theme as MultiSelectTheme)?.base)}>
|
|
196
|
+
<div bind:this={multiSelectContainer} {...restProps} onclick={toggleDropdown} onblur={handleBlur} onkeydown={handleKeyDown} tabindex="0" role="listbox" class={cn(base({ size }), className, (theme as MultiSelectTheme)?.base)}>
|
|
162
197
|
{#if !selectItems.length}
|
|
163
198
|
<span class="text-gray-400">{placeholder}</span>
|
|
164
199
|
{/if}
|
|
@@ -189,7 +224,7 @@
|
|
|
189
224
|
<div role="presentation" class={cn(dropdown(), dropdownClass)}>
|
|
190
225
|
{#each items as item (item.name)}
|
|
191
226
|
<div
|
|
192
|
-
onclick={() => selectOption(item)}
|
|
227
|
+
onclick={(e) => selectOption(item, e)}
|
|
193
228
|
role="presentation"
|
|
194
229
|
class={dropdownitem({
|
|
195
230
|
selected: selectItems.includes(item),
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default as Select } from "./Select.svelte";
|
|
2
2
|
export { default as MultiSelect } from "./MultiSelect.svelte";
|
|
3
|
-
export { select,
|
|
3
|
+
export { select, multiSelect, type SelectTheme, type MultiSelectTheme } from "./theme";
|
|
@@ -93,8 +93,8 @@ export declare const select: import("tailwind-variants").TVReturnType<{
|
|
|
93
93
|
}, undefined, unknown, unknown, undefined>>;
|
|
94
94
|
export type SelectSlots = keyof typeof select.slots;
|
|
95
95
|
export type SelectTheme = Partial<Record<SelectSlots, string>>;
|
|
96
|
-
export type MultiSelectVariants = VariantProps<typeof
|
|
97
|
-
export declare const
|
|
96
|
+
export type MultiSelectVariants = VariantProps<typeof multiSelect>;
|
|
97
|
+
export declare const multiSelect: import("tailwind-variants").TVReturnType<{
|
|
98
98
|
size: {
|
|
99
99
|
sm: string;
|
|
100
100
|
md: string;
|
|
@@ -191,5 +191,5 @@ export declare const multiselect: import("tailwind-variants").TVReturnType<{
|
|
|
191
191
|
dropdownitem: string;
|
|
192
192
|
closebutton: string;
|
|
193
193
|
}, undefined, unknown, unknown, undefined>>;
|
|
194
|
-
export type MultiSelectSlots = keyof typeof
|
|
194
|
+
export type MultiSelectSlots = keyof typeof multiSelect.slots;
|
|
195
195
|
export type MultiSelectTheme = Partial<Record<MultiSelectSlots, string>>;
|
|
@@ -31,7 +31,7 @@ export const select = tv({
|
|
|
31
31
|
size: "md"
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
-
export const
|
|
34
|
+
export const multiSelect = tv({
|
|
35
35
|
slots: {
|
|
36
36
|
base: "relative border border-gray-300 flex items-center rounded-lg gap-2 dark:border-gray-600 ring-primary-500 dark:ring-primary-500 focus-visible:outline-hidden",
|
|
37
37
|
select: "flex flex-wrap gap-2",
|
|
@@ -64,6 +64,16 @@ export const multiselect = tv({
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
|
+
// Add compoundVariants here
|
|
68
|
+
compoundVariants: [
|
|
69
|
+
{
|
|
70
|
+
selected: true,
|
|
71
|
+
active: true,
|
|
72
|
+
class: {
|
|
73
|
+
dropdownitem: "bg-primary-200 dark:bg-primary-600 text-primary-700 dark:text-primary-100 font-semibold" // Adjust colors as needed
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
],
|
|
67
77
|
defaultVariants: {
|
|
68
78
|
underline: false,
|
|
69
79
|
size: "md"
|