v-uixy 1.3.0 → 1.4.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 (37) hide show
  1. package/package.json +1 -1
  2. package/templates/assets/css/main.css +8 -0
  3. package/templates/components/Avatar/Avatar.component.vue +7 -3
  4. package/templates/components/Avatar/Avatar.styles.ts +3 -3
  5. package/templates/components/Badge/Badge.styles.ts +2 -2
  6. package/templates/components/Button/Button.styles.ts +3 -3
  7. package/templates/components/Calendar/Calendar.component.vue +26 -15
  8. package/templates/components/Calendar/Calendar.styles.ts +11 -3
  9. package/templates/components/Calendar/Calendar.types.ts +1 -0
  10. package/templates/components/ColorPicker/ColorPicker.component.vue +287 -0
  11. package/templates/components/ColorPicker/ColorPicker.types.ts +3 -0
  12. package/templates/components/ColorPicker/index.ts +4 -0
  13. package/templates/components/Command/Command.component.vue +6 -6
  14. package/templates/components/Command/Command.types.ts +1 -0
  15. package/templates/components/Input/Input.component.vue +14 -4
  16. package/templates/components/Input/Input.styles.ts +4 -4
  17. package/templates/components/Modal/Modal.component.vue +28 -4
  18. package/templates/components/Modal/Modal.types.ts +3 -1
  19. package/templates/components/Popover/Popover.component.vue +33 -2
  20. package/templates/components/Popover/Popover.types.ts +1 -0
  21. package/templates/components/Select/Select.component.vue +241 -87
  22. package/templates/components/Select/Select.styles.ts +5 -5
  23. package/templates/components/Select/Select.types.ts +12 -1
  24. package/templates/components/Select/SelectTreeItem.vue +154 -0
  25. package/templates/components/Select/composables/useSelect.composable.ts +260 -42
  26. package/templates/components/Sheet/Sheet.component.vue +6 -0
  27. package/templates/components/Tabs/Tabs.component.vue +11 -2
  28. package/templates/components/Tabs/Tabs.types.ts +1 -0
  29. package/templates/components/Textarea/Textarea.component.vue +10 -13
  30. package/templates/components/Textarea/Textarea.styles.ts +3 -3
  31. package/templates/components/TimePicker/TimePicker.component.vue +21 -18
  32. package/templates/components/TimePicker/TimePicker.styles.ts +1 -1
  33. package/templates/components/TimePicker/TimePicker.types.ts +1 -0
  34. package/templates/components/index.ts +35 -0
  35. package/templates/components.json +9 -3
  36. package/templates/composables/useInputValidation.ts +89 -0
  37. package/templates/composables/usePosition.ts +17 -2
@@ -17,7 +17,7 @@ export const inputStyles = styles(
17
17
  right: "pr-8",
18
18
  none: "",
19
19
  },
20
- }
20
+ },
21
21
  );
22
22
 
23
23
  export const iconStyles = styles(
@@ -41,7 +41,7 @@ export const iconStyles = styles(
41
41
  true: "pointer-events-auto cursor-pointer",
42
42
  false: "",
43
43
  },
44
- }
44
+ },
45
45
  );
46
46
 
47
47
  export const labelStyles = styles(
@@ -53,7 +53,7 @@ export const labelStyles = styles(
53
53
  disabled: "text-gray-500 dark:text-gray-700",
54
54
  valid: "",
55
55
  },
56
- }
56
+ },
57
57
  );
58
58
 
59
59
  export const helperStyles = styles(
@@ -65,5 +65,5 @@ export const helperStyles = styles(
65
65
  disabled: "text-gray-400 dark:text-gray-800",
66
66
  valid: "text-gray-600",
67
67
  },
68
- }
68
+ },
69
69
  );
@@ -11,7 +11,6 @@
11
11
  :class="modalStyles($attrs.class as string)"
12
12
  data-modal
13
13
  @click.self="handleClick"
