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.
@@ -16,7 +16,9 @@
16
16
  "Bash(npx tsc:*)",
17
17
  "Bash(npx vitest:*)",
18
18
  "Bash(git show:*)",
19
- "Edit(/.claude/skills/components/**)"
19
+ "Edit(/.claude/skills/components/**)",
20
+ "Bash(npx nuxi:*)",
21
+ "Bash(node -e \"const t = require\\('/Users/simoncornforth/websites/nuxt-components/node_modules/pinia-plugin-persistedstate'\\); console.log\\(Object.keys\\(t\\)\\)\")"
20
22
  ],
21
23
  "additionalDirectories": [
22
24
  "/Users/simoncornforth/websites/nuxt-components/app/components/01.atoms/content-wrappers/content-width",
@@ -17,7 +17,11 @@
17
17
  @mouseleave="resetHoverNavToActive"
18
18
  @mouseover="handleNavHover"
19
19
  >
20
- <li v-for="item in navItemData.main" :key="item.href" :class="item.cssName">
20
+ <li
21
+ v-for="item in navItemData.main"
22
+ :key="item.href"
23
+ :class="[item.cssName, { 'is-active': isActiveItem(item.href) }]"
24
+ >
21
25
  <NuxtLink :href="item.href" :external="item.isExternal || undefined" class="site-nav-link" data-nav-item>
22
26
  <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
23
27
  {{ item.text }}
@@ -70,7 +74,11 @@
70
74
  @mouseover="handlePanelHover"
71
75
  @mouseleave="resetHoverPanelToActive"
72
76
  >
73
- <li v-for="item in navItemData.main" :key="item.href" :class="item.cssName">
77
+ <li
78
+ v-for="item in navItemData.main"
79
+ :key="item.href"
80
+ :class="[item.cssName, { 'is-active': isActiveItem(item.href) }]"
81
+ >
74
82
  <NuxtLink
75
83
  :href="item.href"
76
84
  :external="item.isExternal || undefined"
@@ -92,7 +100,6 @@
92
100
  </template>
93
101
 
94
102
  <script setup lang="ts">
95
- import { useResizeObserver, onClickOutside } from "@vueuse/core";
96
103
  import type { NavItemData } from "~/types/components";
97
104
 
98
105
  interface Props {
@@ -106,35 +113,6 @@ const props = withDefaults(defineProps<Props>(), {
106
113
  styleClassPassthrough: () => [],
107
114
  });
108
115
 
109
- const navRef = ref<HTMLElement | null>(null);
110
- const navListRef = ref<HTMLUListElement | null>(null);
111
-
112
- const isCollapsed = ref(false);
113
- const isLoaded = useState("site-nav-loaded", () => false);
114
- const isMenuOpen = ref(false);
115
-
116
- // Stored natural width of the list — used when the list is not in the DOM
117
- let navListNaturalWidth = 0;
118
-
119
- const checkOverflow = () => {
120
- if (!navRef.value) return;
121
-
122
- // Measure and store the list width whenever it's in the DOM
123
- if (navListRef.value) {
124
- navListNaturalWidth = navListRef.value.scrollWidth;
125
- }
126
-
127
- isCollapsed.value = navListNaturalWidth > navRef.value.clientWidth;
128
- };
129
-
130
- const toggleMenu = () => {
131
- isMenuOpen.value = !isMenuOpen.value;
132
- };
133
-
134
- const closeMenu = () => {
135
- isMenuOpen.value = false;
136
- };
137
-
138
116
  // ─── Nav decorators (active / hover indicators) ─────────────────────────────
139
117
 
140
118
  const NAV_DECORATOR_DURATION = 200;
@@ -200,6 +178,13 @@ const moveNavHoveredIndicator = () => {
200
178
  const handleNavLinkClick = (event: MouseEvent) => {
201
179
  const target = (event.target as HTMLElement).closest<HTMLElement>("[data-nav-item]");
202
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
+
203
188
  currentActiveNavLink = target;
204
189
  currentHoveredNavLink = target;
205
190
  previousHoveredNavLink = target;
@@ -230,10 +215,7 @@ const initNavDecorators = () => {
230
215
  navSnapTimer = null;
231
216
  }
232
217
 
233
- const activeLink =
234
- links.find((el) => el.classList.contains("router-link-exact-active")) ??
235
- links.find((el) => el.classList.contains("router-link-active")) ??
236
- links[0];
218
+ const activeLink = links.find((el) => el.getAttribute("href") === activeHref.value) ?? links[0];
237
219
  if (!activeLink) return;
238
220
 
239
221
  currentActiveNavLink = activeLink;
@@ -305,6 +287,13 @@ const movePanelHoveredIndicator = () => {
305
287
  const handlePanelLinkClick = (event: MouseEvent) => {
306
288
  const target = (event.target as HTMLElement).closest<HTMLElement>("[data-panel-nav-item]");
307
289
  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
+
308
297
  currentActivePanelLink = target;
309
298
  currentHoveredPanelLink = target;
310
299
  previousHoveredPanelLink = target;
@@ -336,10 +325,7 @@ const initPanelDecorators = () => {
336
325
  panelSnapTimer = null;
337
326
  }
338
327
 
339
- const activeLink =
340
- links.find((el) => el.classList.contains("router-link-exact-active")) ??
341
- links.find((el) => el.classList.contains("router-link-active")) ??
342
- links[0];
328
+ const activeLink = links.find((el) => el.getAttribute("href") === activeHref.value) ?? links[0];
343
329
  if (!activeLink) return;
344
330
 
345
331
  currentActivePanelLink = activeLink;
@@ -352,52 +338,23 @@ const initPanelDecorators = () => {
352
338
 
353
339
  // ─────────────────────────────────────────────────────────────────────────────
354
340
 
355
- // Close panel on navigation and re-init decorators.
356
- // flush: 'post' ensures router-link-active classes are applied.
357
- // requestAnimationFrame defers the measurement until after browser layout,
358
- // preventing stale offsetLeft reads and racing with hover setTimeouts.
359
- const route = useRoute();
360
- watch(
361
- () => route.path,
362
- () => {
363
- closeMenu();
364
- requestAnimationFrame(() => {
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);
348
+ },
349
+ onRouteChange: () => {
350
+ requestAnimationFrame(() => {
351
+ initNavDecorators();
352
+ });
353
+ },
354
+ onMounted: () => {
365
355
  initNavDecorators();
366
- });
367
- },
368
- { flush: "post" }
369
- );
370
-
371
- useResizeObserver(navRef, () => {
372
- checkOverflow();
373
- if (!isCollapsed.value) closeMenu();
374
- setFinalNavActivePositions(true);
375
- setFinalNavHoveredPositions(true);
376
- setFinalPanelActivePositions(true);
377
- setFinalPanelHoveredPositions(true);
378
- });
379
-
380
- onClickOutside(navRef, closeMenu);
381
-
382
- const router = useRouter();
383
-
384
- onMounted(async () => {
385
- await nextTick();
386
- checkOverflow();
387
- isLoaded.value = true;
388
- await router.isReady();
389
- requestAnimationFrame(() => {
390
- initNavDecorators();
356
+ },
391
357
  });
392
- });
393
-
394
- watch(isCollapsed, async (collapsed) => {
395
- if (!collapsed) {
396
- cachedNavLinks = []; // nav <ul> was re-rendered — invalidate cache
397
- await nextTick();
398
- initNavDecorators();
399
- }
400
- });
401
358
 
402
359
  watch(isMenuOpen, async (open) => {
403
360
  if (open) {
@@ -583,10 +540,9 @@ watch(
583
540
  color: var(--_link-hover-color);
584
541
  outline: none;
585
542
  }
586
-
587
- &.router-link-exact-active {
588
- color: var(--_link-active-color);
589
- }
543
+ }
544
+ li.is-active .site-nav-link {
545
+ color: var(--_link-active-color);
590
546
  }
591
547
  }
592
548
 
@@ -776,10 +732,9 @@ watch(
776
732
  color: var(--_panel-link-hover-color);
777
733
  outline: none;
778
734
  }
779
-
780
- &.router-link-exact-active {
781
- color: var(--_panel-link-active-color);
782
- }
735
+ }
736
+ li.is-active .site-nav-panel-link {
737
+ color: var(--_panel-link-active-color);
783
738
  }
784
739
  }
785
740
  }