cisse-vue-ui 0.8.0 → 0.8.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.
Files changed (72) hide show
  1. package/README.md +879 -879
  2. package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-DJbuHoDj.cjs → CheckboxGroup.vue_vue_type_script_setup_true_lang-DuJr8cz3.cjs} +4 -4
  3. package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-DuJr8cz3.cjs.map +1 -0
  4. package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-hHJdvUYS.js → CheckboxGroup.vue_vue_type_script_setup_true_lang-N4oS_DJD.js} +4 -4
  5. package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-N4oS_DJD.js.map +1 -0
  6. package/dist/{ConfirmDialog.vue_vue_type_script_setup_true_lang-Bine-xfp.cjs → ConfirmDialog.vue_vue_type_script_setup_true_lang-BGUoa5fT.cjs} +58 -9
  7. package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-BGUoa5fT.cjs.map +1 -0
  8. package/dist/{ConfirmDialog.vue_vue_type_script_setup_true_lang-DqkA1Zr-.js → ConfirmDialog.vue_vue_type_script_setup_true_lang-DWs2V7xX.js} +58 -9
  9. package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-DWs2V7xX.js.map +1 -0
  10. package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-56CxoSmj.js → Dropdown.vue_vue_type_script_setup_true_lang-A9Ax6iob.js} +110 -14
  11. package/dist/Dropdown.vue_vue_type_script_setup_true_lang-A9Ax6iob.js.map +1 -0
  12. package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-Dd3ySRNB.cjs → Dropdown.vue_vue_type_script_setup_true_lang-DoJKvn30.cjs} +109 -13
  13. package/dist/Dropdown.vue_vue_type_script_setup_true_lang-DoJKvn30.cjs.map +1 -0
  14. package/dist/{FilterTabs.vue_vue_type_script_setup_true_lang-DzLwUVCW.js → FilterTabs.vue_vue_type_script_setup_true_lang-CcOgc2Y_.js} +2 -2
  15. package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-CcOgc2Y_.js.map +1 -0
  16. package/dist/{FilterTabs.vue_vue_type_script_setup_true_lang-Bj3I5Sn7.cjs → FilterTabs.vue_vue_type_script_setup_true_lang-jW6Ikbvy.cjs} +2 -2
  17. package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-jW6Ikbvy.cjs.map +1 -0
  18. package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-BwtEbaiT.js.map +1 -1
  19. package/dist/ListSkeleton.vue_vue_type_script_setup_true_lang-DtwwmfWr.cjs.map +1 -1
  20. package/dist/{PageHero.vue_vue_type_script_setup_true_lang-DQQGYAw0.js → PageHero.vue_vue_type_script_setup_true_lang-Gvocjdqh.js} +2 -2
  21. package/dist/PageHero.vue_vue_type_script_setup_true_lang-Gvocjdqh.js.map +1 -0
  22. package/dist/{PageHero.vue_vue_type_script_setup_true_lang-Bi97ypMD.cjs → PageHero.vue_vue_type_script_setup_true_lang-rbvfGvll.cjs} +2 -2
  23. package/dist/PageHero.vue_vue_type_script_setup_true_lang-rbvfGvll.cjs.map +1 -0
  24. package/dist/cisse-vue-ui.css +57 -57
  25. package/dist/components/core/MenuItem.stories.d.ts +1 -0
  26. package/dist/components/core/MenuItem.vue.d.ts +2 -13
  27. package/dist/components/core/index.cjs +2 -2
  28. package/dist/components/core/index.js +2 -2
  29. package/dist/components/feedback/PaginationControls.stories.d.ts +5 -0
  30. package/dist/components/feedback/PaginationControls.vue.d.ts +2 -0
  31. package/dist/components/feedback/index.cjs +1 -1
  32. package/dist/components/feedback/index.js +1 -1
  33. package/dist/components/form/index.cjs +1 -1
  34. package/dist/components/form/index.js +1 -1
  35. package/dist/components/index.cjs +5 -5
  36. package/dist/components/index.js +5 -5
  37. package/dist/components/layout/index.cjs +1 -1
  38. package/dist/components/layout/index.js +1 -1
  39. package/dist/composables/index.cjs +1 -1
  40. package/dist/composables/index.js +1 -1
  41. package/dist/composables/useDropdown.d.ts +4 -1
  42. package/dist/{index-DwqCXgDx.cjs → index-5dQNEzd8.cjs} +6 -6
  43. package/dist/{index-DwqCXgDx.cjs.map → index-5dQNEzd8.cjs.map} +1 -1
  44. package/dist/{index-rBD1MYh-.js → index-CDDUEkXf.js} +6 -6
  45. package/dist/index-CDDUEkXf.js.map +1 -0
  46. package/dist/index.cjs +7 -7
  47. package/dist/index.js +7 -7
  48. package/dist/style.css +1 -1
  49. package/dist/{useDropdown-DHFnd259.cjs → useDropdown-DK4c5JGL.cjs} +66 -10
  50. package/dist/useDropdown-DK4c5JGL.cjs.map +1 -0
  51. package/dist/{useDropdown-iVu14E6s.js → useDropdown-De0cKI83.js} +66 -10
  52. package/dist/useDropdown-De0cKI83.js.map +1 -0
  53. package/dist/useFocusTrap-AnlJsihM.js.map +1 -1
  54. package/dist/useFocusTrap-kcxO8AeU.cjs.map +1 -1
  55. package/dist/useId-nxrBaIC9.cjs.map +1 -1
  56. package/dist/useId-xeHj7rkg.js.map +1 -1
  57. package/dist/useToast-Bk60GArg.cjs.map +1 -1
  58. package/dist/useToast-ina5g3mj.js.map +1 -1
  59. package/package.json +169 -169
  60. package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-DJbuHoDj.cjs.map +0 -1
  61. package/dist/CheckboxGroup.vue_vue_type_script_setup_true_lang-hHJdvUYS.js.map +0 -1
  62. package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-Bine-xfp.cjs.map +0 -1
  63. package/dist/ConfirmDialog.vue_vue_type_script_setup_true_lang-DqkA1Zr-.js.map +0 -1
  64. package/dist/Dropdown.vue_vue_type_script_setup_true_lang-56CxoSmj.js.map +0 -1
  65. package/dist/Dropdown.vue_vue_type_script_setup_true_lang-Dd3ySRNB.cjs.map +0 -1
  66. package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-Bj3I5Sn7.cjs.map +0 -1
  67. package/dist/FilterTabs.vue_vue_type_script_setup_true_lang-DzLwUVCW.js.map +0 -1
  68. package/dist/PageHero.vue_vue_type_script_setup_true_lang-Bi97ypMD.cjs.map +0 -1
  69. package/dist/PageHero.vue_vue_type_script_setup_true_lang-DQQGYAw0.js.map +0 -1
  70. package/dist/index-rBD1MYh-.js.map +0 -1
  71. package/dist/useDropdown-DHFnd259.cjs.map +0 -1
  72. package/dist/useDropdown-iVu14E6s.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDropdown-DK4c5JGL.cjs","sources":["../src/composables/useDropdown.ts"],"sourcesContent":["import { ref, computed, watch, onUnmounted, nextTick, type Ref, type ComputedRef } from 'vue'\r\n\r\nexport type DropdownPlacement =\r\n | 'bottom'\r\n | 'bottom-start'\r\n | 'bottom-end'\r\n | 'top'\r\n | 'top-start'\r\n | 'top-end'\r\n | 'right'\r\n | 'right-start'\r\n | 'right-end'\r\n | 'left'\r\n | 'left-start'\r\n | 'left-end'\r\n\r\nexport interface UseDropdownOptions {\r\n /** Whether teleport is enabled (affects position calculation) */\r\n teleport?: boolean\r\n /** Alignment for position calculation (deprecated, use placement instead) */\r\n align?: 'left' | 'right'\r\n /** Placement of the dropdown relative to the trigger */\r\n placement?: DropdownPlacement\r\n /** Gap between trigger and dropdown in pixels */\r\n gap?: number\r\n /** Callback when dropdown opens */\r\n onOpen?: () => void\r\n /** Callback when dropdown closes */\r\n onClose?: () => void\r\n}\r\n\r\nexport interface UseDropdownReturn {\r\n /** Whether the dropdown is currently open */\r\n isOpen: Ref<boolean>\r\n /** Current highlighted index for keyboard navigation */\r\n highlightedIndex: Ref<number>\r\n /** Calculated position for teleported dropdown */\r\n dropdownPosition: Ref<{ top: number; left: number; right: number; width: number }>\r\n /** Computed style object for teleported dropdown */\r\n dropdownStyle: ComputedRef<Record<string, string>>\r\n /** Open the dropdown */\r\n open: () => void\r\n /** Close the dropdown */\r\n close: () => void\r\n /** Toggle the dropdown */\r\n toggle: () => void\r\n /** Update position (call after DOM changes) */\r\n updatePosition: () => void\r\n /** Handle keyboard navigation */\r\n handleKeydown: (event: KeyboardEvent, options: KeyboardNavigationOptions) => void\r\n /** Scroll to highlighted item */\r\n scrollToHighlighted: (dropdownEl: HTMLElement | null) => void\r\n}\r\n\r\nexport interface KeyboardNavigationOptions {\r\n /** Total number of items to navigate */\r\n itemCount: number\r\n /** Called when Enter is pressed on a highlighted item */\r\n onSelect?: (index: number) => void\r\n /** Called when the dropdown should open (Space/Enter/ArrowDown when closed) */\r\n onOpen?: () => void\r\n /** Whether to handle open keys (Space/Enter/ArrowDown) when closed */\r\n handleOpenKeys?: boolean\r\n}\r\n\r\nexport function useDropdown(\r\n triggerRef: Ref<HTMLElement | null | undefined>,\r\n dropdownRef: Ref<HTMLElement | null | undefined>,\r\n options: UseDropdownOptions = {}\r\n): UseDropdownReturn {\r\n const { teleport = true, align = 'left', placement = 'bottom-start', gap = 8, onOpen, onClose } = options\r\n\r\n const isOpen = ref(false)\r\n const highlightedIndex = ref(-1)\r\n const dropdownPosition = ref({ top: 0, left: 0, right: 0, width: 0 })\r\n\r\n const updatePosition = () => {\r\n if (!triggerRef.value || !teleport) return\r\n const rect = triggerRef.value.getBoundingClientRect()\r\n\r\n // Calculate position based on placement\r\n const [side, alignment = 'start'] = placement.split('-') as [string, string?]\r\n\r\n let top = 0\r\n let left = 0\r\n let right = 0\r\n\r\n // Vertical positioning\r\n switch (side) {\r\n case 'bottom':\r\n top = rect.bottom + window.scrollY + gap\r\n break\r\n case 'top':\r\n top = rect.top + window.scrollY - gap\r\n break\r\n case 'left':\r\n case 'right':\r\n // Vertical alignment for horizontal placements\r\n if (alignment === 'start') {\r\n top = rect.top + window.scrollY\r\n } else if (alignment === 'end') {\r\n top = rect.bottom + window.scrollY\r\n } else {\r\n // center\r\n top = rect.top + window.scrollY + rect.height / 2\r\n }\r\n break\r\n }\r\n\r\n // Horizontal positioning\r\n switch (side) {\r\n case 'left':\r\n right = window.innerWidth - rect.left + gap\r\n left = 0\r\n break\r\n case 'right':\r\n left = rect.right + window.scrollX + gap\r\n right = 0\r\n break\r\n case 'top':\r\n case 'bottom':\r\n // Horizontal alignment for vertical placements\r\n if (alignment === 'start' || align === 'left') {\r\n left = rect.left + window.scrollX\r\n right = 0\r\n } else if (alignment === 'end' || align === 'right') {\r\n right = window.innerWidth - rect.right - window.scrollX\r\n left = 0\r\n } else {\r\n // center\r\n left = rect.left + window.scrollX + rect.width / 2\r\n right = 0\r\n }\r\n break\r\n }\r\n\r\n dropdownPosition.value = {\r\n top,\r\n left,\r\n right,\r\n width: rect.width,\r\n }\r\n }\r\n\r\n const open = () => {\r\n isOpen.value = true\r\n nextTick(updatePosition)\r\n onOpen?.()\r\n }\r\n\r\n const close = () => {\r\n isOpen.value = false\r\n highlightedIndex.value = -1\r\n onClose?.()\r\n }\r\n\r\n const toggle = () => {\r\n if (isOpen.value) {\r\n close()\r\n } else {\r\n open()\r\n }\r\n }\r\n\r\n const handleClickOutside = (event: MouseEvent) => {\r\n const target = event.target as Node\r\n const isInsideTrigger = triggerRef.value?.contains(target)\r\n const isInsideDropdown = dropdownRef.value?.contains(target)\r\n if (!isInsideTrigger && !isInsideDropdown) {\r\n close()\r\n }\r\n }\r\n\r\n const scrollToHighlighted = (dropdownEl: HTMLElement | null) => {\r\n nextTick(() => {\r\n if (dropdownEl) {\r\n const highlighted = dropdownEl.querySelector(\r\n `[data-index=\"${highlightedIndex.value}\"]`\r\n ) as HTMLElement\r\n if (highlighted) {\r\n highlighted.scrollIntoView({ block: 'nearest' })\r\n }\r\n }\r\n })\r\n }\r\n\r\n const handleKeydown = (event: KeyboardEvent, navOptions: KeyboardNavigationOptions) => {\r\n const { itemCount, onSelect, onOpen: onOpenNav, handleOpenKeys = false } = navOptions\r\n\r\n if (!isOpen.value) {\r\n if (handleOpenKeys && (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown')) {\r\n event.preventDefault()\r\n onOpenNav?.()\r\n open()\r\n }\r\n return\r\n }\r\n\r\n switch (event.key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n highlightedIndex.value = Math.min(highlightedIndex.value + 1, itemCount - 1)\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0)\r\n break\r\n case 'Enter':\r\n event.preventDefault()\r\n if (highlightedIndex.value >= 0) {\r\n onSelect?.(highlightedIndex.value)\r\n }\r\n break\r\n case 'Escape':\r\n event.preventDefault()\r\n close()\r\n break\r\n case 'Tab':\r\n close()\r\n break\r\n }\r\n }\r\n\r\n const dropdownStyle = computed(() => {\r\n if (!teleport) return {} as Record<string, string>\r\n\r\n const [side] = placement.split('-')\r\n const isHorizontal = side === 'left' || side === 'right'\r\n\r\n const style: Record<string, string> = {\r\n position: 'absolute',\r\n top: `${dropdownPosition.value.top}px`,\r\n }\r\n\r\n // For horizontal placements, don't set width\r\n if (isHorizontal) {\r\n if (side === 'left') {\r\n style.right = `${dropdownPosition.value.right}px`\r\n } else {\r\n style.left = `${dropdownPosition.value.left}px`\r\n }\r\n } else {\r\n // Vertical placements (bottom/top)\r\n style.left = dropdownPosition.value.right ? 'auto' : `${dropdownPosition.value.left}px`\r\n style.right = dropdownPosition.value.right ? `${dropdownPosition.value.right}px` : 'auto'\r\n style.width = `${dropdownPosition.value.width}px`\r\n }\r\n\r\n return style\r\n })\r\n\r\n // Event listener management\r\n watch(isOpen, (newValue) => {\r\n if (newValue) {\r\n document.addEventListener('click', handleClickOutside)\r\n window.addEventListener('scroll', updatePosition, true)\r\n window.addEventListener('resize', updatePosition)\r\n } else {\r\n document.removeEventListener('click', handleClickOutside)\r\n window.removeEventListener('scroll', updatePosition, true)\r\n window.removeEventListener('resize', updatePosition)\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n document.removeEventListener('click', handleClickOutside)\r\n window.removeEventListener('scroll', updatePosition, true)\r\n window.removeEventListener('resize', updatePosition)\r\n })\r\n\r\n return {\r\n isOpen,\r\n highlightedIndex,\r\n dropdownPosition,\r\n dropdownStyle,\r\n open,\r\n close,\r\n toggle,\r\n updatePosition,\r\n handleKeydown,\r\n scrollToHighlighted,\r\n }\r\n}\r\n"],"names":["ref","nextTick","computed","watch","onUnmounted"],"mappings":"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"}
@@ -1,16 +1,61 @@
1
1
  import { ref, computed, watch, onUnmounted, nextTick } from "vue";
2
2
  function useDropdown(triggerRef, dropdownRef, options = {}) {
3
- const { teleport = true, align = "left", gap = 8, onOpen, onClose } = options;
3
+ const { teleport = true, align = "left", placement = "bottom-start", gap = 8, onOpen, onClose } = options;
4
4
  const isOpen = ref(false);
5
5
  const highlightedIndex = ref(-1);
6
6
  const dropdownPosition = ref({ top: 0, left: 0, right: 0, width: 0 });
7
7
  const updatePosition = () => {
8
8
  if (!triggerRef.value || !teleport) return;
9
9
  const rect = triggerRef.value.getBoundingClientRect();
10
+ const [side, alignment = "start"] = placement.split("-");
11
+ let top = 0;
12
+ let left = 0;
13
+ let right = 0;
14
+ switch (side) {
15
+ case "bottom":
16
+ top = rect.bottom + window.scrollY + gap;
17
+ break;
18
+ case "top":
19
+ top = rect.top + window.scrollY - gap;
20
+ break;
21
+ case "left":
22
+ case "right":
23
+ if (alignment === "start") {
24
+ top = rect.top + window.scrollY;
25
+ } else if (alignment === "end") {
26
+ top = rect.bottom + window.scrollY;
27
+ } else {
28
+ top = rect.top + window.scrollY + rect.height / 2;
29
+ }
30
+ break;
31
+ }
32
+ switch (side) {
33
+ case "left":
34
+ right = window.innerWidth - rect.left + gap;
35
+ left = 0;
36
+ break;
37
+ case "right":
38
+ left = rect.right + window.scrollX + gap;
39
+ right = 0;
40
+ break;
41
+ case "top":
42
+ case "bottom":
43
+ if (alignment === "start" || align === "left") {
44
+ left = rect.left + window.scrollX;
45
+ right = 0;
46
+ } else if (alignment === "end" || align === "right") {
47
+ right = window.innerWidth - rect.right - window.scrollX;
48
+ left = 0;
49
+ } else {
50
+ left = rect.left + window.scrollX + rect.width / 2;
51
+ right = 0;
52
+ }
53
+ break;
54
+ }
10
55
  dropdownPosition.value = {
11
- top: rect.bottom + window.scrollY + gap,
12
- left: rect.left + window.scrollX,
13
- right: window.innerWidth - rect.right - window.scrollX,
56
+ top,
57
+ left,
58
+ right,
14
59
  width: rect.width
15
60
  };
16
61
  };
@@ -88,13 +133,24 @@ function useDropdown(triggerRef, dropdownRef, options = {}) {
88
133
  };
89
134
  const dropdownStyle = computed(() => {
90
135
  if (!teleport) return {};
91
- return {
136
+ const [side] = placement.split("-");
137
+ const isHorizontal = side === "left" || side === "right";
138
+ const style = {
92
139
  position: "absolute",
93
- top: `${dropdownPosition.value.top}px`,
94
- left: align === "right" ? "auto" : `${dropdownPosition.value.left}px`,
95
- right: align === "right" ? `${dropdownPosition.value.right}px` : "auto",
96
- width: `${dropdownPosition.value.width}px`
140
+ top: `${dropdownPosition.value.top}px`
97
141
  };
142
+ if (isHorizontal) {
143
+ if (side === "left") {
144
+ style.right = `${dropdownPosition.value.right}px`;
145
+ } else {
146
+ style.left = `${dropdownPosition.value.left}px`;
147
+ }
148
+ } else {
149
+ style.left = dropdownPosition.value.right ? "auto" : `${dropdownPosition.value.left}px`;
150
+ style.right = dropdownPosition.value.right ? `${dropdownPosition.value.right}px` : "auto";
151
+ style.width = `${dropdownPosition.value.width}px`;
152
+ }
153
+ return style;
98
154
  });
99
155
  watch(isOpen, (newValue) => {
100
156
  if (newValue) {
@@ -128,4 +184,4 @@ function useDropdown(triggerRef, dropdownRef, options = {}) {
128
184
  export {
129
185
  useDropdown as u
130
186
  };
131
- //# sourceMappingURL=useDropdown-iVu14E6s.js.map
187
+ //# sourceMappingURL=useDropdown-De0cKI83.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDropdown-De0cKI83.js","sources":["../src/composables/useDropdown.ts"],"sourcesContent":["import { ref, computed, watch, onUnmounted, nextTick, type Ref, type ComputedRef } from 'vue'\r\n\r\nexport type DropdownPlacement =\r\n | 'bottom'\r\n | 'bottom-start'\r\n | 'bottom-end'\r\n | 'top'\r\n | 'top-start'\r\n | 'top-end'\r\n | 'right'\r\n | 'right-start'\r\n | 'right-end'\r\n | 'left'\r\n | 'left-start'\r\n | 'left-end'\r\n\r\nexport interface UseDropdownOptions {\r\n /** Whether teleport is enabled (affects position calculation) */\r\n teleport?: boolean\r\n /** Alignment for position calculation (deprecated, use placement instead) */\r\n align?: 'left' | 'right'\r\n /** Placement of the dropdown relative to the trigger */\r\n placement?: DropdownPlacement\r\n /** Gap between trigger and dropdown in pixels */\r\n gap?: number\r\n /** Callback when dropdown opens */\r\n onOpen?: () => void\r\n /** Callback when dropdown closes */\r\n onClose?: () => void\r\n}\r\n\r\nexport interface UseDropdownReturn {\r\n /** Whether the dropdown is currently open */\r\n isOpen: Ref<boolean>\r\n /** Current highlighted index for keyboard navigation */\r\n highlightedIndex: Ref<number>\r\n /** Calculated position for teleported dropdown */\r\n dropdownPosition: Ref<{ top: number; left: number; right: number; width: number }>\r\n /** Computed style object for teleported dropdown */\r\n dropdownStyle: ComputedRef<Record<string, string>>\r\n /** Open the dropdown */\r\n open: () => void\r\n /** Close the dropdown */\r\n close: () => void\r\n /** Toggle the dropdown */\r\n toggle: () => void\r\n /** Update position (call after DOM changes) */\r\n updatePosition: () => void\r\n /** Handle keyboard navigation */\r\n handleKeydown: (event: KeyboardEvent, options: KeyboardNavigationOptions) => void\r\n /** Scroll to highlighted item */\r\n scrollToHighlighted: (dropdownEl: HTMLElement | null) => void\r\n}\r\n\r\nexport interface KeyboardNavigationOptions {\r\n /** Total number of items to navigate */\r\n itemCount: number\r\n /** Called when Enter is pressed on a highlighted item */\r\n onSelect?: (index: number) => void\r\n /** Called when the dropdown should open (Space/Enter/ArrowDown when closed) */\r\n onOpen?: () => void\r\n /** Whether to handle open keys (Space/Enter/ArrowDown) when closed */\r\n handleOpenKeys?: boolean\r\n}\r\n\r\nexport function useDropdown(\r\n triggerRef: Ref<HTMLElement | null | undefined>,\r\n dropdownRef: Ref<HTMLElement | null | undefined>,\r\n options: UseDropdownOptions = {}\r\n): UseDropdownReturn {\r\n const { teleport = true, align = 'left', placement = 'bottom-start', gap = 8, onOpen, onClose } = options\r\n\r\n const isOpen = ref(false)\r\n const highlightedIndex = ref(-1)\r\n const dropdownPosition = ref({ top: 0, left: 0, right: 0, width: 0 })\r\n\r\n const updatePosition = () => {\r\n if (!triggerRef.value || !teleport) return\r\n const rect = triggerRef.value.getBoundingClientRect()\r\n\r\n // Calculate position based on placement\r\n const [side, alignment = 'start'] = placement.split('-') as [string, string?]\r\n\r\n let top = 0\r\n let left = 0\r\n let right = 0\r\n\r\n // Vertical positioning\r\n switch (side) {\r\n case 'bottom':\r\n top = rect.bottom + window.scrollY + gap\r\n break\r\n case 'top':\r\n top = rect.top + window.scrollY - gap\r\n break\r\n case 'left':\r\n case 'right':\r\n // Vertical alignment for horizontal placements\r\n if (alignment === 'start') {\r\n top = rect.top + window.scrollY\r\n } else if (alignment === 'end') {\r\n top = rect.bottom + window.scrollY\r\n } else {\r\n // center\r\n top = rect.top + window.scrollY + rect.height / 2\r\n }\r\n break\r\n }\r\n\r\n // Horizontal positioning\r\n switch (side) {\r\n case 'left':\r\n right = window.innerWidth - rect.left + gap\r\n left = 0\r\n break\r\n case 'right':\r\n left = rect.right + window.scrollX + gap\r\n right = 0\r\n break\r\n case 'top':\r\n case 'bottom':\r\n // Horizontal alignment for vertical placements\r\n if (alignment === 'start' || align === 'left') {\r\n left = rect.left + window.scrollX\r\n right = 0\r\n } else if (alignment === 'end' || align === 'right') {\r\n right = window.innerWidth - rect.right - window.scrollX\r\n left = 0\r\n } else {\r\n // center\r\n left = rect.left + window.scrollX + rect.width / 2\r\n right = 0\r\n }\r\n break\r\n }\r\n\r\n dropdownPosition.value = {\r\n top,\r\n left,\r\n right,\r\n width: rect.width,\r\n }\r\n }\r\n\r\n const open = () => {\r\n isOpen.value = true\r\n nextTick(updatePosition)\r\n onOpen?.()\r\n }\r\n\r\n const close = () => {\r\n isOpen.value = false\r\n highlightedIndex.value = -1\r\n onClose?.()\r\n }\r\n\r\n const toggle = () => {\r\n if (isOpen.value) {\r\n close()\r\n } else {\r\n open()\r\n }\r\n }\r\n\r\n const handleClickOutside = (event: MouseEvent) => {\r\n const target = event.target as Node\r\n const isInsideTrigger = triggerRef.value?.contains(target)\r\n const isInsideDropdown = dropdownRef.value?.contains(target)\r\n if (!isInsideTrigger && !isInsideDropdown) {\r\n close()\r\n }\r\n }\r\n\r\n const scrollToHighlighted = (dropdownEl: HTMLElement | null) => {\r\n nextTick(() => {\r\n if (dropdownEl) {\r\n const highlighted = dropdownEl.querySelector(\r\n `[data-index=\"${highlightedIndex.value}\"]`\r\n ) as HTMLElement\r\n if (highlighted) {\r\n highlighted.scrollIntoView({ block: 'nearest' })\r\n }\r\n }\r\n })\r\n }\r\n\r\n const handleKeydown = (event: KeyboardEvent, navOptions: KeyboardNavigationOptions) => {\r\n const { itemCount, onSelect, onOpen: onOpenNav, handleOpenKeys = false } = navOptions\r\n\r\n if (!isOpen.value) {\r\n if (handleOpenKeys && (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown')) {\r\n event.preventDefault()\r\n onOpenNav?.()\r\n open()\r\n }\r\n return\r\n }\r\n\r\n switch (event.key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n highlightedIndex.value = Math.min(highlightedIndex.value + 1, itemCount - 1)\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0)\r\n break\r\n case 'Enter':\r\n event.preventDefault()\r\n if (highlightedIndex.value >= 0) {\r\n onSelect?.(highlightedIndex.value)\r\n }\r\n break\r\n case 'Escape':\r\n event.preventDefault()\r\n close()\r\n break\r\n case 'Tab':\r\n close()\r\n break\r\n }\r\n }\r\n\r\n const dropdownStyle = computed(() => {\r\n if (!teleport) return {} as Record<string, string>\r\n\r\n const [side] = placement.split('-')\r\n const isHorizontal = side === 'left' || side === 'right'\r\n\r\n const style: Record<string, string> = {\r\n position: 'absolute',\r\n top: `${dropdownPosition.value.top}px`,\r\n }\r\n\r\n // For horizontal placements, don't set width\r\n if (isHorizontal) {\r\n if (side === 'left') {\r\n style.right = `${dropdownPosition.value.right}px`\r\n } else {\r\n style.left = `${dropdownPosition.value.left}px`\r\n }\r\n } else {\r\n // Vertical placements (bottom/top)\r\n style.left = dropdownPosition.value.right ? 'auto' : `${dropdownPosition.value.left}px`\r\n style.right = dropdownPosition.value.right ? `${dropdownPosition.value.right}px` : 'auto'\r\n style.width = `${dropdownPosition.value.width}px`\r\n }\r\n\r\n return style\r\n })\r\n\r\n // Event listener management\r\n watch(isOpen, (newValue) => {\r\n if (newValue) {\r\n document.addEventListener('click', handleClickOutside)\r\n window.addEventListener('scroll', updatePosition, true)\r\n window.addEventListener('resize', updatePosition)\r\n } else {\r\n document.removeEventListener('click', handleClickOutside)\r\n window.removeEventListener('scroll', updatePosition, true)\r\n window.removeEventListener('resize', updatePosition)\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n document.removeEventListener('click', handleClickOutside)\r\n window.removeEventListener('scroll', updatePosition, true)\r\n window.removeEventListener('resize', updatePosition)\r\n })\r\n\r\n return {\r\n isOpen,\r\n highlightedIndex,\r\n dropdownPosition,\r\n dropdownStyle,\r\n open,\r\n close,\r\n toggle,\r\n updatePosition,\r\n handleKeydown,\r\n scrollToHighlighted,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFocusTrap-AnlJsihM.js","sources":["../src/composables/useFocusTrap.ts"],"sourcesContent":["import { ref, watch, onUnmounted, getCurrentInstance, type Ref } from 'vue'\n\nconst FOCUSABLE_SELECTORS = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n].join(', ')\n\nexport interface UseFocusTrapOptions {\n /** Whether the focus trap is active */\n active?: Ref<boolean> | boolean\n /** Focus the first focusable element when trap activates */\n focusFirst?: boolean\n /** Restore focus to the previously focused element when trap deactivates */\n restoreFocus?: boolean\n /** Initial element to focus (selector or element) */\n initialFocus?: string | HTMLElement | null\n}\n\nexport interface UseFocusTrapReturn {\n /** Ref to attach to the container element */\n containerRef: Ref<HTMLElement | null>\n /** Activate the focus trap manually */\n activate: () => void\n /** Deactivate the focus trap manually */\n deactivate: () => void\n /** Whether the focus trap is currently active */\n isActive: Ref<boolean>\n}\n\n/**\n * Composable for trapping focus within a container element (for modals, dialogs, etc.)\n *\n * @example\n * ```vue\n * <script setup>\n * const isOpen = ref(false)\n * const { containerRef } = useFocusTrap({ active: isOpen })\n * </script>\n *\n * <template>\n * <div v-if=\"isOpen\" ref=\"containerRef\" role=\"dialog\">\n * <button>First focusable</button>\n * <input type=\"text\" />\n * <button @click=\"isOpen = false\">Close</button>\n * </div>\n * </template>\n * ```\n */\nexport function useFocusTrap(options: UseFocusTrapOptions = {}): UseFocusTrapReturn {\n const { focusFirst = true, restoreFocus = true, initialFocus = null } = options\n\n const containerRef = ref<HTMLElement | null>(null)\n const isActive = ref(false)\n const previouslyFocusedElement = ref<HTMLElement | null>(null)\n\n function getFocusableElements(): HTMLElement[] {\n if (!containerRef.value) return []\n return Array.from(containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(\n (el) => el.offsetParent !== null, // Element is visible\n )\n }\n\n function getInitialFocusElement(): HTMLElement | null {\n if (!containerRef.value) return null\n\n if (initialFocus) {\n if (typeof initialFocus === 'string') {\n return containerRef.value.querySelector<HTMLElement>(initialFocus)\n }\n return initialFocus\n }\n\n const focusable = getFocusableElements()\n return focusable[0] || null\n }\n\n function handleKeyDown(event: KeyboardEvent): void {\n if (event.key !== 'Tab' || !containerRef.value) return\n\n const focusable = getFocusableElements()\n if (focusable.length === 0) return\n\n const firstFocusable = focusable[0]\n const lastFocusable = focusable[focusable.length - 1]\n const activeElement = document.activeElement as HTMLElement\n\n if (event.shiftKey) {\n // Shift + Tab: move backwards\n if (activeElement === firstFocusable || !containerRef.value.contains(activeElement)) {\n event.preventDefault()\n lastFocusable.focus()\n }\n } else {\n // Tab: move forwards\n if (activeElement === lastFocusable || !containerRef.value.contains(activeElement)) {\n event.preventDefault()\n firstFocusable.focus()\n }\n }\n }\n\n function activate(): void {\n if (isActive.value) return\n\n // Store the currently focused element\n previouslyFocusedElement.value = document.activeElement as HTMLElement\n\n isActive.value = true\n\n // Add event listener\n document.addEventListener('keydown', handleKeyDown)\n\n // Focus initial element after a tick to ensure DOM is ready\n if (focusFirst) {\n requestAnimationFrame(() => {\n const initialElement = getInitialFocusElement()\n if (initialElement) {\n initialElement.focus()\n }\n })\n }\n }\n\n function deactivate(): void {\n if (!isActive.value) return\n\n isActive.value = false\n\n // Remove event listener\n document.removeEventListener('keydown', handleKeyDown)\n\n // Restore focus\n if (restoreFocus && previouslyFocusedElement.value) {\n previouslyFocusedElement.value.focus()\n previouslyFocusedElement.value = null\n }\n }\n\n // Handle reactive active option\n const activeOption = options.active\n if (activeOption !== undefined) {\n if (typeof activeOption === 'boolean') {\n if (activeOption) {\n // Wait for containerRef to be set\n const unwatch = watch(\n containerRef,\n (container) => {\n if (container) {\n activate()\n unwatch()\n }\n },\n { immediate: true },\n )\n }\n } else {\n // It's a Ref<boolean>\n watch(\n [activeOption, containerRef],\n ([active, container]) => {\n if (active && container) {\n activate()\n } else if (!active) {\n deactivate()\n }\n },\n { immediate: true },\n )\n }\n }\n\n // Cleanup on unmount (only when used inside a component)\n if (getCurrentInstance()) {\n onUnmounted(() => {\n deactivate()\n })\n }\n\n return {\n containerRef,\n activate,\n deactivate,\n isActive,\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useFocusTrap-AnlJsihM.js","sources":["../src/composables/useFocusTrap.ts"],"sourcesContent":["import { ref, watch, onUnmounted, getCurrentInstance, type Ref } from 'vue'\r\n\r\nconst FOCUSABLE_SELECTORS = [\r\n 'a[href]',\r\n 'button:not([disabled])',\r\n 'input:not([disabled])',\r\n 'select:not([disabled])',\r\n 'textarea:not([disabled])',\r\n '[tabindex]:not([tabindex=\"-1\"])',\r\n '[contenteditable=\"true\"]',\r\n].join(', ')\r\n\r\nexport interface UseFocusTrapOptions {\r\n /** Whether the focus trap is active */\r\n active?: Ref<boolean> | boolean\r\n /** Focus the first focusable element when trap activates */\r\n focusFirst?: boolean\r\n /** Restore focus to the previously focused element when trap deactivates */\r\n restoreFocus?: boolean\r\n /** Initial element to focus (selector or element) */\r\n initialFocus?: string | HTMLElement | null\r\n}\r\n\r\nexport interface UseFocusTrapReturn {\r\n /** Ref to attach to the container element */\r\n containerRef: Ref<HTMLElement | null>\r\n /** Activate the focus trap manually */\r\n activate: () => void\r\n /** Deactivate the focus trap manually */\r\n deactivate: () => void\r\n /** Whether the focus trap is currently active */\r\n isActive: Ref<boolean>\r\n}\r\n\r\n/**\r\n * Composable for trapping focus within a container element (for modals, dialogs, etc.)\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup>\r\n * const isOpen = ref(false)\r\n * const { containerRef } = useFocusTrap({ active: isOpen })\r\n * </script>\r\n *\r\n * <template>\r\n * <div v-if=\"isOpen\" ref=\"containerRef\" role=\"dialog\">\r\n * <button>First focusable</button>\r\n * <input type=\"text\" />\r\n * <button @click=\"isOpen = false\">Close</button>\r\n * </div>\r\n * </template>\r\n * ```\r\n */\r\nexport function useFocusTrap(options: UseFocusTrapOptions = {}): UseFocusTrapReturn {\r\n const { focusFirst = true, restoreFocus = true, initialFocus = null } = options\r\n\r\n const containerRef = ref<HTMLElement | null>(null)\r\n const isActive = ref(false)\r\n const previouslyFocusedElement = ref<HTMLElement | null>(null)\r\n\r\n function getFocusableElements(): HTMLElement[] {\r\n if (!containerRef.value) return []\r\n return Array.from(containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(\r\n (el) => el.offsetParent !== null, // Element is visible\r\n )\r\n }\r\n\r\n function getInitialFocusElement(): HTMLElement | null {\r\n if (!containerRef.value) return null\r\n\r\n if (initialFocus) {\r\n if (typeof initialFocus === 'string') {\r\n return containerRef.value.querySelector<HTMLElement>(initialFocus)\r\n }\r\n return initialFocus\r\n }\r\n\r\n const focusable = getFocusableElements()\r\n return focusable[0] || null\r\n }\r\n\r\n function handleKeyDown(event: KeyboardEvent): void {\r\n if (event.key !== 'Tab' || !containerRef.value) return\r\n\r\n const focusable = getFocusableElements()\r\n if (focusable.length === 0) return\r\n\r\n const firstFocusable = focusable[0]\r\n const lastFocusable = focusable[focusable.length - 1]\r\n const activeElement = document.activeElement as HTMLElement\r\n\r\n if (event.shiftKey) {\r\n // Shift + Tab: move backwards\r\n if (activeElement === firstFocusable || !containerRef.value.contains(activeElement)) {\r\n event.preventDefault()\r\n lastFocusable.focus()\r\n }\r\n } else {\r\n // Tab: move forwards\r\n if (activeElement === lastFocusable || !containerRef.value.contains(activeElement)) {\r\n event.preventDefault()\r\n firstFocusable.focus()\r\n }\r\n }\r\n }\r\n\r\n function activate(): void {\r\n if (isActive.value) return\r\n\r\n // Store the currently focused element\r\n previouslyFocusedElement.value = document.activeElement as HTMLElement\r\n\r\n isActive.value = true\r\n\r\n // Add event listener\r\n document.addEventListener('keydown', handleKeyDown)\r\n\r\n // Focus initial element after a tick to ensure DOM is ready\r\n if (focusFirst) {\r\n requestAnimationFrame(() => {\r\n const initialElement = getInitialFocusElement()\r\n if (initialElement) {\r\n initialElement.focus()\r\n }\r\n })\r\n }\r\n }\r\n\r\n function deactivate(): void {\r\n if (!isActive.value) return\r\n\r\n isActive.value = false\r\n\r\n // Remove event listener\r\n document.removeEventListener('keydown', handleKeyDown)\r\n\r\n // Restore focus\r\n if (restoreFocus && previouslyFocusedElement.value) {\r\n previouslyFocusedElement.value.focus()\r\n previouslyFocusedElement.value = null\r\n }\r\n }\r\n\r\n // Handle reactive active option\r\n const activeOption = options.active\r\n if (activeOption !== undefined) {\r\n if (typeof activeOption === 'boolean') {\r\n if (activeOption) {\r\n // Wait for containerRef to be set\r\n const unwatch = watch(\r\n containerRef,\r\n (container) => {\r\n if (container) {\r\n activate()\r\n unwatch()\r\n }\r\n },\r\n { immediate: true },\r\n )\r\n }\r\n } else {\r\n // It's a Ref<boolean>\r\n watch(\r\n [activeOption, containerRef],\r\n ([active, container]) => {\r\n if (active && container) {\r\n activate()\r\n } else if (!active) {\r\n deactivate()\r\n }\r\n },\r\n { immediate: true },\r\n )\r\n }\r\n }\r\n\r\n // Cleanup on unmount (only when used inside a component)\r\n if (getCurrentInstance()) {\r\n onUnmounted(() => {\r\n deactivate()\r\n })\r\n }\r\n\r\n return {\r\n containerRef,\r\n activate,\r\n deactivate,\r\n isActive,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFocusTrap-kcxO8AeU.cjs","sources":["../src/composables/useFocusTrap.ts"],"sourcesContent":["import { ref, watch, onUnmounted, getCurrentInstance, type Ref } from 'vue'\n\nconst FOCUSABLE_SELECTORS = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n].join(', ')\n\nexport interface UseFocusTrapOptions {\n /** Whether the focus trap is active */\n active?: Ref<boolean> | boolean\n /** Focus the first focusable element when trap activates */\n focusFirst?: boolean\n /** Restore focus to the previously focused element when trap deactivates */\n restoreFocus?: boolean\n /** Initial element to focus (selector or element) */\n initialFocus?: string | HTMLElement | null\n}\n\nexport interface UseFocusTrapReturn {\n /** Ref to attach to the container element */\n containerRef: Ref<HTMLElement | null>\n /** Activate the focus trap manually */\n activate: () => void\n /** Deactivate the focus trap manually */\n deactivate: () => void\n /** Whether the focus trap is currently active */\n isActive: Ref<boolean>\n}\n\n/**\n * Composable for trapping focus within a container element (for modals, dialogs, etc.)\n *\n * @example\n * ```vue\n * <script setup>\n * const isOpen = ref(false)\n * const { containerRef } = useFocusTrap({ active: isOpen })\n * </script>\n *\n * <template>\n * <div v-if=\"isOpen\" ref=\"containerRef\" role=\"dialog\">\n * <button>First focusable</button>\n * <input type=\"text\" />\n * <button @click=\"isOpen = false\">Close</button>\n * </div>\n * </template>\n * ```\n */\nexport function useFocusTrap(options: UseFocusTrapOptions = {}): UseFocusTrapReturn {\n const { focusFirst = true, restoreFocus = true, initialFocus = null } = options\n\n const containerRef = ref<HTMLElement | null>(null)\n const isActive = ref(false)\n const previouslyFocusedElement = ref<HTMLElement | null>(null)\n\n function getFocusableElements(): HTMLElement[] {\n if (!containerRef.value) return []\n return Array.from(containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(\n (el) => el.offsetParent !== null, // Element is visible\n )\n }\n\n function getInitialFocusElement(): HTMLElement | null {\n if (!containerRef.value) return null\n\n if (initialFocus) {\n if (typeof initialFocus === 'string') {\n return containerRef.value.querySelector<HTMLElement>(initialFocus)\n }\n return initialFocus\n }\n\n const focusable = getFocusableElements()\n return focusable[0] || null\n }\n\n function handleKeyDown(event: KeyboardEvent): void {\n if (event.key !== 'Tab' || !containerRef.value) return\n\n const focusable = getFocusableElements()\n if (focusable.length === 0) return\n\n const firstFocusable = focusable[0]\n const lastFocusable = focusable[focusable.length - 1]\n const activeElement = document.activeElement as HTMLElement\n\n if (event.shiftKey) {\n // Shift + Tab: move backwards\n if (activeElement === firstFocusable || !containerRef.value.contains(activeElement)) {\n event.preventDefault()\n lastFocusable.focus()\n }\n } else {\n // Tab: move forwards\n if (activeElement === lastFocusable || !containerRef.value.contains(activeElement)) {\n event.preventDefault()\n firstFocusable.focus()\n }\n }\n }\n\n function activate(): void {\n if (isActive.value) return\n\n // Store the currently focused element\n previouslyFocusedElement.value = document.activeElement as HTMLElement\n\n isActive.value = true\n\n // Add event listener\n document.addEventListener('keydown', handleKeyDown)\n\n // Focus initial element after a tick to ensure DOM is ready\n if (focusFirst) {\n requestAnimationFrame(() => {\n const initialElement = getInitialFocusElement()\n if (initialElement) {\n initialElement.focus()\n }\n })\n }\n }\n\n function deactivate(): void {\n if (!isActive.value) return\n\n isActive.value = false\n\n // Remove event listener\n document.removeEventListener('keydown', handleKeyDown)\n\n // Restore focus\n if (restoreFocus && previouslyFocusedElement.value) {\n previouslyFocusedElement.value.focus()\n previouslyFocusedElement.value = null\n }\n }\n\n // Handle reactive active option\n const activeOption = options.active\n if (activeOption !== undefined) {\n if (typeof activeOption === 'boolean') {\n if (activeOption) {\n // Wait for containerRef to be set\n const unwatch = watch(\n containerRef,\n (container) => {\n if (container) {\n activate()\n unwatch()\n }\n },\n { immediate: true },\n )\n }\n } else {\n // It's a Ref<boolean>\n watch(\n [activeOption, containerRef],\n ([active, container]) => {\n if (active && container) {\n activate()\n } else if (!active) {\n deactivate()\n }\n },\n { immediate: true },\n )\n }\n }\n\n // Cleanup on unmount (only when used inside a component)\n if (getCurrentInstance()) {\n onUnmounted(() => {\n deactivate()\n })\n }\n\n return {\n containerRef,\n activate,\n deactivate,\n isActive,\n }\n}\n"],"names":["ref","watch","getCurrentInstance","onUnmounted"],"mappings":"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"}
1
+ {"version":3,"file":"useFocusTrap-kcxO8AeU.cjs","sources":["../src/composables/useFocusTrap.ts"],"sourcesContent":["import { ref, watch, onUnmounted, getCurrentInstance, type Ref } from 'vue'\r\n\r\nconst FOCUSABLE_SELECTORS = [\r\n 'a[href]',\r\n 'button:not([disabled])',\r\n 'input:not([disabled])',\r\n 'select:not([disabled])',\r\n 'textarea:not([disabled])',\r\n '[tabindex]:not([tabindex=\"-1\"])',\r\n '[contenteditable=\"true\"]',\r\n].join(', ')\r\n\r\nexport interface UseFocusTrapOptions {\r\n /** Whether the focus trap is active */\r\n active?: Ref<boolean> | boolean\r\n /** Focus the first focusable element when trap activates */\r\n focusFirst?: boolean\r\n /** Restore focus to the previously focused element when trap deactivates */\r\n restoreFocus?: boolean\r\n /** Initial element to focus (selector or element) */\r\n initialFocus?: string | HTMLElement | null\r\n}\r\n\r\nexport interface UseFocusTrapReturn {\r\n /** Ref to attach to the container element */\r\n containerRef: Ref<HTMLElement | null>\r\n /** Activate the focus trap manually */\r\n activate: () => void\r\n /** Deactivate the focus trap manually */\r\n deactivate: () => void\r\n /** Whether the focus trap is currently active */\r\n isActive: Ref<boolean>\r\n}\r\n\r\n/**\r\n * Composable for trapping focus within a container element (for modals, dialogs, etc.)\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup>\r\n * const isOpen = ref(false)\r\n * const { containerRef } = useFocusTrap({ active: isOpen })\r\n * </script>\r\n *\r\n * <template>\r\n * <div v-if=\"isOpen\" ref=\"containerRef\" role=\"dialog\">\r\n * <button>First focusable</button>\r\n * <input type=\"text\" />\r\n * <button @click=\"isOpen = false\">Close</button>\r\n * </div>\r\n * </template>\r\n * ```\r\n */\r\nexport function useFocusTrap(options: UseFocusTrapOptions = {}): UseFocusTrapReturn {\r\n const { focusFirst = true, restoreFocus = true, initialFocus = null } = options\r\n\r\n const containerRef = ref<HTMLElement | null>(null)\r\n const isActive = ref(false)\r\n const previouslyFocusedElement = ref<HTMLElement | null>(null)\r\n\r\n function getFocusableElements(): HTMLElement[] {\r\n if (!containerRef.value) return []\r\n return Array.from(containerRef.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(\r\n (el) => el.offsetParent !== null, // Element is visible\r\n )\r\n }\r\n\r\n function getInitialFocusElement(): HTMLElement | null {\r\n if (!containerRef.value) return null\r\n\r\n if (initialFocus) {\r\n if (typeof initialFocus === 'string') {\r\n return containerRef.value.querySelector<HTMLElement>(initialFocus)\r\n }\r\n return initialFocus\r\n }\r\n\r\n const focusable = getFocusableElements()\r\n return focusable[0] || null\r\n }\r\n\r\n function handleKeyDown(event: KeyboardEvent): void {\r\n if (event.key !== 'Tab' || !containerRef.value) return\r\n\r\n const focusable = getFocusableElements()\r\n if (focusable.length === 0) return\r\n\r\n const firstFocusable = focusable[0]\r\n const lastFocusable = focusable[focusable.length - 1]\r\n const activeElement = document.activeElement as HTMLElement\r\n\r\n if (event.shiftKey) {\r\n // Shift + Tab: move backwards\r\n if (activeElement === firstFocusable || !containerRef.value.contains(activeElement)) {\r\n event.preventDefault()\r\n lastFocusable.focus()\r\n }\r\n } else {\r\n // Tab: move forwards\r\n if (activeElement === lastFocusable || !containerRef.value.contains(activeElement)) {\r\n event.preventDefault()\r\n firstFocusable.focus()\r\n }\r\n }\r\n }\r\n\r\n function activate(): void {\r\n if (isActive.value) return\r\n\r\n // Store the currently focused element\r\n previouslyFocusedElement.value = document.activeElement as HTMLElement\r\n\r\n isActive.value = true\r\n\r\n // Add event listener\r\n document.addEventListener('keydown', handleKeyDown)\r\n\r\n // Focus initial element after a tick to ensure DOM is ready\r\n if (focusFirst) {\r\n requestAnimationFrame(() => {\r\n const initialElement = getInitialFocusElement()\r\n if (initialElement) {\r\n initialElement.focus()\r\n }\r\n })\r\n }\r\n }\r\n\r\n function deactivate(): void {\r\n if (!isActive.value) return\r\n\r\n isActive.value = false\r\n\r\n // Remove event listener\r\n document.removeEventListener('keydown', handleKeyDown)\r\n\r\n // Restore focus\r\n if (restoreFocus && previouslyFocusedElement.value) {\r\n previouslyFocusedElement.value.focus()\r\n previouslyFocusedElement.value = null\r\n }\r\n }\r\n\r\n // Handle reactive active option\r\n const activeOption = options.active\r\n if (activeOption !== undefined) {\r\n if (typeof activeOption === 'boolean') {\r\n if (activeOption) {\r\n // Wait for containerRef to be set\r\n const unwatch = watch(\r\n containerRef,\r\n (container) => {\r\n if (container) {\r\n activate()\r\n unwatch()\r\n }\r\n },\r\n { immediate: true },\r\n )\r\n }\r\n } else {\r\n // It's a Ref<boolean>\r\n watch(\r\n [activeOption, containerRef],\r\n ([active, container]) => {\r\n if (active && container) {\r\n activate()\r\n } else if (!active) {\r\n deactivate()\r\n }\r\n },\r\n { immediate: true },\r\n )\r\n }\r\n }\r\n\r\n // Cleanup on unmount (only when used inside a component)\r\n if (getCurrentInstance()) {\r\n onUnmounted(() => {\r\n deactivate()\r\n })\r\n }\r\n\r\n return {\r\n containerRef,\r\n activate,\r\n deactivate,\r\n isActive,\r\n }\r\n}\r\n"],"names":["ref","watch","getCurrentInstance","onUnmounted"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useId-nxrBaIC9.cjs","sources":["../src/composables/useId.ts"],"sourcesContent":["import { ref, type Ref } from 'vue'\n\nlet idCounter = 0\n\n/**\n * Generates a unique ID for accessibility relationships (aria-labelledby, aria-describedby, etc.)\n * Pattern: cisse-{prefix}-{counter}\n */\nexport function generateId(prefix = 'id'): string {\n idCounter++\n return `cisse-${prefix}-${idCounter}`\n}\n\n/**\n * Resets the ID counter (useful for testing)\n */\nexport function resetIdCounter(): void {\n idCounter = 0\n}\n\nexport interface UseIdOptions {\n /** Prefix for the generated ID */\n prefix?: string\n /** Custom ID to use instead of generating one */\n id?: string\n}\n\nexport interface UseIdReturn {\n /** The unique ID (reactive) */\n id: Ref<string>\n /** Generate a related ID with a suffix (e.g., id-label, id-description) */\n related: (suffix: string) => string\n}\n\n/**\n * Composable for generating unique IDs for accessibility relationships\n *\n * @example\n * ```vue\n * <script setup>\n * const { id, related } = useId({ prefix: 'modal' })\n * // id.value = 'cisse-modal-1'\n * // related('title') = 'cisse-modal-1-title'\n * // related('description') = 'cisse-modal-1-description'\n * </script>\n *\n * <template>\n * <div :id=\"id\" role=\"dialog\" :aria-labelledby=\"related('title')\">\n * <h2 :id=\"related('title')\">Modal Title</h2>\n * </div>\n * </template>\n * ```\n */\nexport function useId(options: UseIdOptions = {}): UseIdReturn {\n const { prefix = 'id', id: customId } = options\n\n const id = ref(customId || generateId(prefix))\n\n const related = (suffix: string): string => {\n return `${id.value}-${suffix}`\n }\n\n return {\n id,\n related,\n }\n}\n"],"names":["ref"],"mappings":";;AAEA,IAAI,YAAY;AAMT,SAAS,WAAW,SAAS,MAAc;AAChD;AACA,SAAO,SAAS,MAAM,IAAI,SAAS;AACrC;AAKO,SAAS,iBAAuB;AACrC,cAAY;AACd;AAmCO,SAAS,MAAM,UAAwB,IAAiB;AAC7D,QAAM,EAAE,SAAS,MAAM,IAAI,aAAa;AAExC,QAAM,KAAKA,IAAAA,IAAI,YAAY,WAAW,MAAM,CAAC;AAE7C,QAAM,UAAU,CAAC,WAA2B;AAC1C,WAAO,GAAG,GAAG,KAAK,IAAI,MAAM;AAAA,EAC9B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"useId-nxrBaIC9.cjs","sources":["../src/composables/useId.ts"],"sourcesContent":["import { ref, type Ref } from 'vue'\r\n\r\nlet idCounter = 0\r\n\r\n/**\r\n * Generates a unique ID for accessibility relationships (aria-labelledby, aria-describedby, etc.)\r\n * Pattern: cisse-{prefix}-{counter}\r\n */\r\nexport function generateId(prefix = 'id'): string {\r\n idCounter++\r\n return `cisse-${prefix}-${idCounter}`\r\n}\r\n\r\n/**\r\n * Resets the ID counter (useful for testing)\r\n */\r\nexport function resetIdCounter(): void {\r\n idCounter = 0\r\n}\r\n\r\nexport interface UseIdOptions {\r\n /** Prefix for the generated ID */\r\n prefix?: string\r\n /** Custom ID to use instead of generating one */\r\n id?: string\r\n}\r\n\r\nexport interface UseIdReturn {\r\n /** The unique ID (reactive) */\r\n id: Ref<string>\r\n /** Generate a related ID with a suffix (e.g., id-label, id-description) */\r\n related: (suffix: string) => string\r\n}\r\n\r\n/**\r\n * Composable for generating unique IDs for accessibility relationships\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup>\r\n * const { id, related } = useId({ prefix: 'modal' })\r\n * // id.value = 'cisse-modal-1'\r\n * // related('title') = 'cisse-modal-1-title'\r\n * // related('description') = 'cisse-modal-1-description'\r\n * </script>\r\n *\r\n * <template>\r\n * <div :id=\"id\" role=\"dialog\" :aria-labelledby=\"related('title')\">\r\n * <h2 :id=\"related('title')\">Modal Title</h2>\r\n * </div>\r\n * </template>\r\n * ```\r\n */\r\nexport function useId(options: UseIdOptions = {}): UseIdReturn {\r\n const { prefix = 'id', id: customId } = options\r\n\r\n const id = ref(customId || generateId(prefix))\r\n\r\n const related = (suffix: string): string => {\r\n return `${id.value}-${suffix}`\r\n }\r\n\r\n return {\r\n id,\r\n related,\r\n }\r\n}\r\n"],"names":["ref"],"mappings":";;AAEA,IAAI,YAAY;AAMT,SAAS,WAAW,SAAS,MAAc;AAChD;AACA,SAAO,SAAS,MAAM,IAAI,SAAS;AACrC;AAKO,SAAS,iBAAuB;AACrC,cAAY;AACd;AAmCO,SAAS,MAAM,UAAwB,IAAiB;AAC7D,QAAM,EAAE,SAAS,MAAM,IAAI,aAAa;AAExC,QAAM,KAAKA,IAAAA,IAAI,YAAY,WAAW,MAAM,CAAC;AAE7C,QAAM,UAAU,CAAC,WAA2B;AAC1C,WAAO,GAAG,GAAG,KAAK,IAAI,MAAM;AAAA,EAC9B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EAAA;AAEJ;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useId-xeHj7rkg.js","sources":["../src/composables/useId.ts"],"sourcesContent":["import { ref, type Ref } from 'vue'\n\nlet idCounter = 0\n\n/**\n * Generates a unique ID for accessibility relationships (aria-labelledby, aria-describedby, etc.)\n * Pattern: cisse-{prefix}-{counter}\n */\nexport function generateId(prefix = 'id'): string {\n idCounter++\n return `cisse-${prefix}-${idCounter}`\n}\n\n/**\n * Resets the ID counter (useful for testing)\n */\nexport function resetIdCounter(): void {\n idCounter = 0\n}\n\nexport interface UseIdOptions {\n /** Prefix for the generated ID */\n prefix?: string\n /** Custom ID to use instead of generating one */\n id?: string\n}\n\nexport interface UseIdReturn {\n /** The unique ID (reactive) */\n id: Ref<string>\n /** Generate a related ID with a suffix (e.g., id-label, id-description) */\n related: (suffix: string) => string\n}\n\n/**\n * Composable for generating unique IDs for accessibility relationships\n *\n * @example\n * ```vue\n * <script setup>\n * const { id, related } = useId({ prefix: 'modal' })\n * // id.value = 'cisse-modal-1'\n * // related('title') = 'cisse-modal-1-title'\n * // related('description') = 'cisse-modal-1-description'\n * </script>\n *\n * <template>\n * <div :id=\"id\" role=\"dialog\" :aria-labelledby=\"related('title')\">\n * <h2 :id=\"related('title')\">Modal Title</h2>\n * </div>\n * </template>\n * ```\n */\nexport function useId(options: UseIdOptions = {}): UseIdReturn {\n const { prefix = 'id', id: customId } = options\n\n const id = ref(customId || generateId(prefix))\n\n const related = (suffix: string): string => {\n return `${id.value}-${suffix}`\n }\n\n return {\n id,\n related,\n }\n}\n"],"names":[],"mappings":";AAEA,IAAI,YAAY;AAMT,SAAS,WAAW,SAAS,MAAc;AAChD;AACA,SAAO,SAAS,MAAM,IAAI,SAAS;AACrC;AAKO,SAAS,iBAAuB;AACrC,cAAY;AACd;AAmCO,SAAS,MAAM,UAAwB,IAAiB;AAC7D,QAAM,EAAE,SAAS,MAAM,IAAI,aAAa;AAExC,QAAM,KAAK,IAAI,YAAY,WAAW,MAAM,CAAC;AAE7C,QAAM,UAAU,CAAC,WAA2B;AAC1C,WAAO,GAAG,GAAG,KAAK,IAAI,MAAM;AAAA,EAC9B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"useId-xeHj7rkg.js","sources":["../src/composables/useId.ts"],"sourcesContent":["import { ref, type Ref } from 'vue'\r\n\r\nlet idCounter = 0\r\n\r\n/**\r\n * Generates a unique ID for accessibility relationships (aria-labelledby, aria-describedby, etc.)\r\n * Pattern: cisse-{prefix}-{counter}\r\n */\r\nexport function generateId(prefix = 'id'): string {\r\n idCounter++\r\n return `cisse-${prefix}-${idCounter}`\r\n}\r\n\r\n/**\r\n * Resets the ID counter (useful for testing)\r\n */\r\nexport function resetIdCounter(): void {\r\n idCounter = 0\r\n}\r\n\r\nexport interface UseIdOptions {\r\n /** Prefix for the generated ID */\r\n prefix?: string\r\n /** Custom ID to use instead of generating one */\r\n id?: string\r\n}\r\n\r\nexport interface UseIdReturn {\r\n /** The unique ID (reactive) */\r\n id: Ref<string>\r\n /** Generate a related ID with a suffix (e.g., id-label, id-description) */\r\n related: (suffix: string) => string\r\n}\r\n\r\n/**\r\n * Composable for generating unique IDs for accessibility relationships\r\n *\r\n * @example\r\n * ```vue\r\n * <script setup>\r\n * const { id, related } = useId({ prefix: 'modal' })\r\n * // id.value = 'cisse-modal-1'\r\n * // related('title') = 'cisse-modal-1-title'\r\n * // related('description') = 'cisse-modal-1-description'\r\n * </script>\r\n *\r\n * <template>\r\n * <div :id=\"id\" role=\"dialog\" :aria-labelledby=\"related('title')\">\r\n * <h2 :id=\"related('title')\">Modal Title</h2>\r\n * </div>\r\n * </template>\r\n * ```\r\n */\r\nexport function useId(options: UseIdOptions = {}): UseIdReturn {\r\n const { prefix = 'id', id: customId } = options\r\n\r\n const id = ref(customId || generateId(prefix))\r\n\r\n const related = (suffix: string): string => {\r\n return `${id.value}-${suffix}`\r\n }\r\n\r\n return {\r\n id,\r\n related,\r\n }\r\n}\r\n"],"names":[],"mappings":";AAEA,IAAI,YAAY;AAMT,SAAS,WAAW,SAAS,MAAc;AAChD;AACA,SAAO,SAAS,MAAM,IAAI,SAAS;AACrC;AAKO,SAAS,iBAAuB;AACrC,cAAY;AACd;AAmCO,SAAS,MAAM,UAAwB,IAAiB;AAC7D,QAAM,EAAE,SAAS,MAAM,IAAI,aAAa;AAExC,QAAM,KAAK,IAAI,YAAY,WAAW,MAAM,CAAC;AAE7C,QAAM,UAAU,CAAC,WAA2B;AAC1C,WAAO,GAAG,GAAG,KAAK,IAAI,MAAM;AAAA,EAC9B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useToast-Bk60GArg.cjs","sources":["../node_modules/uid/dist/index.mjs","../src/composables/useNotifications.ts","../src/composables/useExportCSV.ts","../src/composables/useModal.ts","../src/composables/useToast.ts"],"sourcesContent":["var IDX=256, HEX=[], SIZE=256, BUFFER;\nwhile (IDX--) HEX[IDX] = (IDX + 256).toString(16).substring(1);\n\nexport function uid(len) {\n\tvar i=0, tmp=(len || 11);\n\tif (!BUFFER || ((IDX + tmp) > SIZE*2)) {\n\t\tfor (BUFFER='',IDX=0; i < SIZE; i++) {\n\t\t\tBUFFER += HEX[Math.random() * 256 | 0];\n\t\t}\n\t}\n\n\treturn BUFFER.substring(IDX, IDX++ + tmp);\n}\n","import { ref, readonly } from 'vue'\nimport { uid } from 'uid'\nimport type { Notification, NotificationType, NotificationOptions } from '@/types'\n\n// Global state (singleton pattern) - shared across all useNotifications() calls\nconst notifications = ref<Notification[]>([])\n\n/**\n * Composable for managing notifications/toasts\n * Uses singleton pattern - all components share the same notifications state\n */\nexport function useNotifications() {\n\n const notify = (\n type: NotificationType,\n message: string,\n options: NotificationOptions = {},\n ): string => {\n const id = uid()\n const notification: Notification = {\n id,\n type,\n message,\n title: options.title ?? null,\n duration: options.duration ?? 5000,\n }\n\n notifications.value.push(notification)\n\n if (notification.duration && notification.duration > 0) {\n setTimeout(() => remove(id), notification.duration)\n }\n\n return id\n }\n\n const success = (message: string, options?: NotificationOptions) =>\n notify('success', message, options)\n\n const warning = (message: string, options?: NotificationOptions) =>\n notify('warning', message, options)\n\n const error = (message: string, options?: NotificationOptions) =>\n notify('error', message, options)\n\n const info = (message: string, options?: NotificationOptions) =>\n notify('info', message, options)\n\n const remove = (id: string) => {\n notifications.value = notifications.value.filter((n) => n.id !== id)\n }\n\n const clear = () => {\n notifications.value = []\n }\n\n return {\n notifications: readonly(notifications),\n notify,\n success,\n warning,\n error,\n info,\n remove,\n clear,\n }\n}\n","/**\n * Composable for exporting data to CSV format\n */\nexport function useExportCSV() {\n const escapeCSV = (value: unknown): string => {\n if (value === null || value === undefined) return ''\n const str = String(value)\n if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`\n }\n return str\n }\n\n const exportToCSV = <T extends Record<string, unknown>>(\n data: T[],\n columns: { key: keyof T; label: string }[],\n filename: string = 'export.csv',\n ) => {\n if (!data || data.length === 0) {\n console.warn('No data to export')\n return\n }\n\n // Create header row\n const headers = columns.map((col) => escapeCSV(col.label)).join(',')\n\n // Create data rows\n const rows = data.map((item) =>\n columns.map((col) => escapeCSV(item[col.key])).join(','),\n )\n\n // Combine header and rows\n const csv = [headers, ...rows].join('\\n')\n\n // Create and trigger download\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.setAttribute('href', url)\n link.setAttribute('download', filename)\n link.style.visibility = 'hidden'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n URL.revokeObjectURL(url)\n }\n\n return {\n exportToCSV,\n escapeCSV,\n }\n}\n","import { ref, type Ref } from 'vue'\n\nexport interface UseModalReturn<T = unknown> {\n /** Whether the modal is currently open */\n isOpen: Ref<boolean>\n /** Data associated with the modal (e.g., item being edited) */\n data: Ref<T | null>\n /** Open the modal, optionally with data */\n open: (newData?: T) => void\n /** Close the modal and clear data */\n close: () => void\n /** Toggle the modal state */\n toggle: () => void\n}\n\n/**\n * Composable for managing modal state\n *\n * @example\n * ```ts\n * // Simple modal\n * const createModal = useModal()\n * createModal.open()\n * createModal.close()\n *\n * // Modal with data (e.g., for editing)\n * const editModal = useModal<User>()\n * editModal.open(selectedUser)\n * // Access editModal.data.value in modal\n *\n * // With onClose callback\n * const deleteModal = useModal<Item>({ onClose: () => refetch() })\n * ```\n */\nexport function useModal<T = unknown>(options?: {\n /** Initial open state */\n initialOpen?: boolean\n /** Initial data */\n initialData?: T | null\n /** Callback when modal opens */\n onOpen?: (data: T | null) => void\n /** Callback when modal closes */\n onClose?: () => void\n}): UseModalReturn<T> {\n const {\n initialOpen = false,\n initialData = null,\n onOpen,\n onClose,\n } = options ?? {}\n\n const isOpen = ref(initialOpen)\n const data = ref<T | null>(initialData) as Ref<T | null>\n\n const open = (newData?: T) => {\n data.value = newData ?? null\n isOpen.value = true\n onOpen?.(data.value)\n }\n\n const close = () => {\n isOpen.value = false\n data.value = null\n onClose?.()\n }\n\n const toggle = () => {\n if (isOpen.value) {\n close()\n } else {\n open()\n }\n }\n\n return {\n isOpen,\n data,\n open,\n close,\n toggle,\n }\n}\n\n/**\n * Create multiple related modals at once\n * Useful when a page has several modals (create, edit, delete, etc.)\n *\n * @example\n * ```ts\n * const modals = useModals({\n * create: useModal(),\n * edit: useModal<User>(),\n * delete: useModal<User>(),\n * })\n *\n * modals.create.open()\n * modals.edit.open(user)\n * modals.delete.close()\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useModals<T extends Record<string, UseModalReturn<any>>>(\n modals: T\n): T {\n return modals\n}\n","import { ref } from 'vue'\nimport type { ToastType } from '@/components/feedback/Toast.vue'\nimport type { ToastItem } from '@/components/feedback/ToastContainer.vue'\n\nexport interface ToastOptions {\n message: string\n type?: ToastType\n title?: string\n duration?: number\n}\n\nconst toasts = ref<ToastItem[]>([])\n\nlet toastId = 0\n\nexport function useToast() {\n const add = (options: ToastOptions): string => {\n const id = `toast-${++toastId}`\n const toast: ToastItem = {\n id,\n message: options.message,\n type: options.type || 'info',\n title: options.title,\n duration: options.duration ?? 5000,\n }\n toasts.value.push(toast)\n return id\n }\n\n const remove = (id: string) => {\n const index = toasts.value.findIndex((t) => t.id === id)\n if (index > -1) {\n toasts.value.splice(index, 1)\n }\n }\n\n const clear = () => {\n toasts.value = []\n }\n\n const success = (message: string, title?: string) => {\n return add({ message, title, type: 'success' })\n }\n\n const error = (message: string, title?: string) => {\n return add({ message, title, type: 'error' })\n }\n\n const warning = (message: string, title?: string) => {\n return add({ message, title, type: 'warning' })\n }\n\n const info = (message: string, title?: string) => {\n return add({ message, title, type: 'info' })\n }\n\n return {\n toasts,\n add,\n remove,\n clear,\n success,\n error,\n warning,\n info,\n }\n}\n"],"names":["ref","readonly"],"mappings":"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","x_google_ignoreList":[0]}
1
+ {"version":3,"file":"useToast-Bk60GArg.cjs","sources":["../node_modules/uid/dist/index.mjs","../src/composables/useNotifications.ts","../src/composables/useExportCSV.ts","../src/composables/useModal.ts","../src/composables/useToast.ts"],"sourcesContent":["var IDX=256, HEX=[], SIZE=256, BUFFER;\nwhile (IDX--) HEX[IDX] = (IDX + 256).toString(16).substring(1);\n\nexport function uid(len) {\n\tvar i=0, tmp=(len || 11);\n\tif (!BUFFER || ((IDX + tmp) > SIZE*2)) {\n\t\tfor (BUFFER='',IDX=0; i < SIZE; i++) {\n\t\t\tBUFFER += HEX[Math.random() * 256 | 0];\n\t\t}\n\t}\n\n\treturn BUFFER.substring(IDX, IDX++ + tmp);\n}\n","import { ref, readonly } from 'vue'\r\nimport { uid } from 'uid'\r\nimport type { Notification, NotificationType, NotificationOptions } from '@/types'\r\n\r\n// Global state (singleton pattern) - shared across all useNotifications() calls\r\nconst notifications = ref<Notification[]>([])\r\n\r\n/**\r\n * Composable for managing notifications/toasts\r\n * Uses singleton pattern - all components share the same notifications state\r\n */\r\nexport function useNotifications() {\r\n\r\n const notify = (\r\n type: NotificationType,\r\n message: string,\r\n options: NotificationOptions = {},\r\n ): string => {\r\n const id = uid()\r\n const notification: Notification = {\r\n id,\r\n type,\r\n message,\r\n title: options.title ?? null,\r\n duration: options.duration ?? 5000,\r\n }\r\n\r\n notifications.value.push(notification)\r\n\r\n if (notification.duration && notification.duration > 0) {\r\n setTimeout(() => remove(id), notification.duration)\r\n }\r\n\r\n return id\r\n }\r\n\r\n const success = (message: string, options?: NotificationOptions) =>\r\n notify('success', message, options)\r\n\r\n const warning = (message: string, options?: NotificationOptions) =>\r\n notify('warning', message, options)\r\n\r\n const error = (message: string, options?: NotificationOptions) =>\r\n notify('error', message, options)\r\n\r\n const info = (message: string, options?: NotificationOptions) =>\r\n notify('info', message, options)\r\n\r\n const remove = (id: string) => {\r\n notifications.value = notifications.value.filter((n) => n.id !== id)\r\n }\r\n\r\n const clear = () => {\r\n notifications.value = []\r\n }\r\n\r\n return {\r\n notifications: readonly(notifications),\r\n notify,\r\n success,\r\n warning,\r\n error,\r\n info,\r\n remove,\r\n clear,\r\n }\r\n}\r\n","/**\n * Composable for exporting data to CSV format\n */\nexport function useExportCSV() {\n const escapeCSV = (value: unknown): string => {\n if (value === null || value === undefined) return ''\n const str = String(value)\n if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`\n }\n return str\n }\n\n const exportToCSV = <T extends Record<string, unknown>>(\n data: T[],\n columns: { key: keyof T; label: string }[],\n filename: string = 'export.csv',\n ) => {\n if (!data || data.length === 0) {\n console.warn('No data to export')\n return\n }\n\n // Create header row\n const headers = columns.map((col) => escapeCSV(col.label)).join(',')\n\n // Create data rows\n const rows = data.map((item) =>\n columns.map((col) => escapeCSV(item[col.key])).join(','),\n )\n\n // Combine header and rows\n const csv = [headers, ...rows].join('\\n')\n\n // Create and trigger download\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.setAttribute('href', url)\n link.setAttribute('download', filename)\n link.style.visibility = 'hidden'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n URL.revokeObjectURL(url)\n }\n\n return {\n exportToCSV,\n escapeCSV,\n }\n}\n","import { ref, type Ref } from 'vue'\r\n\r\nexport interface UseModalReturn<T = unknown> {\r\n /** Whether the modal is currently open */\r\n isOpen: Ref<boolean>\r\n /** Data associated with the modal (e.g., item being edited) */\r\n data: Ref<T | null>\r\n /** Open the modal, optionally with data */\r\n open: (newData?: T) => void\r\n /** Close the modal and clear data */\r\n close: () => void\r\n /** Toggle the modal state */\r\n toggle: () => void\r\n}\r\n\r\n/**\r\n * Composable for managing modal state\r\n *\r\n * @example\r\n * ```ts\r\n * // Simple modal\r\n * const createModal = useModal()\r\n * createModal.open()\r\n * createModal.close()\r\n *\r\n * // Modal with data (e.g., for editing)\r\n * const editModal = useModal<User>()\r\n * editModal.open(selectedUser)\r\n * // Access editModal.data.value in modal\r\n *\r\n * // With onClose callback\r\n * const deleteModal = useModal<Item>({ onClose: () => refetch() })\r\n * ```\r\n */\r\nexport function useModal<T = unknown>(options?: {\r\n /** Initial open state */\r\n initialOpen?: boolean\r\n /** Initial data */\r\n initialData?: T | null\r\n /** Callback when modal opens */\r\n onOpen?: (data: T | null) => void\r\n /** Callback when modal closes */\r\n onClose?: () => void\r\n}): UseModalReturn<T> {\r\n const {\r\n initialOpen = false,\r\n initialData = null,\r\n onOpen,\r\n onClose,\r\n } = options ?? {}\r\n\r\n const isOpen = ref(initialOpen)\r\n const data = ref<T | null>(initialData) as Ref<T | null>\r\n\r\n const open = (newData?: T) => {\r\n data.value = newData ?? null\r\n isOpen.value = true\r\n onOpen?.(data.value)\r\n }\r\n\r\n const close = () => {\r\n isOpen.value = false\r\n data.value = null\r\n onClose?.()\r\n }\r\n\r\n const toggle = () => {\r\n if (isOpen.value) {\r\n close()\r\n } else {\r\n open()\r\n }\r\n }\r\n\r\n return {\r\n isOpen,\r\n data,\r\n open,\r\n close,\r\n toggle,\r\n }\r\n}\r\n\r\n/**\r\n * Create multiple related modals at once\r\n * Useful when a page has several modals (create, edit, delete, etc.)\r\n *\r\n * @example\r\n * ```ts\r\n * const modals = useModals({\r\n * create: useModal(),\r\n * edit: useModal<User>(),\r\n * delete: useModal<User>(),\r\n * })\r\n *\r\n * modals.create.open()\r\n * modals.edit.open(user)\r\n * modals.delete.close()\r\n * ```\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport function useModals<T extends Record<string, UseModalReturn<any>>>(\r\n modals: T\r\n): T {\r\n return modals\r\n}\r\n","import { ref } from 'vue'\r\nimport type { ToastType } from '@/components/feedback/Toast.vue'\r\nimport type { ToastItem } from '@/components/feedback/ToastContainer.vue'\r\n\r\nexport interface ToastOptions {\r\n message: string\r\n type?: ToastType\r\n title?: string\r\n duration?: number\r\n}\r\n\r\nconst toasts = ref<ToastItem[]>([])\r\n\r\nlet toastId = 0\r\n\r\nexport function useToast() {\r\n const add = (options: ToastOptions): string => {\r\n const id = `toast-${++toastId}`\r\n const toast: ToastItem = {\r\n id,\r\n message: options.message,\r\n type: options.type || 'info',\r\n title: options.title,\r\n duration: options.duration ?? 5000,\r\n }\r\n toasts.value.push(toast)\r\n return id\r\n }\r\n\r\n const remove = (id: string) => {\r\n const index = toasts.value.findIndex((t) => t.id === id)\r\n if (index > -1) {\r\n toasts.value.splice(index, 1)\r\n }\r\n }\r\n\r\n const clear = () => {\r\n toasts.value = []\r\n }\r\n\r\n const success = (message: string, title?: string) => {\r\n return add({ message, title, type: 'success' })\r\n }\r\n\r\n const error = (message: string, title?: string) => {\r\n return add({ message, title, type: 'error' })\r\n }\r\n\r\n const warning = (message: string, title?: string) => {\r\n return add({ message, title, type: 'warning' })\r\n }\r\n\r\n const info = (message: string, title?: string) => {\r\n return add({ message, title, type: 'info' })\r\n }\r\n\r\n return {\r\n toasts,\r\n add,\r\n remove,\r\n clear,\r\n success,\r\n error,\r\n warning,\r\n info,\r\n }\r\n}\r\n"],"names":["ref","readonly"],"mappings":"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","x_google_ignoreList":[0]}
@@ -1 +1 @@
1
- {"version":3,"file":"useToast-ina5g3mj.js","sources":["../node_modules/uid/dist/index.mjs","../src/composables/useNotifications.ts","../src/composables/useExportCSV.ts","../src/composables/useModal.ts","../src/composables/useToast.ts"],"sourcesContent":["var IDX=256, HEX=[], SIZE=256, BUFFER;\nwhile (IDX--) HEX[IDX] = (IDX + 256).toString(16).substring(1);\n\nexport function uid(len) {\n\tvar i=0, tmp=(len || 11);\n\tif (!BUFFER || ((IDX + tmp) > SIZE*2)) {\n\t\tfor (BUFFER='',IDX=0; i < SIZE; i++) {\n\t\t\tBUFFER += HEX[Math.random() * 256 | 0];\n\t\t}\n\t}\n\n\treturn BUFFER.substring(IDX, IDX++ + tmp);\n}\n","import { ref, readonly } from 'vue'\nimport { uid } from 'uid'\nimport type { Notification, NotificationType, NotificationOptions } from '@/types'\n\n// Global state (singleton pattern) - shared across all useNotifications() calls\nconst notifications = ref<Notification[]>([])\n\n/**\n * Composable for managing notifications/toasts\n * Uses singleton pattern - all components share the same notifications state\n */\nexport function useNotifications() {\n\n const notify = (\n type: NotificationType,\n message: string,\n options: NotificationOptions = {},\n ): string => {\n const id = uid()\n const notification: Notification = {\n id,\n type,\n message,\n title: options.title ?? null,\n duration: options.duration ?? 5000,\n }\n\n notifications.value.push(notification)\n\n if (notification.duration && notification.duration > 0) {\n setTimeout(() => remove(id), notification.duration)\n }\n\n return id\n }\n\n const success = (message: string, options?: NotificationOptions) =>\n notify('success', message, options)\n\n const warning = (message: string, options?: NotificationOptions) =>\n notify('warning', message, options)\n\n const error = (message: string, options?: NotificationOptions) =>\n notify('error', message, options)\n\n const info = (message: string, options?: NotificationOptions) =>\n notify('info', message, options)\n\n const remove = (id: string) => {\n notifications.value = notifications.value.filter((n) => n.id !== id)\n }\n\n const clear = () => {\n notifications.value = []\n }\n\n return {\n notifications: readonly(notifications),\n notify,\n success,\n warning,\n error,\n info,\n remove,\n clear,\n }\n}\n","/**\n * Composable for exporting data to CSV format\n */\nexport function useExportCSV() {\n const escapeCSV = (value: unknown): string => {\n if (value === null || value === undefined) return ''\n const str = String(value)\n if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`\n }\n return str\n }\n\n const exportToCSV = <T extends Record<string, unknown>>(\n data: T[],\n columns: { key: keyof T; label: string }[],\n filename: string = 'export.csv',\n ) => {\n if (!data || data.length === 0) {\n console.warn('No data to export')\n return\n }\n\n // Create header row\n const headers = columns.map((col) => escapeCSV(col.label)).join(',')\n\n // Create data rows\n const rows = data.map((item) =>\n columns.map((col) => escapeCSV(item[col.key])).join(','),\n )\n\n // Combine header and rows\n const csv = [headers, ...rows].join('\\n')\n\n // Create and trigger download\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.setAttribute('href', url)\n link.setAttribute('download', filename)\n link.style.visibility = 'hidden'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n URL.revokeObjectURL(url)\n }\n\n return {\n exportToCSV,\n escapeCSV,\n }\n}\n","import { ref, type Ref } from 'vue'\n\nexport interface UseModalReturn<T = unknown> {\n /** Whether the modal is currently open */\n isOpen: Ref<boolean>\n /** Data associated with the modal (e.g., item being edited) */\n data: Ref<T | null>\n /** Open the modal, optionally with data */\n open: (newData?: T) => void\n /** Close the modal and clear data */\n close: () => void\n /** Toggle the modal state */\n toggle: () => void\n}\n\n/**\n * Composable for managing modal state\n *\n * @example\n * ```ts\n * // Simple modal\n * const createModal = useModal()\n * createModal.open()\n * createModal.close()\n *\n * // Modal with data (e.g., for editing)\n * const editModal = useModal<User>()\n * editModal.open(selectedUser)\n * // Access editModal.data.value in modal\n *\n * // With onClose callback\n * const deleteModal = useModal<Item>({ onClose: () => refetch() })\n * ```\n */\nexport function useModal<T = unknown>(options?: {\n /** Initial open state */\n initialOpen?: boolean\n /** Initial data */\n initialData?: T | null\n /** Callback when modal opens */\n onOpen?: (data: T | null) => void\n /** Callback when modal closes */\n onClose?: () => void\n}): UseModalReturn<T> {\n const {\n initialOpen = false,\n initialData = null,\n onOpen,\n onClose,\n } = options ?? {}\n\n const isOpen = ref(initialOpen)\n const data = ref<T | null>(initialData) as Ref<T | null>\n\n const open = (newData?: T) => {\n data.value = newData ?? null\n isOpen.value = true\n onOpen?.(data.value)\n }\n\n const close = () => {\n isOpen.value = false\n data.value = null\n onClose?.()\n }\n\n const toggle = () => {\n if (isOpen.value) {\n close()\n } else {\n open()\n }\n }\n\n return {\n isOpen,\n data,\n open,\n close,\n toggle,\n }\n}\n\n/**\n * Create multiple related modals at once\n * Useful when a page has several modals (create, edit, delete, etc.)\n *\n * @example\n * ```ts\n * const modals = useModals({\n * create: useModal(),\n * edit: useModal<User>(),\n * delete: useModal<User>(),\n * })\n *\n * modals.create.open()\n * modals.edit.open(user)\n * modals.delete.close()\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function useModals<T extends Record<string, UseModalReturn<any>>>(\n modals: T\n): T {\n return modals\n}\n","import { ref } from 'vue'\nimport type { ToastType } from '@/components/feedback/Toast.vue'\nimport type { ToastItem } from '@/components/feedback/ToastContainer.vue'\n\nexport interface ToastOptions {\n message: string\n type?: ToastType\n title?: string\n duration?: number\n}\n\nconst toasts = ref<ToastItem[]>([])\n\nlet toastId = 0\n\nexport function useToast() {\n const add = (options: ToastOptions): string => {\n const id = `toast-${++toastId}`\n const toast: ToastItem = {\n id,\n message: options.message,\n type: options.type || 'info',\n title: options.title,\n duration: options.duration ?? 5000,\n }\n toasts.value.push(toast)\n return id\n }\n\n const remove = (id: string) => {\n const index = toasts.value.findIndex((t) => t.id === id)\n if (index > -1) {\n toasts.value.splice(index, 1)\n }\n }\n\n const clear = () => {\n toasts.value = []\n }\n\n const success = (message: string, title?: string) => {\n return add({ message, title, type: 'success' })\n }\n\n const error = (message: string, title?: string) => {\n return add({ message, title, type: 'error' })\n }\n\n const warning = (message: string, title?: string) => {\n return add({ message, title, type: 'warning' })\n }\n\n const info = (message: string, title?: string) => {\n return add({ message, title, type: 'info' })\n }\n\n return {\n toasts,\n add,\n remove,\n clear,\n success,\n error,\n warning,\n info,\n }\n}\n"],"names":[],"mappings":"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","x_google_ignoreList":[0]}
1
+ {"version":3,"file":"useToast-ina5g3mj.js","sources":["../node_modules/uid/dist/index.mjs","../src/composables/useNotifications.ts","../src/composables/useExportCSV.ts","../src/composables/useModal.ts","../src/composables/useToast.ts"],"sourcesContent":["var IDX=256, HEX=[], SIZE=256, BUFFER;\nwhile (IDX--) HEX[IDX] = (IDX + 256).toString(16).substring(1);\n\nexport function uid(len) {\n\tvar i=0, tmp=(len || 11);\n\tif (!BUFFER || ((IDX + tmp) > SIZE*2)) {\n\t\tfor (BUFFER='',IDX=0; i < SIZE; i++) {\n\t\t\tBUFFER += HEX[Math.random() * 256 | 0];\n\t\t}\n\t}\n\n\treturn BUFFER.substring(IDX, IDX++ + tmp);\n}\n","import { ref, readonly } from 'vue'\r\nimport { uid } from 'uid'\r\nimport type { Notification, NotificationType, NotificationOptions } from '@/types'\r\n\r\n// Global state (singleton pattern) - shared across all useNotifications() calls\r\nconst notifications = ref<Notification[]>([])\r\n\r\n/**\r\n * Composable for managing notifications/toasts\r\n * Uses singleton pattern - all components share the same notifications state\r\n */\r\nexport function useNotifications() {\r\n\r\n const notify = (\r\n type: NotificationType,\r\n message: string,\r\n options: NotificationOptions = {},\r\n ): string => {\r\n const id = uid()\r\n const notification: Notification = {\r\n id,\r\n type,\r\n message,\r\n title: options.title ?? null,\r\n duration: options.duration ?? 5000,\r\n }\r\n\r\n notifications.value.push(notification)\r\n\r\n if (notification.duration && notification.duration > 0) {\r\n setTimeout(() => remove(id), notification.duration)\r\n }\r\n\r\n return id\r\n }\r\n\r\n const success = (message: string, options?: NotificationOptions) =>\r\n notify('success', message, options)\r\n\r\n const warning = (message: string, options?: NotificationOptions) =>\r\n notify('warning', message, options)\r\n\r\n const error = (message: string, options?: NotificationOptions) =>\r\n notify('error', message, options)\r\n\r\n const info = (message: string, options?: NotificationOptions) =>\r\n notify('info', message, options)\r\n\r\n const remove = (id: string) => {\r\n notifications.value = notifications.value.filter((n) => n.id !== id)\r\n }\r\n\r\n const clear = () => {\r\n notifications.value = []\r\n }\r\n\r\n return {\r\n notifications: readonly(notifications),\r\n notify,\r\n success,\r\n warning,\r\n error,\r\n info,\r\n remove,\r\n clear,\r\n }\r\n}\r\n","/**\n * Composable for exporting data to CSV format\n */\nexport function useExportCSV() {\n const escapeCSV = (value: unknown): string => {\n if (value === null || value === undefined) return ''\n const str = String(value)\n if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`\n }\n return str\n }\n\n const exportToCSV = <T extends Record<string, unknown>>(\n data: T[],\n columns: { key: keyof T; label: string }[],\n filename: string = 'export.csv',\n ) => {\n if (!data || data.length === 0) {\n console.warn('No data to export')\n return\n }\n\n // Create header row\n const headers = columns.map((col) => escapeCSV(col.label)).join(',')\n\n // Create data rows\n const rows = data.map((item) =>\n columns.map((col) => escapeCSV(item[col.key])).join(','),\n )\n\n // Combine header and rows\n const csv = [headers, ...rows].join('\\n')\n\n // Create and trigger download\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.setAttribute('href', url)\n link.setAttribute('download', filename)\n link.style.visibility = 'hidden'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n URL.revokeObjectURL(url)\n }\n\n return {\n exportToCSV,\n escapeCSV,\n }\n}\n","import { ref, type Ref } from 'vue'\r\n\r\nexport interface UseModalReturn<T = unknown> {\r\n /** Whether the modal is currently open */\r\n isOpen: Ref<boolean>\r\n /** Data associated with the modal (e.g., item being edited) */\r\n data: Ref<T | null>\r\n /** Open the modal, optionally with data */\r\n open: (newData?: T) => void\r\n /** Close the modal and clear data */\r\n close: () => void\r\n /** Toggle the modal state */\r\n toggle: () => void\r\n}\r\n\r\n/**\r\n * Composable for managing modal state\r\n *\r\n * @example\r\n * ```ts\r\n * // Simple modal\r\n * const createModal = useModal()\r\n * createModal.open()\r\n * createModal.close()\r\n *\r\n * // Modal with data (e.g., for editing)\r\n * const editModal = useModal<User>()\r\n * editModal.open(selectedUser)\r\n * // Access editModal.data.value in modal\r\n *\r\n * // With onClose callback\r\n * const deleteModal = useModal<Item>({ onClose: () => refetch() })\r\n * ```\r\n */\r\nexport function useModal<T = unknown>(options?: {\r\n /** Initial open state */\r\n initialOpen?: boolean\r\n /** Initial data */\r\n initialData?: T | null\r\n /** Callback when modal opens */\r\n onOpen?: (data: T | null) => void\r\n /** Callback when modal closes */\r\n onClose?: () => void\r\n}): UseModalReturn<T> {\r\n const {\r\n initialOpen = false,\r\n initialData = null,\r\n onOpen,\r\n onClose,\r\n } = options ?? {}\r\n\r\n const isOpen = ref(initialOpen)\r\n const data = ref<T | null>(initialData) as Ref<T | null>\r\n\r\n const open = (newData?: T) => {\r\n data.value = newData ?? null\r\n isOpen.value = true\r\n onOpen?.(data.value)\r\n }\r\n\r\n const close = () => {\r\n isOpen.value = false\r\n data.value = null\r\n onClose?.()\r\n }\r\n\r\n const toggle = () => {\r\n if (isOpen.value) {\r\n close()\r\n } else {\r\n open()\r\n }\r\n }\r\n\r\n return {\r\n isOpen,\r\n data,\r\n open,\r\n close,\r\n toggle,\r\n }\r\n}\r\n\r\n/**\r\n * Create multiple related modals at once\r\n * Useful when a page has several modals (create, edit, delete, etc.)\r\n *\r\n * @example\r\n * ```ts\r\n * const modals = useModals({\r\n * create: useModal(),\r\n * edit: useModal<User>(),\r\n * delete: useModal<User>(),\r\n * })\r\n *\r\n * modals.create.open()\r\n * modals.edit.open(user)\r\n * modals.delete.close()\r\n * ```\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport function useModals<T extends Record<string, UseModalReturn<any>>>(\r\n modals: T\r\n): T {\r\n return modals\r\n}\r\n","import { ref } from 'vue'\r\nimport type { ToastType } from '@/components/feedback/Toast.vue'\r\nimport type { ToastItem } from '@/components/feedback/ToastContainer.vue'\r\n\r\nexport interface ToastOptions {\r\n message: string\r\n type?: ToastType\r\n title?: string\r\n duration?: number\r\n}\r\n\r\nconst toasts = ref<ToastItem[]>([])\r\n\r\nlet toastId = 0\r\n\r\nexport function useToast() {\r\n const add = (options: ToastOptions): string => {\r\n const id = `toast-${++toastId}`\r\n const toast: ToastItem = {\r\n id,\r\n message: options.message,\r\n type: options.type || 'info',\r\n title: options.title,\r\n duration: options.duration ?? 5000,\r\n }\r\n toasts.value.push(toast)\r\n return id\r\n }\r\n\r\n const remove = (id: string) => {\r\n const index = toasts.value.findIndex((t) => t.id === id)\r\n if (index > -1) {\r\n toasts.value.splice(index, 1)\r\n }\r\n }\r\n\r\n const clear = () => {\r\n toasts.value = []\r\n }\r\n\r\n const success = (message: string, title?: string) => {\r\n return add({ message, title, type: 'success' })\r\n }\r\n\r\n const error = (message: string, title?: string) => {\r\n return add({ message, title, type: 'error' })\r\n }\r\n\r\n const warning = (message: string, title?: string) => {\r\n return add({ message, title, type: 'warning' })\r\n }\r\n\r\n const info = (message: string, title?: string) => {\r\n return add({ message, title, type: 'info' })\r\n }\r\n\r\n return {\r\n toasts,\r\n add,\r\n remove,\r\n clear,\r\n success,\r\n error,\r\n warning,\r\n info,\r\n }\r\n}\r\n"],"names":[],"mappings":"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","x_google_ignoreList":[0]}