14
- @keydown{esc}="handleClick"
15
14
  >
16
15
  <motion.div
17
16
  :initial="{ scale: 0.8 }"
@@ -32,7 +31,7 @@
32
31
  import { modalStyles } from "./Modal.styles";
33
32
  import { AnimatePresence, motion } from "motion-v";
34
33
 
35
- defineProps<UixyModalProps>();
34
+ const props = defineProps<UixyModalProps>();
36
35
 
37
36
  defineOptions({
38
37
  inheritAttrs: false,
@@ -41,14 +40,30 @@
41
40
  const open = defineModel<boolean>();
42
41
 
43
42
  const handleClick = () => {
43
+ if (props.persistent) return;
44
+
44
45
  open.value = false;
45
46
  };
46
47
 
48
+ const handleKeyDown = (e: KeyboardEvent) => {
49
+ if (e.key === "Escape") {
50
+ handleClick();
51
+ return;
52
+ }
53
+ if (e.key === "Enter") {
54
+ const target = e.target as HTMLElement;
55
+ if (target.tagName === "TEXTAREA" || target.tagName === "BUTTON") return;
56
+ const modalEl = document.querySelector("[data-modal]");
57
+ const form = modalEl?.querySelector("form");
58
+ form?.requestSubmit();
59
+ }
60
+ };
61
+
47
62
  const toggleSiblingsInert = (enable?: boolean) => {
48
63
  if (typeof document === "undefined") return;
49
64
 
50
65
  const modalEl = document.querySelector(
51
- "[data-modal]"
66
+ "[data-modal]",
52
67
  ) as HTMLElement | null;
53
68
 
54
69
  Array.from(document.body.children).forEach((el) => {
@@ -68,7 +83,16 @@
68
83
  open,
69
84
  (isOpen) => {
70
85
  toggleSiblingsInert(isOpen);
86
+ if (isOpen) {
87
+ window.addEventListener("keydown", handleKeyDown);
88
+ } else {
89
+ window.removeEventListener("keydown", handleKeyDown);
90
+ }
71
91
  },
72
- { immediate: true }
92
+ { immediate: true },
73
93
  );
94
+
95
+ onUnmounted(() => {
96
+ window.removeEventListener("keydown", handleKeyDown);
97
+ });
74
98
  </script>
@@ -1 +1,3 @@
1
- export interface UixyModalProps {}
1
+ export interface UixyModalProps {
2
+ persistent?: boolean;
3
+ }
@@ -27,12 +27,16 @@
27
27
 
28
28
  <script setup lang="ts">
29
29
  import { AnimatePresence, motion } from "motion-v";
30
+ import { v4 as uuidv4 } from "uuid";
30
31
  import { usePosition } from "~/composables";
31
32
  import { onWatcherCleanup } from "vue";
32
33
  import type { UixyPopoverProps } from "./Popover.types";
33
34
 
34
35
  const props = defineProps<UixyPopoverProps>();
35
36
 
37
+ const POPOVER_GROUP_EVENT = "uixy:popover-group-open";
38
+ const instanceId = uuidv4();
39
+
36
40
  const { active, handleOpen, handleLeave, styles, refElement } = usePosition({
37
41
  direction: props.direction ?? "bottom",
38
42
  });
@@ -45,22 +49,49 @@
45
49
  handleLeave();
46
50
  };
47
51
 
52
+ const handleKeyDown = (e: KeyboardEvent) => {
53
+ if (e.key === "Escape") handleLeave();
54
+ };
55
+
48
56
  watch(
49
57
  () => active.value,
50
- (_, prevVal) => {
58
+ (isActive, prevVal) => {
51
59
  if (prevVal) return;
52
60
 
61
+ if (isActive && props.group) {
62
+ window.dispatchEvent(
63
+ new CustomEvent(POPOVER_GROUP_EVENT, { detail: { id: instanceId } }),
64
+ );
65
+ }
66
+
53
67
  requestAnimationFrame(() => {
54
68
  window.addEventListener("click", handleClickOutside);
69
+ window.addEventListener("keydown", handleKeyDown);
55
70
  });
56
71
 
57
72
  onWatcherCleanup(() => {
58
73
  window.removeEventListener("click", handleClickOutside);
74
+ window.removeEventListener("keydown", handleKeyDown);
59
75
  });
60
- }
76
+ },
61
77
  );
62
78
 
79
+ const handleGroupOpen = (e: Event) => {
80
+ const event = e as CustomEvent<{ id: string }>;
81
+ if (event.detail.id !== instanceId) handleLeave();
82
+ };
83
+
84
+ onMounted(() => {
85
+ if (props.group) {
86
+ window.addEventListener(POPOVER_GROUP_EVENT, handleGroupOpen);
87
+ }
88
+ });
89
+
63
90
  onUnmounted(() => {
64
91
  window.removeEventListener("click", handleClickOutside);
92
+ window.removeEventListener("keydown", handleKeyDown);
93
+ if (props.group) {
94
+ window.removeEventListener(POPOVER_GROUP_EVENT, handleGroupOpen);
95
+ }
65
96
  });
66
97
  </script>
@@ -1,4 +1,5 @@
1
1
  export interface UixyPopoverProps {
2
2
  direction?: "top" | "bottom";
3
3
  closeOnClick?: boolean;
4
+ group?: string;
4
5
  }
@@ -5,6 +5,7 @@
5
5
  :class="selectStyles({ status }, $attrs.class as string)"
6
6
  :tabIndex="props.disabled ? -1 : 0"
7
7
  :auto-focus="props.autoFocus"
8
+ ref="refTrigger"
8
9
  @click="handleOpen"
9
10
  @keydown="handleKeydownSelect"
10
11
  v-bind="filteredAttrs"
@@ -20,7 +21,7 @@
20
21
  :class="badgesWrapperStyles({ disabled: props.disabled })"
21
22
  >
22
23
  <uixy-badge
23
- v-for="(item, index) in model"
24
+ v-for="(item, index) in model as string[]"
24
25
  data-badge
25
26
  variant="tertiary"
26
27
  size="xs"
@@ -28,64 +29,113 @@
28
29
  :key="`${item}--${index}`"
29
30
  @click.stop="handleClickSelectedOption(item)"
30
31
  >
31
- {{ item }}
32
+ {{ getLabel(item) }}
32
33
  </uixy-badge>
33
34
  </div>
34
- <p v-else>{{ model }}</p>
35
+ <p v-else>{{ getLabel(model as string) }}</p>
35
36
 
37
+ <uixy-icon
38
+ v-if="props.deselectable && !props.multiple && model"
39
+ name="close"
40
+ class="absolute right-7 top-1/2 h-4 w-4 -translate-y-1/2 cursor-pointer z-10 text-gray-500 dark:text-gray-600 hover:text-black dark:hover:text-gray-400"
41
+ @click.stop="handleDeselect"
42
+ />
36
43
  <uixy-icon name="chevron-down" :class="iconStyles({ open })" />
37
44
  </div>
38
- <animate-presence>
39
- <motion.div
40
- v-if="open && !props.disabled"
41
- class="scrollbar absolute top-full z-20 mt-[2px] max-h-[320px] w-full overflow-y-auto rounded-1 border border-gray-300 bg-white p-1 text-sm dark:border-gray-900 dark:bg-gray-1000"
42
- :initial="{ opacity: 0, y: -4, scale: 0.8 }"
43
- :animate="{ opacity: 1, y: 0, scale: 1 }"
44
- :exit="{ opacity: 0, scale: 0.8 }"
45
- :transition="{ duration: 0.15, ease: 'easeInOut' }"
46
- select-dropdown
47
- >
48
- <div v-if="props.search" class="mb-2">
49
- <uixy-input
50
- v-model="search"
51
- status="default"
52
- icon="search"
53
- iconPositon="right"
54
- placeholder="Search..."
55
- hide-helper
56
- auto-focus
57
- />
58
- </div>
59
- <ul class="flex flex-col gap-1 py-1">
60
- <li
61
- v-for="option in filteredOptions"
62
- :tabIndex="option.disabled ? -1 : 0"
63
- :key="option.label"
64
- :class="
65
- itemStyles({
66
- selected: isSelected(option),
67
- disabled: !!option.disabled,
68
- })
69
- "
70
- @keydown="handleKeydownOption($event, option)"
71
- @click="handleClick(option.label)"
72
- >
73
- {{ option.label }}
74
- <uixy-icon
75
- v-if="isSelected(option)"
76
- name="check"
77
- class="absolute right-2 top-2 size-4"
78
- />
79
- </li>
80
- <li
81
- v-if="filteredOptions.length === 0"
82
- class="px-3 text-center dark:text-gray-600"
83
- >
84
- No results found
85
- </li>
86
- </ul>
87
- </motion.div>
88
- </animate-presence>
45
+
46
+ <teleport to="body">
47
+ <animate-presence>
48
+ <motion.div
49
+ v-if="open && !props.disabled"
50
+ class="scrollbar z-50 max-h-60 overflow-y-auto rounded-1 border border-gray-300 bg-white p-1 text-sm shadow-sm dark:border-gray-900 dark:bg-gray-1000"
51
+ :style="dropdownStyles"
52
+ :initial="{ opacity: 0, y: -4, scale: 0.98 }"
53
+ :animate="{ opacity: 1, y: 0, scale: 1 }"
54
+ :exit="{ opacity: 0, scale: 0.98 }"
55
+ :transition="{ duration: 0.12, ease: 'easeInOut' }"
56
+ >
57
+ <div ref="refDropdownEl" select-dropdown>
58
+ <div v-if="props.search" class="mb-2" @click.stop>
59
+ <uixy-input
60
+ ref="refSearchInput"
61
+ v-model="search"
62
+ status="default"
63
+ icon="search"
64
+ iconPositon="right"
65
+ placeholder="Search..."
66
+ hide-helper
67
+ />
68
+ </div>
69
+
70
+ <ul v-if="props.tree" class="flex flex-col gap-1 py-1">
71
+ <template
72
+ v-for="option in filteredOptions"
73
+ :key="option.value ?? option.label"
74
+ >
75
+ <SelectTreeItem
76
+ :option="option"
77
+ :depth="0"
78
+ :is-selected="isSelected"
79
+ :is-partially-selected="isPartiallySelected"
80
+ :expanded-items="effectiveExpandedItems"
81
+ :leaf-only="props.leafOnly"
82
+ :multiple="props.multiple"
83
+ :search-term="search"
84
+ :original-options="props.options"
85
+ @toggle-expand="toggleExpand"
86
+ @select="handleClick"
87
+ />
88
+ </template>
89
+
90
+ <li
91
+ v-if="filteredOptions.length === 0"
92
+ class="px-3 text-center dark:text-gray-600"
93
+ >
94
+ No results found
95
+ </li>
96
+ </ul>
97
+
98
+ <ul v-else class="flex flex-col gap-1 py-1">
99
+ <li
100
+ v-for="option in filteredOptions"
101
+ :tabIndex="option.disabled ? -1 : 0"
102
+ :key="option.value ?? option.label"
103
+ :class="
104
+ itemStyles({
105
+ selected: isSelected(option),
106
+ disabled: !!option.disabled,
107
+ })
108
+ "
109
+ @keydown="handleKeydownOption($event, option)"
110
+ @click="handleClick(option.value ?? option.label)"
111
+ >
112
+ {{ option.label }}
113
+ <uixy-icon
114
+ v-if="isSelected(option)"
115
+ name="check"
116
+ class="absolute right-2 top-2 size-4"
117
+ />
118
+ </li>
119
+
120
+ <li
121
+ v-if="customAddVisible"
122
+ class="relative py-2 px-3 rounded-1 cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-900 text-gray-500 dark:text-gray-600"
123
+ @click="handleCustomAdd"
124
+ >
125
+ Add: "{{ search }}"
126
+ </li>
127
+
128
+ <li
129
+ v-if="filteredOptions.length === 0 && !customAddVisible"
130
+ class="px-3 text-center dark:text-gray-600"
131
+ >
132
+ No results found
133
+ </li>
134
+ </ul>
135
+ </div>
136
+ </motion.div>
137
+ </animate-presence>
138
+ </teleport>
89
139
  <div v-if="props.label" :class="labelStyles({ status })">
90
140
  {{ props.label }}
91
141
  </div>
@@ -106,7 +156,8 @@
106
156
  </div>
107
157
  </template>
108
158
 
109
- <script setup lang="ts" generic="T extends (string | string[])">
159
+ <script setup lang="ts" generic="T extends string | string[]">
160
+ import { nextTick, onWatcherCleanup, onMounted } from "vue";
110
161
  import {
111
162
  selectStyles,
112
163
  iconStyles,
@@ -115,16 +166,13 @@
115
166
  helperStyles,
116
167
  badgesWrapperStyles,
117
168
  } from "./Select.styles";
118
- import type {
119
- UixySelectEmits,
120
- UixySelectOptionType,
121
- UixySelectProps,
122
- } from "./Select.types";
169
+ import type { UixySelectEmits, UixySelectProps } from "./Select.types";
123
170
  import { UixyBadge } from "../Badge";
124
171
  import { UixyIcon } from "../Icon";
125
172
  import { UixyInput } from "../Input";
126
173
  import { motion, AnimatePresence } from "motion-v";
127
174
  import { useSelect } from "./composables";
175
+ import SelectTreeItem from "./SelectTreeItem.vue";
128
176
 
129
177
  const props = defineProps<UixySelectProps>();
130
178
 
@@ -144,49 +192,155 @@
144
192
  return rest;
145
193
  });
146
194
 
147
- const handleChange = (v: string) => {
148
- if (props.multiple) {
149
- let prevArray = model.value as string[];
150
-
151
- if (!Array.isArray(prevArray)) prevArray = [];
195
+ const refTrigger = ref<HTMLElement>();
196
+ const refDropdownEl = ref<HTMLElement>();
197
+ const refSearchInput = ref<{ focus: () => void } | null>(null);
152
198
 
153
- if (prevArray.includes(v)) {
154
- (model.value as string[]) = prevArray.filter((item) => item !== v);
155
- } else {
156
- if (props.max && prevArray.length >= props.max)
157
- (model.value as string[]) = [...prevArray.slice(1), v];
158
-
159
- (model.value as string[]) = [...prevArray, v];
160
- }
161
- } else {
162
- (model.value as string) = v;
163
- }
164
- };
199
+ const dropdownStyles = ref<Record<string, string>>({
200
+ position: "absolute",
201
+ top: "0px",
202
+ left: "0px",
203
+ width: "0px",
204
+ });
165
205
 
166
206
  const {
167
207
  refOptions,
168
208
  open,
169
209
  search,
170
210
  filteredOptions,
211
+ effectiveExpandedItems,
212
+ customAddVisible,
213
+ isSelected,
214
+ isPartiallySelected,
215
+ getLabel,
216
+ toggleExpand,
171
217
  handleOpen,
172
218
  handleKeydownSelect,
173
219
  handleKeydownOption,
174
220
  handleClickSelectedOption,
175
- } = useSelect<T>(props.options, !!props.disabled, handleChange);
221
+ handleClick,
222
+ handleDeselect,
223
+ handleCustomAdd,
224
+ } = useSelect<T>(props, model as Ref<T>, emits, refDropdownEl);
225
+
226
+ const updateDropdownPosition = async () => {
227
+ if (!open.value || props.disabled) return;
228
+
229
+ await nextTick();
230
+ await new Promise<void>((r) => requestAnimationFrame(() => r()));
231
+
232
+ const trigger = refTrigger.value;
233
+ const dropdown = refDropdownEl.value;
234
+ if (!trigger || !dropdown) return;
235
+
236
+ const triggerRect = trigger.getBoundingClientRect();
237
+ const gap = 4;
238
+ const pad = 8;
239
+ const width = Math.max(80, triggerRect.width);
240
+
241
+ const scrollY = window.scrollY;
242
+ const scrollX = window.scrollX;
243
+
244
+ const cs = window.getComputedStyle(dropdown);
245
+ const maxH = Number.parseFloat(cs.maxHeight || "") || 240;
246
+ const desiredH = Math.min(dropdown.scrollHeight || 0, maxH);
247
+
248
+ const availableBelow = window.innerHeight - triggerRect.bottom - gap - pad;
249
+ const availableAbove = triggerRect.top - gap - pad;
176
250
 
177
- const handleClick = (label: string) => {
178
- handleChange(label);
251
+ const placeBelow =
252
+ desiredH <= availableBelow || availableBelow >= availableAbove;
179
253
 
180
- if (!props.multiple) open.value = false;
254
+ let top =
255
+ (placeBelow
256
+ ? triggerRect.bottom + gap
257
+ : triggerRect.top - gap - desiredH) + scrollY;
258
+
259
+ const minTop = scrollY + pad;
260
+ const maxTop = scrollY + window.innerHeight - desiredH - pad;
261
+ top = Math.min(Math.max(minTop, top), Math.max(minTop, maxTop));
262
+
263
+ let left = triggerRect.left + scrollX;
264
+ const minLeft = scrollX + pad;
265
+ const maxLeft = scrollX + window.innerWidth - width - pad;
266
+ left = Math.min(Math.max(minLeft, left), Math.max(minLeft, maxLeft));
267
+
268
+ dropdownStyles.value = {
269
+ position: "absolute",
270
+ top: `${Math.round(top)}px`,
271
+ left: `${Math.round(left)}px`,
272
+ width: `${Math.round(width)}px`,
273
+ };
274
+ };
275
+
276
+ const onResize = () => {
277
+ void updateDropdownPosition();
278
+ };
279
+
280
+ const onAnyScroll = () => {
281
+ void updateDropdownPosition();
181
282
  };
182
283
 
183
- const isSelected = (option: UixySelectOptionType) =>
184
- Array.isArray(model.value)
185
- ? model.value.includes(option.label)
186
- : model.value === option.label;
284
+ watch(
285
+ () => open.value,
286
+ (isOpen) => {
287
+ emits("openChange", isOpen);
288
+
289
+ if (!isOpen) return;
290
+
291
+ void updateDropdownPosition();
292
+
293
+ if (props.search) {
294
+ nextTick(() => refSearchInput.value?.focus());
295
+ }
296
+
297
+ window.addEventListener("resize", onResize);
298
+ if (props.followOnScroll) {
299
+ window.addEventListener("scroll", onAnyScroll, true);
300
+ }
301
+
302
+ onWatcherCleanup(() => {
303
+ window.removeEventListener("resize", onResize);
304
+ window.removeEventListener("scroll", onAnyScroll, true);
305
+ });
306
+ },
307
+ );
308
+
309
+ onMounted(() => {
310
+ if (props.autoOpen && !props.disabled) {
311
+ const delayMs = (props.delay ?? 0) * 1000;
312
+ setTimeout(() => {
313
+ nextTick(() => {
314
+ open.value = true;
315
+ });
316
+ }, delayMs);
317
+ }
318
+ });
319
+
320
+ watch(search, () => {
321
+ if (!open.value) return;
322
+ void updateDropdownPosition();
323
+ });
324
+
325
+ watch(
326
+ () => filteredOptions.value.length,
327
+ () => {
328
+ if (!open.value) return;
329
+ void updateDropdownPosition();
330
+ },
331
+ );
332
+
333
+ watch(
334
+ () => model.value,
335
+ () => {
336
+ if (!open.value) return;
337
+ void updateDropdownPosition();
338
+ },
339
+ { deep: true },
340
+ );
187
341
 
188
342
  const status = computed(() =>
189
- props.disabled ? "disabled" : props.status ?? "default"
343
+ props.disabled ? "disabled" : (props.status ?? "default"),
190
344
  );
191
345
 
192
346
  const text = computed(
@@ -196,6 +350,6 @@
196
350
  default: props.helperText,
197
351
  disabled: "",
198
352
  valid: "",
199
- }[props.status ?? "default"])
353
+ })[props.status ?? "default"],
200
354
  );
