srcdev-nuxt-components 9.1.8 → 9.1.10

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.
@@ -0,0 +1,90 @@
1
+ import { useResizeObserver, onClickOutside } from "@vueuse/core";
2
+ import type { NavItemData } from "~/types/components";
3
+
4
+ interface NavCollapseOptions {
5
+ onResize?: () => void;
6
+ onRouteChange?: () => void;
7
+ onMounted?: () => void;
8
+ }
9
+
10
+ export const useNavCollapse = (navItemData: NavItemData, stateKey: string, options: NavCollapseOptions = {}) => {
11
+ const navRef = ref<HTMLElement | null>(null);
12
+ const navListRef = ref<HTMLUListElement | null>(null);
13
+
14
+ const isCollapsed = ref(false);
15
+ const isLoaded = useState(stateKey, () => false);
16
+ const isMenuOpen = ref(false);
17
+ const isMounted = ref(false);
18
+
19
+ // Stored natural width of the list — used when the list is not in the DOM
20
+ let navListNaturalWidth = 0;
21
+
22
+ const checkOverflow = () => {
23
+ if (!navRef.value) return;
24
+ if (navListRef.value) {
25
+ navListNaturalWidth = navListRef.value.scrollWidth;
26
+ }
27
+ isCollapsed.value = navListNaturalWidth > navRef.value.clientWidth;
28
+ };
29
+
30
+ const toggleMenu = () => {
31
+ isMenuOpen.value = !isMenuOpen.value;
32
+ };
33
+
34
+ const closeMenu = () => {
35
+ isMenuOpen.value = false;
36
+ };
37
+
38
+ const navigationStore = useNavigationStore();
39
+ const route = useRoute();
40
+
41
+ const activeHref = computed(() => navigationStore.currentActiveHref);
42
+
43
+ // Client-side only active state check to prevent hydration mismatch
44
+ const isActiveItem = (href?: string) => isMounted.value && activeHref.value === href;
45
+
46
+ watch(
47
+ () => route.path,
48
+ (newPath) => {
49
+ closeMenu();
50
+ navigationStore.syncWithRoute(navItemData.main ?? [], newPath);
51
+ options.onRouteChange?.();
52
+ },
53
+ { flush: "post" }
54
+ );
55
+
56
+ useResizeObserver(navRef, () => {
57
+ checkOverflow();
58
+ if (!isCollapsed.value) closeMenu();
59
+ options.onResize?.();
60
+ });
61
+
62
+ onClickOutside(navRef, closeMenu);
63
+
64
+ const router = useRouter();
65
+
66
+ onMounted(async () => {
67
+ await nextTick();
68
+ checkOverflow();
69
+ isLoaded.value = true;
70
+ await router.isReady();
71
+ navigationStore.initializeFromRoute(navItemData.main ?? [], route.path);
72
+ isMounted.value = true;
73
+ options.onMounted?.();
74
+ });
75
+
76
+ return {
77
+ navRef,
78
+ navListRef,
79
+ isCollapsed,
80
+ isLoaded,
81
+ isMenuOpen,
82
+ isMounted,
83
+ activeHref,
84
+ checkOverflow,
85
+ toggleMenu,
86
+ closeMenu,
87
+ isActiveItem,
88
+ navigationStore,
89
+ };
90
+ };
@@ -0,0 +1,113 @@
1
+ export const useNavigationStore = defineStore(
2
+ "useNavigationStore",
3
+ () => {
4
+ // State
5
+ const activeHref = ref<string | null>(null);
6
+ const isInitialized = ref(false);
7
+
8
+ // Getters
9
+ const currentActiveHref = computed(() => activeHref.value);
10
+
11
+ // Actions
12
+ const setActiveHref = (href: string | null) => {
13
+ activeHref.value = href;
14
+ };
15
+
16
+ /**
17
+ * Initialize active href based on current route and navigation items
18
+ * Used on component mount to handle deep links and initial page loads
19
+ */
20
+ const initializeFromRoute = (navItems: Array<{ href?: string }>, routePath: string) => {
21
+ if (isInitialized.value) return;
22
+
23
+ const items = navItems.filter((item): item is { href: string } => Boolean(item.href));
24
+
25
+ // Try exact match first
26
+ const exact = items.find((item) => routePath === item.href);
27
+ if (exact) {
28
+ activeHref.value = exact.href;
29
+ isInitialized.value = true;
30
+ return;
31
+ }
32
+
33
+ // Fall back to longest prefix match
34
+ const prefixMatches = items
35
+ .filter((item) => routePath.startsWith(item.href + "/"))
36
+ .sort((a, b) => b.href.length - a.href.length);
37
+
38
+ if (prefixMatches.length > 0) {
39
+ activeHref.value = prefixMatches[0]!.href;
40
+ } else {
41
+ // Default to null if no matches (let component handle fallback)
42
+ activeHref.value = null;
43
+ }
44
+
45
+ isInitialized.value = true;
46
+ };
47
+
48
+ /**
49
+ * Update active href when user clicks a navigation link
50
+ * Provides explicit control over active state
51
+ */
52
+ const handleNavLinkClick = (href: string) => {
53
+ activeHref.value = href;
54
+ };
55
+
56
+ /**
57
+ * Sync with route changes (for programmatic navigation)
58
+ * Re-runs the route matching logic when route changes
59
+ */
60
+ const syncWithRoute = (navItems: Array<{ href?: string }>, routePath: string) => {
61
+ const items = navItems.filter((item): item is { href: string } => Boolean(item.href));
62
+
63
+ // Try exact match first
64
+ const exact = items.find((item) => routePath === item.href);
65
+ if (exact) {
66
+ activeHref.value = exact.href;
67
+ return;
68
+ }
69
+
70
+ // Fall back to longest prefix match
71
+ const prefixMatches = items
72
+ .filter((item) => routePath.startsWith(item.href + "/"))
73
+ .sort((a, b) => b.href.length - a.href.length);
74
+
75
+ if (prefixMatches.length > 0) {
76
+ activeHref.value = prefixMatches[0]!.href;
77
+ }
78
+ // Don't reset to null if no matches - preserve existing active state
79
+ };
80
+
81
+ /**
82
+ * Reset store state (useful for testing or when navigation data changes)
83
+ */
84
+ const reset = () => {
85
+ activeHref.value = null;
86
+ isInitialized.value = false;
87
+ };
88
+
89
+ return {
90
+ // State
91
+ activeHref,
92
+ isInitialized,
93
+
94
+ // Getters
95
+ currentActiveHref,
96
+
97
+ // Actions
98
+ setActiveHref,
99
+ initializeFromRoute,
100
+ handleNavLinkClick,
101
+ syncWithRoute,
102
+ reset,
103
+ };
104
+ },
105
+ {
106
+ persist: {
107
+ storage: piniaPluginPersistedstate.sessionStorage(),
108
+ // Only persist the active href, not initialization state
109
+ // (we want to re-initialize on page refresh to handle route changes)
110
+ pick: ["activeHref"],
111
+ },
112
+ }
113
+ );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.8",
4
+ "version": "9.1.10",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",