srcdev-nuxt-components 9.1.10 → 9.1.11

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.
@@ -178,13 +178,6 @@ const moveNavHoveredIndicator = () => {
178
178
  const handleNavLinkClick = (event: MouseEvent) => {
179
179
  const target = (event.target as HTMLElement).closest<HTMLElement>("[data-nav-item]");
180
180
  if (!target) return;
181
-
182
- // Update store with clicked href for reliable active state tracking
183
- const href = target.getAttribute("href");
184
- if (href) {
185
- navigationStore.handleNavLinkClick(href);
186
- }
187
-
188
181
  currentActiveNavLink = target;
189
182
  currentHoveredNavLink = target;
190
183
  previousHoveredNavLink = target;
@@ -206,6 +199,7 @@ const resetHoverNavToActive = () => {
206
199
  };
207
200
 
208
201
  const initNavDecorators = () => {
202
+ cachedNavLinks = []; // invalidate — list may have been re-rendered (collapse toggle)
209
203
  if (!navListRef.value) return;
210
204
  const links = getNavLinks();
211
205
  if (!links.length) return;
@@ -287,13 +281,6 @@ const movePanelHoveredIndicator = () => {
287
281
  const handlePanelLinkClick = (event: MouseEvent) => {
288
282
  const target = (event.target as HTMLElement).closest<HTMLElement>("[data-panel-nav-item]");
289
283
  if (!target) return;
290
-
291
- // Update store with clicked href for reliable active state tracking
292
- const href = target.getAttribute("href");
293
- if (href) {
294
- navigationStore.handleNavLinkClick(href);
295
- }
296
-
297
284
  currentActivePanelLink = target;
298
285
  currentHoveredPanelLink = target;
299
286
  previousHoveredPanelLink = target;
@@ -338,13 +325,15 @@ const initPanelDecorators = () => {
338
325
 
339
326
  // ─────────────────────────────────────────────────────────────────────────────
340
327
 
341
- const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, activeHref, isActiveItem, toggleMenu, closeMenu, navigationStore } =
342
- useNavCollapse(props.navItemData, "site-nav-loaded", {
343
- onResize: () => {
344
- setFinalNavActivePositions(true);
345
- setFinalNavHoveredPositions(true);
346
- setFinalPanelActivePositions(true);
347
- setFinalPanelHoveredPositions(true);
328
+ const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, activeHref, isActiveItem, toggleMenu, closeMenu } =
329
+ useNavCollapse("site-nav-loaded", {
330
+ onResize: async () => {
331
+ await nextTick(); // wait for Vue to re-render after any isCollapsed change
332
+ initNavDecorators();
333
+ if (isMenuOpen.value) {
334
+ setFinalPanelActivePositions(true);
335
+ setFinalPanelHoveredPositions(true);
336
+ }
348
337
  },
349
338
  onRouteChange: () => {
350
339
  requestAnimationFrame(() => {
@@ -3,7 +3,7 @@
3
3
  exports[`SiteNavigation > renders correct HTML structure 1`] = `
4
4
  "<nav class="site-navigation site-navigation--left is-loaded" aria-label="Site navigation">
5
5
  <ul class="site-nav-list" style="--_transition-duration: 0ms; --_x-active: 0px; --_width-active: NaN; --_x-hovered: 0px; --_width-hovered: NaN;">
6
- <li class=""><a href="/" class="site-nav-link" data-nav-item="">
6
+ <li class="is-active"><a href="/" class="site-nav-link" data-nav-item="">
7
7
  <!--v-if--> Home
8
8
  </a></li>
9
9
  <li class=""><a href="/about" class="site-nav-link" data-nav-item="">
@@ -13,7 +13,6 @@
13
13
  v-if="!isCollapsed || !isLoaded"
14
14
  ref="navListRef"
15
15
  class="tab-nav-list"
16
- @mousemove="handleNavMousemove"
17
16
  @mouseleave="hoveredItemHref = null"
18
17
  >
19
18
  <li
@@ -21,13 +20,13 @@
21
20
  :key="item.href"
22
21
  :data-href="item.href"
23
22
  :class="[item.cssName, { 'is-active': isActiveItem(item.href), 'is-hovered': hoveredItemHref === item.href }]"
23
+ @mouseenter="hoveredItemHref = item.href ?? null"
24
24
  >
25
25
  <NuxtLink
26
26
  :href="item.href"
27
27
  :external="item.isExternal || undefined"
28
28
  class="tab-nav-link"
29
29
  data-nav-item
30
- @click="handleNavLinkClick"
31
30
  >
32
31
  <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
33
32
  {{ item.text }}
@@ -38,7 +37,7 @@
38
37
  </ul>
39
38
 
40
39
  <InputButtonCore
41
- v-if="isCollapsed && isLoaded"
40
+ v-if="showCollapsed"
42
41
  class="tab-nav-burger"
43
42
  :class="{ 'is-open': isMenuOpen }"
44
43
  variant="tertiary"
@@ -56,7 +55,7 @@
56
55
 
57
56
  <Teleport to="body">
58
57
  <div
59
- v-if="isCollapsed && isLoaded"
58
+ v-if="showCollapsed"
60
59
  class="tab-nav-backdrop"
61
60
  :class="{ 'is-open': isMenuOpen }"
62
61
  aria-hidden="true"
@@ -65,7 +64,7 @@
65
64
  </Teleport>
66
65
 
67
66
  <div
68
- v-if="isCollapsed && isLoaded"
67
+ v-if="showCollapsed"
69
68
  id="tab-nav-panel"
70
69
  class="tab-nav-panel"
71
70
  :class="{ 'is-open': isMenuOpen }"
@@ -78,7 +77,7 @@
78
77
  :href="item.href"
79
78
  :external="item.isExternal || undefined"
80
79
  class="tab-nav-panel-link"
81
- @click="handlePanelLinkClick"
80
+ @click="closeMenu"
82
81
  >
83
82
  <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
84
83
  {{ item.text }}
@@ -104,33 +103,11 @@ const props = withDefaults(defineProps<Props>(), {
104
103
  styleClassPassthrough: () => [],
105
104
  });
106
105
 
107
- const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, isActiveItem, toggleMenu, closeMenu, navigationStore } =
108
- useNavCollapse(props.navItemData, "tab-nav-loaded");
109
-
110
- // Handle navigation link clicks to update store
111
- const handleNavLinkClick = (event: MouseEvent) => {
112
- const target = (event.target as HTMLElement).closest<HTMLElement>("[href]");
113
- if (!target) return;
114
- const href = target.getAttribute("href");
115
- if (href) navigationStore.handleNavLinkClick(href);
116
- };
117
-
118
- // Handle panel link clicks to update store and close menu
119
- const handlePanelLinkClick = (event: MouseEvent) => {
120
- const target = (event.target as HTMLElement).closest<HTMLElement>("[href]");
121
- if (!target) return;
122
- const href = target.getAttribute("href");
123
- if (href) navigationStore.handleNavLinkClick(href);
124
- closeMenu();
125
- };
106
+ const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, isActiveItem, toggleMenu, closeMenu } =
107
+ useNavCollapse("tab-nav-loaded");
126
108
 
127
109
  const hoveredItemHref = ref<string | null>(null);
128
-
129
- const handleNavMousemove = (event: MouseEvent) => {
130
- const li = (event.target as HTMLElement).closest<HTMLElement>("li[data-href]");
131
- if (!li) return;
132
- hoveredItemHref.value = li.dataset.href ?? null;
133
- };
110
+ const showCollapsed = computed(() => isCollapsed.value && isLoaded.value);
134
111
 
135
112
  const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
136
113
 
@@ -167,8 +144,7 @@ watch(
167
144
 
168
145
  /* Decorators — horizontal nav */
169
146
  --_decorator-hovered-bg: var(--tab-nav-decorator-hovered-bg, transparent);
170
- /* --_decorator-indicator-color: var(--tab-nav-decorator-indicator-color, var(--slate-01, currentColor)); */
171
- --_decorator-indicator-color: red;
147
+ --_decorator-indicator-color: var(--tab-nav-decorator-indicator-color, var(--slate-01, currentColor));
172
148
 
173
149
  /* Horizontal nav */
174
150
  --_link-color: var(--tab-nav-link-color, var(--slate-01, currentColor));
@@ -3,7 +3,7 @@
3
3
  exports[`TabNavigation > renders correct HTML structure 1`] = `
4
4
  "<nav class="tab-navigation tab-navigation--left is-loaded" aria-label="Site navigation">
5
5
  <ul class="tab-nav-list">
6
- <li data-href="/" class=""><a href="/" class="tab-nav-link" data-nav-item="">
6
+ <li data-href="/" class="is-active"><a href="/" class="tab-nav-link" data-nav-item="">
7
7
  <!--v-if--> Home
8
8
  </a></li>
9
9
  <li data-href="/about" class=""><a href="/about" class="tab-nav-link" data-nav-item="">
@@ -1,5 +1,4 @@
1
1
  import { useResizeObserver, onClickOutside } from "@vueuse/core";
2
- import type { NavItemData } from "~/types/components";
3
2
 
4
3
  interface NavCollapseOptions {
5
4
  onResize?: () => void;
@@ -7,14 +6,13 @@ interface NavCollapseOptions {
7
6
  onMounted?: () => void;
8
7
  }
9
8
 
10
- export const useNavCollapse = (navItemData: NavItemData, stateKey: string, options: NavCollapseOptions = {}) => {
9
+ export const useNavCollapse = (stateKey: string, options: NavCollapseOptions = {}) => {
11
10
  const navRef = ref<HTMLElement | null>(null);
12
11
  const navListRef = ref<HTMLUListElement | null>(null);
13
12
 
14
13
  const isCollapsed = ref(false);
15
14
  const isLoaded = useState(stateKey, () => false);
16
15
  const isMenuOpen = ref(false);
17
- const isMounted = ref(false);
18
16
 
19
17
  // Stored natural width of the list — used when the list is not in the DOM
20
18
  let navListNaturalWidth = 0;
@@ -35,19 +33,17 @@ export const useNavCollapse = (navItemData: NavItemData, stateKey: string, optio
35
33
  isMenuOpen.value = false;
36
34
  };
37
35
 
38
- const navigationStore = useNavigationStore();
36
+ // useRoute() in Nuxt is SSR-aware — route.path is consistent on server and client,
37
+ // so isActiveItem can be used directly without a isMounted guard.
39
38
  const route = useRoute();
40
39
 
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;
40
+ const activeHref = computed(() => route.path);
41
+ const isActiveItem = (href?: string) => href === route.path;
45
42
 
46
43
  watch(
47
44
  () => route.path,
48
- (newPath) => {
45
+ () => {
49
46
  closeMenu();
50
- navigationStore.syncWithRoute(navItemData.main ?? [], newPath);
51
47
  options.onRouteChange?.();
52
48
  },
53
49
  { flush: "post" }
@@ -68,8 +64,6 @@ export const useNavCollapse = (navItemData: NavItemData, stateKey: string, optio
68
64
  checkOverflow();
69
65
  isLoaded.value = true;
70
66
  await router.isReady();
71
- navigationStore.initializeFromRoute(navItemData.main ?? [], route.path);
72
- isMounted.value = true;
73
67
  options.onMounted?.();
74
68
  });
75
69
 
@@ -79,12 +73,10 @@ export const useNavCollapse = (navItemData: NavItemData, stateKey: string, optio
79
73
  isCollapsed,
80
74
  isLoaded,
81
75
  isMenuOpen,
82
- isMounted,
83
76
  activeHref,
84
77
  checkOverflow,
85
78
  toggleMenu,
86
79
  closeMenu,
87
80
  isActiveItem,
88
- navigationStore,
89
81
  };
90
82
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.10",
4
+ "version": "9.1.11",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",
@@ -1,113 +0,0 @@
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
- );