201
355
  </script>
@@ -13,7 +13,7 @@ export const selectStyles = styles(
13
13
  valid:
14
14
  "bg-white dark:bg-gray-1000 focus-visible:border-gray-500 dark:focus-visible:border-gray-600 cursor-pointer",
15
15
  },
16
- }
16
+ },
17
17
  );
18
18
 
19
19
  export const iconStyles = styles(
@@ -23,7 +23,7 @@ export const iconStyles = styles(
23
23
  true: "rotate-180",
24
24
  false: "",
25
25
  },
26
- }
26
+ },
27
27
  );
28
28
 
29
29
  export const labelStyles = styles(
@@ -36,7 +36,7 @@ export const labelStyles = styles(
36
36
  "text-gray-500 dark:text-gray-700 pointer-events-none select-none",
37
37
  valid: "",
38
38
  },
39
- }
39
+ },
40
40
  );
41
41
 
42
42
  export const itemStyles = styles(
@@ -50,7 +50,7 @@ export const itemStyles = styles(
50
50
  true: "pointer-events-none text-gray-500 dark:text-gray-700",
51
51
  false: "",
52
52
  },
53
- }
53
+ },
54
54
  );
55
55
 
56
56
  export const helperStyles = styles(
@@ -63,7 +63,7 @@ export const helperStyles = styles(
63
63
  "text-gray-400 dark:text-gray-800 pointer-events-none select-none",
64
64
  valid: "text-gray-600",
65
65
  },
66
- }
66
+ },
67
67
  );
