cisse-vue-ui 0.8.1 → 0.8.3
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.
- package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-CYnPpiRd.cjs → CheckboxGroup.vue_vue_type_script_setup_true_lang-DuJr8cz3.cjs} +2 -2
- package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-CYnPpiRd.cjs.map → CheckboxGroup.vue_vue_type_script_setup_true_lang-DuJr8cz3.cjs.map} +1 -1
- package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-BPALlche.js → CheckboxGroup.vue_vue_type_script_setup_true_lang-N4oS_DJD.js} +2 -2
- package/dist/{CheckboxGroup.vue_vue_type_script_setup_true_lang-BPALlche.js.map → CheckboxGroup.vue_vue_type_script_setup_true_lang-N4oS_DJD.js.map} +1 -1
- package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-56CxoSmj.js → Dropdown.vue_vue_type_script_setup_true_lang-A9Ax6iob.js} +110 -14
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-A9Ax6iob.js.map +1 -0
- package/dist/{Dropdown.vue_vue_type_script_setup_true_lang-Dd3ySRNB.cjs → Dropdown.vue_vue_type_script_setup_true_lang-DoJKvn30.cjs} +109 -13
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-DoJKvn30.cjs.map +1 -0
- package/dist/{FilterTabs.vue_vue_type_script_setup_true_lang-DzLwUVCW.js → FilterTabs.vue_vue_type_script_setup_true_lang-CcOgc2Y_.js} +2 -2
- package/dist/{FilterTabs.vue_vue_type_script_setup_true_lang-DzLwUVCW.js.map → FilterTabs.vue_vue_type_script_setup_true_lang-CcOgc2Y_.js.map} +1 -1
- package/dist/{FilterTabs.vue_vue_type_script_setup_true_lang-Bj3I5Sn7.cjs → FilterTabs.vue_vue_type_script_setup_true_lang-jW6Ikbvy.cjs} +2 -2
- package/dist/{FilterTabs.vue_vue_type_script_setup_true_lang-Bj3I5Sn7.cjs.map → FilterTabs.vue_vue_type_script_setup_true_lang-jW6Ikbvy.cjs.map} +1 -1
- package/dist/{PageHero.vue_vue_type_script_setup_true_lang-DQQGYAw0.js → PageHero.vue_vue_type_script_setup_true_lang-Gvocjdqh.js} +2 -2
- package/dist/{PageHero.vue_vue_type_script_setup_true_lang-DQQGYAw0.js.map → PageHero.vue_vue_type_script_setup_true_lang-Gvocjdqh.js.map} +1 -1
- package/dist/{PageHero.vue_vue_type_script_setup_true_lang-Bi97ypMD.cjs → PageHero.vue_vue_type_script_setup_true_lang-rbvfGvll.cjs} +2 -2
- package/dist/{PageHero.vue_vue_type_script_setup_true_lang-Bi97ypMD.cjs.map → PageHero.vue_vue_type_script_setup_true_lang-rbvfGvll.cjs.map} +1 -1
- package/dist/components/core/MenuItem.stories.d.ts +1 -0
- package/dist/components/core/MenuItem.vue.d.ts +13 -2
- package/dist/components/core/index.cjs +2 -2
- package/dist/components/core/index.js +2 -2
- package/dist/components/form/index.cjs +1 -1
- package/dist/components/form/index.js +1 -1
- package/dist/components/index.cjs +4 -4
- package/dist/components/index.js +4 -4
- package/dist/components/layout/index.cjs +1 -1
- package/dist/components/layout/index.js +1 -1
- package/dist/composables/index.cjs +1 -1
- package/dist/composables/index.js +1 -1
- package/dist/composables/useDropdown.d.ts +4 -1
- package/dist/{index-QKwLtDJE.cjs → index-5dQNEzd8.cjs} +5 -5
- package/dist/{index-QKwLtDJE.cjs.map → index-5dQNEzd8.cjs.map} +1 -1
- package/dist/{index-CE90-_mh.js → index-CDDUEkXf.js} +5 -5
- package/dist/index-CDDUEkXf.js.map +1 -0
- package/dist/index.cjs +6 -6
- package/dist/index.js +6 -6
- package/dist/style.css +1 -1
- package/dist/{useDropdown-DHFnd259.cjs → useDropdown-DK4c5JGL.cjs} +66 -10
- package/dist/useDropdown-DK4c5JGL.cjs.map +1 -0
- package/dist/{useDropdown-iVu14E6s.js → useDropdown-De0cKI83.js} +66 -10
- package/dist/useDropdown-De0cKI83.js.map +1 -0
- package/package.json +1 -1
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-56CxoSmj.js.map +0 -1
- package/dist/Dropdown.vue_vue_type_script_setup_true_lang-Dd3ySRNB.cjs.map +0 -1
- package/dist/index-CE90-_mh.js.map +0 -1
- package/dist/useDropdown-DHFnd259.cjs.map +0 -1
- package/dist/useDropdown-iVu14E6s.js.map +0 -1
|
@@ -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
|
|
12
|
-
left
|
|
13
|
-
right
|
|
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
|
-
|
|
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-
|
|
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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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.vue_vue_type_script_setup_true_lang-56CxoSmj.js","sources":["../src/components/core/MenuItem.vue","../src/components/core/Dropdown.vue"],"sourcesContent":["<script lang=\"ts\" setup>\r\nimport { computed, resolveComponent, ref, useSlots } from 'vue'\r\nimport { Icon } from '@iconify/vue'\r\nimport type { MenuItemProps } from '@/types'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n menuItem: MenuItemProps\r\n /** Whether sidebar is expanded (shows labels) */\r\n expanded?: boolean\r\n /** Override active state directly */\r\n active?: boolean\r\n /** Current route path (pass from parent using useRoute().path) */\r\n currentPath?: string\r\n /** Nesting depth level (used internally for indentation) */\r\n depth?: number\r\n }>(),\r\n {\r\n expanded: true,\r\n active: undefined,\r\n currentPath: undefined,\r\n depth: 0,\r\n },\r\n)\r\n\r\nconst slots = useSlots()\r\nconst submenuOpen = ref(false)\r\n\r\nconst hasSlotContent = computed(() => !!slots.submenu)\r\n\r\nconst hasChildren = computed(() => {\r\n return props.menuItem.children && props.menuItem.children.length > 0\r\n})\r\n\r\nconst hasSubmenu = computed(() => hasChildren.value || hasSlotContent.value)\r\n\r\nconst isRouteActive = computed(() => {\r\n // If active prop is explicitly set, use it\r\n if (props.active !== undefined) {\r\n return props.active\r\n }\r\n\r\n // Use currentPath prop if provided, otherwise fall back to window.location\r\n const path = props.currentPath ?? (typeof window !== 'undefined' ? window.location.pathname : '/')\r\n\r\n if (props.menuItem.link === '/') {\r\n return path === '/'\r\n }\r\n return path === props.menuItem.link || path.startsWith(props.menuItem.link + '/')\r\n})\r\n\r\n// Check if any child is active (to highlight parent)\r\nconst isChildActive = computed(() => {\r\n if (!hasChildren.value) return false\r\n\r\n const path = props.currentPath ?? (typeof window !== 'undefined' ? window.location.pathname : '/')\r\n\r\n const checkActive = (items: MenuItemProps[]): boolean => {\r\n return items.some((item) => {\r\n if (item.link === path || path.startsWith(item.link + '/')) {\r\n return true\r\n }\r\n if (item.children) {\r\n return checkActive(item.children)\r\n }\r\n return false\r\n })\r\n }\r\n\r\n return checkActive(props.menuItem.children!)\r\n})\r\n\r\n// Try to resolve RouterLink, fallback to 'a' tag\r\nconst linkComponent = computed(() => {\r\n if (hasSubmenu.value) {\r\n return 'button'\r\n }\r\n try {\r\n const RouterLink = resolveComponent('RouterLink')\r\n if (typeof RouterLink !== 'string') {\r\n return RouterLink\r\n }\r\n } catch {\r\n // RouterLink not available\r\n }\r\n return 'a'\r\n})\r\n\r\nconst linkProps = computed(() => {\r\n if (hasSubmenu.value) {\r\n return { type: 'button' as const }\r\n }\r\n if (linkComponent.value === 'a') {\r\n return { href: props.menuItem.link }\r\n }\r\n return { to: props.menuItem.link }\r\n})\r\n\r\nconst toggleSubmenu = () => {\r\n if (hasSubmenu.value) {\r\n submenuOpen.value = !submenuOpen.value\r\n }\r\n}\r\n\r\nconst paddingLeft = computed(() => {\r\n if (!props.expanded) return undefined\r\n const basePadding = 20 // px-5 = 20px\r\n const indentPerLevel = 16 // 16px per nesting level\r\n return `${basePadding + props.depth * indentPerLevel}px`\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"w-full\">\r\n <component\r\n :is=\"linkComponent\"\r\n v-bind=\"linkProps\"\r\n :class=\"expanded ? 'flex-row' : 'flex-col'\"\r\n :style=\"expanded ? { paddingLeft } : undefined\"\r\n class=\"group relative flex w-full items-center justify-center gap-2 pr-5 py-2\"\r\n @click=\"toggleSubmenu\"\r\n >\r\n <div class=\"relative\">\r\n <Icon\r\n :class=\"[\r\n isRouteActive || isChildActive\r\n ? 'text-white'\r\n : 'text-white/50 group-hover:text-white/80 dark:text-gray-700 dark:group-hover:text-gray-500',\r\n expanded ? 'size-5' : 'size-8',\r\n ]\"\r\n class=\"transition-all duration-300\"\r\n :icon=\"menuItem.icon\"\r\n />\r\n\r\n <span\r\n v-if=\"menuItem.notification\"\r\n class=\"absolute top-0.25 right-0.25 size-1.5 rounded-full bg-red-600\"\r\n />\r\n </div>\r\n\r\n <Transition\r\n enter-active-class=\"transition-all duration-300 ease-out\"\r\n enter-from-class=\"opacity-0 -translate-x-2\"\r\n enter-to-class=\"opacity-100 translate-x-0\"\r\n leave-active-class=\"transition-all duration-200 ease-in\"\r\n leave-from-class=\"opacity-100 translate-x-0\"\r\n leave-to-class=\"opacity-0 -translate-x-2\"\r\n >\r\n <span\r\n v-if=\"expanded\"\r\n :class=\"\r\n isRouteActive || isChildActive\r\n ? 'text-white'\r\n : 'text-white/50 group-hover:text-white/80 dark:text-gray-700 dark:group-hover:text-gray-500'\r\n \"\r\n class=\"flex-1 text-left text-sm font-semibold whitespace-nowrap\"\r\n >{{ menuItem.label }}</span>\r\n </Transition>\r\n\r\n <!-- Chevron for submenu -->\r\n <Icon\r\n v-if=\"hasSubmenu && expanded\"\r\n :class=\"[\r\n submenuOpen ? 'rotate-90' : '',\r\n isRouteActive || isChildActive\r\n ? 'text-white'\r\n : 'text-white/50 group-hover:text-white/80 dark:text-gray-700 dark:group-hover:text-gray-500',\r\n ]\"\r\n class=\"size-4 transition-transform duration-200\"\r\n icon=\"lucide:chevron-right\"\r\n />\r\n </component>\r\n\r\n <!-- Submenu content -->\r\n <Transition\r\n enter-active-class=\"transition-all duration-300 ease-out\"\r\n enter-from-class=\"opacity-0 max-h-0\"\r\n enter-to-class=\"opacity-100 max-h-96\"\r\n leave-active-class=\"transition-all duration-200 ease-in\"\r\n leave-from-class=\"opacity-100 max-h-96\"\r\n leave-to-class=\"opacity-0 max-h-0\"\r\n >\r\n <div\r\n v-if=\"hasSubmenu && submenuOpen && expanded\"\r\n class=\"overflow-hidden\"\r\n >\r\n <!-- Slot for custom submenu content -->\r\n <slot\r\n name=\"submenu\"\r\n :depth=\"depth + 1\"\r\n :expanded=\"expanded\"\r\n :current-path=\"currentPath\"\r\n />\r\n\r\n <!-- Default children from prop -->\r\n <MenuItem\r\n v-for=\"(child, index) in menuItem.children\"\r\n :key=\"index\"\r\n :menu-item=\"child\"\r\n :expanded=\"expanded\"\r\n :current-path=\"currentPath\"\r\n :depth=\"depth + 1\"\r\n />\r\n </div>\r\n </Transition>\r\n </div>\r\n</template>\r\n","<script lang=\"ts\" setup>\r\nimport { ref, computed } from 'vue'\r\nimport { Icon } from '@iconify/vue'\r\nimport { useDropdown } from '@/composables/useDropdown'\r\nimport { useId } from '@/composables/useId'\r\n\r\nexport interface DropdownItem {\r\n key: string\r\n label: string\r\n icon?: string\r\n disabled?: boolean\r\n danger?: boolean\r\n divider?: boolean\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** Dropdown items (optional if using default slot) */\r\n items?: DropdownItem[]\r\n /** Align dropdown */\r\n align?: 'left' | 'right'\r\n /** Dropdown width */\r\n width?: 'auto' | 'full' | 'sm' | 'md' | 'lg'\r\n /** Use teleport to body to avoid overflow clipping */\r\n teleport?: boolean\r\n /** Custom ID for accessibility */\r\n id?: string\r\n }>(),\r\n {\r\n items: () => [],\r\n align: 'left',\r\n width: 'auto',\r\n teleport: true,\r\n },\r\n)\r\n\r\n// Generate unique IDs for accessibility\r\nconst { related } = useId({ prefix: 'dropdown', id: props.id })\r\nconst triggerId = computed(() => related('trigger'))\r\nconst menuId = computed(() => related('menu'))\r\n\r\nconst emit = defineEmits<{\r\n select: [item: DropdownItem]\r\n}>()\r\n\r\nconst triggerRef = ref<HTMLElement>()\r\nconst menuRef = ref<HTMLElement>()\r\n\r\nconst { isOpen, dropdownStyle, toggle, close } = useDropdown(triggerRef, menuRef, {\r\n teleport: props.teleport,\r\n align: props.align,\r\n})\r\n\r\nconst selectItem = (item: DropdownItem) => {\r\n if (item.disabled || item.divider) return\r\n emit('select', item)\r\n close()\r\n}\r\n\r\nconst widthClasses = {\r\n auto: 'w-auto min-w-40',\r\n full: 'w-full',\r\n sm: 'w-32',\r\n md: 'w-48',\r\n lg: 'w-64',\r\n}\r\n\r\nconst computedDropdownStyle = computed(() => {\r\n if (!props.teleport) return {}\r\n const { width: _, ...rest } = dropdownStyle.value\r\n return rest\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"relative inline-block\">\r\n <div\r\n ref=\"triggerRef\"\r\n @click=\"toggle\"\r\n >\r\n <slot name=\"trigger\">\r\n <button\r\n :id=\"triggerId\"\r\n type=\"button\"\r\n :aria-expanded=\"isOpen\"\r\n aria-haspopup=\"menu\"\r\n :aria-controls=\"menuId\"\r\n class=\"inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700\"\r\n >\r\n <slot name=\"trigger-label\">\r\n Options\r\n </slot>\r\n <Icon\r\n icon=\"lucide:chevron-down\"\r\n :class=\"['size-4 transition-transform', isOpen && 'rotate-180']\"\r\n aria-hidden=\"true\"\r\n />\r\n </button>\r\n </slot>\r\n </div>\r\n\r\n <Teleport\r\n to=\"body\"\r\n :disabled=\"!teleport\"\r\n >\r\n <Transition\r\n enter-active-class=\"transition ease-out duration-100\"\r\n enter-from-class=\"transform opacity-0 scale-95\"\r\n enter-to-class=\"transform opacity-100 scale-100\"\r\n leave-active-class=\"transition ease-in duration-75\"\r\n leave-from-class=\"transform opacity-100 scale-100\"\r\n leave-to-class=\"transform opacity-0 scale-95\"\r\n >\r\n <div\r\n v-if=\"isOpen\"\r\n :id=\"menuId\"\r\n ref=\"menuRef\"\r\n role=\"menu\"\r\n :aria-labelledby=\"triggerId\"\r\n :style=\"computedDropdownStyle\"\r\n :class=\"[\r\n 'z-9999 rounded-lg border border-gray-200 bg-white py-1 shadow-lg dark:border-gray-700 dark:bg-gray-800',\r\n widthClasses[width],\r\n !teleport && (align === 'right' ? 'absolute mt-2 right-0' : 'absolute mt-2 left-0'),\r\n ]\"\r\n >\r\n <!-- Custom content via default slot -->\r\n <slot :close=\"close\">\r\n <!-- Default items rendering -->\r\n <template\r\n v-for=\"item in items\"\r\n :key=\"item.key\"\r\n >\r\n <div\r\n v-if=\"item.divider\"\r\n role=\"separator\"\r\n class=\"my-1 border-t border-gray-200 dark:border-gray-700\"\r\n />\r\n <button\r\n v-else\r\n type=\"button\"\r\n role=\"menuitem\"\r\n :disabled=\"item.disabled\"\r\n :class=\"[\r\n 'flex w-full items-center gap-2 px-4 py-2 text-left text-sm transition-colors',\r\n item.disabled\r\n ? 'cursor-not-allowed opacity-50'\r\n : item.danger\r\n ? 'text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20'\r\n : 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700',\r\n ]\"\r\n @click=\"selectItem(item)\"\r\n >\r\n <Icon\r\n v-if=\"item.icon\"\r\n :icon=\"item.icon\"\r\n class=\"size-4\"\r\n aria-hidden=\"true\"\r\n />\r\n {{ item.label }}\r\n </button>\r\n </template>\r\n </slot>\r\n </div>\r\n </Transition>\r\n </Teleport>\r\n </div>\r\n</template>\r\n"],"names":["_openBlock","_createElementBlock","_hoisted_1","_createBlock","_resolveDynamicComponent","_mergeProps","_createElementVNode","_hoisted_2","_createVNode","_unref","_normalizeClass","_hoisted_3","_Transition","_toDisplayString","_hoisted_4","_renderSlot","_Fragment","_Teleport","_renderList"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.vue_vue_type_script_setup_true_lang-Dd3ySRNB.cjs","sources":["../src/components/core/MenuItem.vue","../src/components/core/Dropdown.vue"],"sourcesContent":["<script lang=\"ts\" setup>\r\nimport { computed, resolveComponent, ref, useSlots } from 'vue'\r\nimport { Icon } from '@iconify/vue'\r\nimport type { MenuItemProps } from '@/types'\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n menuItem: MenuItemProps\r\n /** Whether sidebar is expanded (shows labels) */\r\n expanded?: boolean\r\n /** Override active state directly */\r\n active?: boolean\r\n /** Current route path (pass from parent using useRoute().path) */\r\n currentPath?: string\r\n /** Nesting depth level (used internally for indentation) */\r\n depth?: number\r\n }>(),\r\n {\r\n expanded: true,\r\n active: undefined,\r\n currentPath: undefined,\r\n depth: 0,\r\n },\r\n)\r\n\r\nconst slots = useSlots()\r\nconst submenuOpen = ref(false)\r\n\r\nconst hasSlotContent = computed(() => !!slots.submenu)\r\n\r\nconst hasChildren = computed(() => {\r\n return props.menuItem.children && props.menuItem.children.length > 0\r\n})\r\n\r\nconst hasSubmenu = computed(() => hasChildren.value || hasSlotContent.value)\r\n\r\nconst isRouteActive = computed(() => {\r\n // If active prop is explicitly set, use it\r\n if (props.active !== undefined) {\r\n return props.active\r\n }\r\n\r\n // Use currentPath prop if provided, otherwise fall back to window.location\r\n const path = props.currentPath ?? (typeof window !== 'undefined' ? window.location.pathname : '/')\r\n\r\n if (props.menuItem.link === '/') {\r\n return path === '/'\r\n }\r\n return path === props.menuItem.link || path.startsWith(props.menuItem.link + '/')\r\n})\r\n\r\n// Check if any child is active (to highlight parent)\r\nconst isChildActive = computed(() => {\r\n if (!hasChildren.value) return false\r\n\r\n const path = props.currentPath ?? (typeof window !== 'undefined' ? window.location.pathname : '/')\r\n\r\n const checkActive = (items: MenuItemProps[]): boolean => {\r\n return items.some((item) => {\r\n if (item.link === path || path.startsWith(item.link + '/')) {\r\n return true\r\n }\r\n if (item.children) {\r\n return checkActive(item.children)\r\n }\r\n return false\r\n })\r\n }\r\n\r\n return checkActive(props.menuItem.children!)\r\n})\r\n\r\n// Try to resolve RouterLink, fallback to 'a' tag\r\nconst linkComponent = computed(() => {\r\n if (hasSubmenu.value) {\r\n return 'button'\r\n }\r\n try {\r\n const RouterLink = resolveComponent('RouterLink')\r\n if (typeof RouterLink !== 'string') {\r\n return RouterLink\r\n }\r\n } catch {\r\n // RouterLink not available\r\n }\r\n return 'a'\r\n})\r\n\r\nconst linkProps = computed(() => {\r\n if (hasSubmenu.value) {\r\n return { type: 'button' as const }\r\n }\r\n if (linkComponent.value === 'a') {\r\n return { href: props.menuItem.link }\r\n }\r\n return { to: props.menuItem.link }\r\n})\r\n\r\nconst toggleSubmenu = () => {\r\n if (hasSubmenu.value) {\r\n submenuOpen.value = !submenuOpen.value\r\n }\r\n}\r\n\r\nconst paddingLeft = computed(() => {\r\n if (!props.expanded) return undefined\r\n const basePadding = 20 // px-5 = 20px\r\n const indentPerLevel = 16 // 16px per nesting level\r\n return `${basePadding + props.depth * indentPerLevel}px`\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"w-full\">\r\n <component\r\n :is=\"linkComponent\"\r\n v-bind=\"linkProps\"\r\n :class=\"expanded ? 'flex-row' : 'flex-col'\"\r\n :style=\"expanded ? { paddingLeft } : undefined\"\r\n class=\"group relative flex w-full items-center justify-center gap-2 pr-5 py-2\"\r\n @click=\"toggleSubmenu\"\r\n >\r\n <div class=\"relative\">\r\n <Icon\r\n :class=\"[\r\n isRouteActive || isChildActive\r\n ? 'text-white'\r\n : 'text-white/50 group-hover:text-white/80 dark:text-gray-700 dark:group-hover:text-gray-500',\r\n expanded ? 'size-5' : 'size-8',\r\n ]\"\r\n class=\"transition-all duration-300\"\r\n :icon=\"menuItem.icon\"\r\n />\r\n\r\n <span\r\n v-if=\"menuItem.notification\"\r\n class=\"absolute top-0.25 right-0.25 size-1.5 rounded-full bg-red-600\"\r\n />\r\n </div>\r\n\r\n <Transition\r\n enter-active-class=\"transition-all duration-300 ease-out\"\r\n enter-from-class=\"opacity-0 -translate-x-2\"\r\n enter-to-class=\"opacity-100 translate-x-0\"\r\n leave-active-class=\"transition-all duration-200 ease-in\"\r\n leave-from-class=\"opacity-100 translate-x-0\"\r\n leave-to-class=\"opacity-0 -translate-x-2\"\r\n >\r\n <span\r\n v-if=\"expanded\"\r\n :class=\"\r\n isRouteActive || isChildActive\r\n ? 'text-white'\r\n : 'text-white/50 group-hover:text-white/80 dark:text-gray-700 dark:group-hover:text-gray-500'\r\n \"\r\n class=\"flex-1 text-left text-sm font-semibold whitespace-nowrap\"\r\n >{{ menuItem.label }}</span>\r\n </Transition>\r\n\r\n <!-- Chevron for submenu -->\r\n <Icon\r\n v-if=\"hasSubmenu && expanded\"\r\n :class=\"[\r\n submenuOpen ? 'rotate-90' : '',\r\n isRouteActive || isChildActive\r\n ? 'text-white'\r\n : 'text-white/50 group-hover:text-white/80 dark:text-gray-700 dark:group-hover:text-gray-500',\r\n ]\"\r\n class=\"size-4 transition-transform duration-200\"\r\n icon=\"lucide:chevron-right\"\r\n />\r\n </component>\r\n\r\n <!-- Submenu content -->\r\n <Transition\r\n enter-active-class=\"transition-all duration-300 ease-out\"\r\n enter-from-class=\"opacity-0 max-h-0\"\r\n enter-to-class=\"opacity-100 max-h-96\"\r\n leave-active-class=\"transition-all duration-200 ease-in\"\r\n leave-from-class=\"opacity-100 max-h-96\"\r\n leave-to-class=\"opacity-0 max-h-0\"\r\n >\r\n <div\r\n v-if=\"hasSubmenu && submenuOpen && expanded\"\r\n class=\"overflow-hidden\"\r\n >\r\n <!-- Slot for custom submenu content -->\r\n <slot\r\n name=\"submenu\"\r\n :depth=\"depth + 1\"\r\n :expanded=\"expanded\"\r\n :current-path=\"currentPath\"\r\n />\r\n\r\n <!-- Default children from prop -->\r\n <MenuItem\r\n v-for=\"(child, index) in menuItem.children\"\r\n :key=\"index\"\r\n :menu-item=\"child\"\r\n :expanded=\"expanded\"\r\n :current-path=\"currentPath\"\r\n :depth=\"depth + 1\"\r\n />\r\n </div>\r\n </Transition>\r\n </div>\r\n</template>\r\n","<script lang=\"ts\" setup>\r\nimport { ref, computed } from 'vue'\r\nimport { Icon } from '@iconify/vue'\r\nimport { useDropdown } from '@/composables/useDropdown'\r\nimport { useId } from '@/composables/useId'\r\n\r\nexport interface DropdownItem {\r\n key: string\r\n label: string\r\n icon?: string\r\n disabled?: boolean\r\n danger?: boolean\r\n divider?: boolean\r\n}\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** Dropdown items (optional if using default slot) */\r\n items?: DropdownItem[]\r\n /** Align dropdown */\r\n align?: 'left' | 'right'\r\n /** Dropdown width */\r\n width?: 'auto' | 'full' | 'sm' | 'md' | 'lg'\r\n /** Use teleport to body to avoid overflow clipping */\r\n teleport?: boolean\r\n /** Custom ID for accessibility */\r\n id?: string\r\n }>(),\r\n {\r\n items: () => [],\r\n align: 'left',\r\n width: 'auto',\r\n teleport: true,\r\n },\r\n)\r\n\r\n// Generate unique IDs for accessibility\r\nconst { related } = useId({ prefix: 'dropdown', id: props.id })\r\nconst triggerId = computed(() => related('trigger'))\r\nconst menuId = computed(() => related('menu'))\r\n\r\nconst emit = defineEmits<{\r\n select: [item: DropdownItem]\r\n}>()\r\n\r\nconst triggerRef = ref<HTMLElement>()\r\nconst menuRef = ref<HTMLElement>()\r\n\r\nconst { isOpen, dropdownStyle, toggle, close } = useDropdown(triggerRef, menuRef, {\r\n teleport: props.teleport,\r\n align: props.align,\r\n})\r\n\r\nconst selectItem = (item: DropdownItem) => {\r\n if (item.disabled || item.divider) return\r\n emit('select', item)\r\n close()\r\n}\r\n\r\nconst widthClasses = {\r\n auto: 'w-auto min-w-40',\r\n full: 'w-full',\r\n sm: 'w-32',\r\n md: 'w-48',\r\n lg: 'w-64',\r\n}\r\n\r\nconst computedDropdownStyle = computed(() => {\r\n if (!props.teleport) return {}\r\n const { width: _, ...rest } = dropdownStyle.value\r\n return rest\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"relative inline-block\">\r\n <div\r\n ref=\"triggerRef\"\r\n @click=\"toggle\"\r\n >\r\n <slot name=\"trigger\">\r\n <button\r\n :id=\"triggerId\"\r\n type=\"button\"\r\n :aria-expanded=\"isOpen\"\r\n aria-haspopup=\"menu\"\r\n :aria-controls=\"menuId\"\r\n class=\"inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700\"\r\n >\r\n <slot name=\"trigger-label\">\r\n Options\r\n </slot>\r\n <Icon\r\n icon=\"lucide:chevron-down\"\r\n :class=\"['size-4 transition-transform', isOpen && 'rotate-180']\"\r\n aria-hidden=\"true\"\r\n />\r\n </button>\r\n </slot>\r\n </div>\r\n\r\n <Teleport\r\n to=\"body\"\r\n :disabled=\"!teleport\"\r\n >\r\n <Transition\r\n enter-active-class=\"transition ease-out duration-100\"\r\n enter-from-class=\"transform opacity-0 scale-95\"\r\n enter-to-class=\"transform opacity-100 scale-100\"\r\n leave-active-class=\"transition ease-in duration-75\"\r\n leave-from-class=\"transform opacity-100 scale-100\"\r\n leave-to-class=\"transform opacity-0 scale-95\"\r\n >\r\n <div\r\n v-if=\"isOpen\"\r\n :id=\"menuId\"\r\n ref=\"menuRef\"\r\n role=\"menu\"\r\n :aria-labelledby=\"triggerId\"\r\n :style=\"computedDropdownStyle\"\r\n :class=\"[\r\n 'z-9999 rounded-lg border border-gray-200 bg-white py-1 shadow-lg dark:border-gray-700 dark:bg-gray-800',\r\n widthClasses[width],\r\n !teleport && (align === 'right' ? 'absolute mt-2 right-0' : 'absolute mt-2 left-0'),\r\n ]\"\r\n >\r\n <!-- Custom content via default slot -->\r\n <slot :close=\"close\">\r\n <!-- Default items rendering -->\r\n <template\r\n v-for=\"item in items\"\r\n :key=\"item.key\"\r\n >\r\n <div\r\n v-if=\"item.divider\"\r\n role=\"separator\"\r\n class=\"my-1 border-t border-gray-200 dark:border-gray-700\"\r\n />\r\n <button\r\n v-else\r\n type=\"button\"\r\n role=\"menuitem\"\r\n :disabled=\"item.disabled\"\r\n :class=\"[\r\n 'flex w-full items-center gap-2 px-4 py-2 text-left text-sm transition-colors',\r\n item.disabled\r\n ? 'cursor-not-allowed opacity-50'\r\n : item.danger\r\n ? 'text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20'\r\n : 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700',\r\n ]\"\r\n @click=\"selectItem(item)\"\r\n >\r\n <Icon\r\n v-if=\"item.icon\"\r\n :icon=\"item.icon\"\r\n class=\"size-4\"\r\n aria-hidden=\"true\"\r\n />\r\n {{ item.label }}\r\n </button>\r\n </template>\r\n </slot>\r\n </div>\r\n </Transition>\r\n </Teleport>\r\n </div>\r\n</template>\r\n"],"names":["useSlots","ref","computed","resolveComponent","_openBlock","_createElementBlock","_hoisted_1","_createBlock","_resolveDynamicComponent","_mergeProps","_createElementVNode","_hoisted_2","_createVNode","_unref","Icon","_normalizeClass","_hoisted_3","_Transition","_toDisplayString","_hoisted_4","_renderSlot","_Fragment","useId","useDropdown","_Teleport","_renderList"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-CE90-_mh.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDropdown-DHFnd259.cjs","sources":["../src/composables/useDropdown.ts"],"sourcesContent":["import { ref, computed, watch, onUnmounted, nextTick, type Ref, type ComputedRef } from 'vue'\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 */\r\n align?: 'left' | 'right'\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', 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 dropdownPosition.value = {\r\n top: rect.bottom + window.scrollY + gap,\r\n left: rect.left + window.scrollX,\r\n right: window.innerWidth - rect.right - window.scrollX,\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 return {\r\n position: 'absolute',\r\n top: `${dropdownPosition.value.top}px`,\r\n left: align === 'right' ? 'auto' : `${dropdownPosition.value.left}px`,\r\n right: align === 'right' ? `${dropdownPosition.value.right}px` : 'auto',\r\n width: `${dropdownPosition.value.width}px`,\r\n } as Record<string, string>\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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDropdown-iVu14E6s.js","sources":["../src/composables/useDropdown.ts"],"sourcesContent":["import { ref, computed, watch, onUnmounted, nextTick, type Ref, type ComputedRef } from 'vue'\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 */\r\n align?: 'left' | 'right'\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', 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 dropdownPosition.value = {\r\n top: rect.bottom + window.scrollY + gap,\r\n left: rect.left + window.scrollX,\r\n right: window.innerWidth - rect.right - window.scrollX,\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 return {\r\n position: 'absolute',\r\n top: `${dropdownPosition.value.top}px`,\r\n left: align === 'right' ? 'auto' : `${dropdownPosition.value.left}px`,\r\n right: align === 'right' ? `${dropdownPosition.value.right}px` : 'auto',\r\n width: `${dropdownPosition.value.width}px`,\r\n } as Record<string, string>\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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"}
|