68
68
 
69
69
  export const badgesWrapperStyles = styles("flex flex-wrap gap-1", {
@@ -1,6 +1,8 @@
1
1
  export interface UixySelectOptionType {
2
2
  label: string;
3
+ value?: string;
3
4
  disabled?: boolean;
5
+ children?: UixySelectOptionType[];
4
6
  }
5
7
 
6
8
  export interface UixyMultipleSelectProps {
@@ -13,6 +15,7 @@ export interface UixySingleSelectProps {
13
15
 
14
16
  export interface UixySelectEmits {
15
17
  (event: "change", v: string): void;
18
+ (event: "openChange", isOpen: boolean): void;
16
19
  }
17
20
 
18
21
  export type UixySelectProps = {
@@ -26,6 +29,14 @@ export type UixySelectProps = {
26
29
  helperText?: string;
27
30
  errorText?: string;
28
31
  autoFocus?: boolean;
29
- hideHelper?: boolean;
30
32
  max?: number;
33
+ tree?: boolean;
34
+ leafOnly?: boolean;
35
+ autoOpen?: boolean;
36
+ delay?: number;
37
+ hideHelper?: boolean;
38
+ deselectable?: boolean;
39
+ allowCustom?: boolean;
40
+ clearSearchOnSelect?: boolean;
41
+ followOnScroll?: boolean;
31
42
  } & (UixyMultipleSelectProps | UixySingleSelectProps);