@gsa-tts/graymatter-ui 0.4.17 → 0.4.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsa-tts/graymatter-ui",
3
- "version": "0.4.17",
3
+ "version": "0.4.19",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -187,6 +187,17 @@
187
187
  let currentSelectedId = $state<string | null>(null);
188
188
  let hydrated = $state(false);
189
189
  let pageLoadComplete = $state(false);
190
+ let scrollTimeoutId: ReturnType<typeof setTimeout> | null = null;
191
+
192
+ function queueScrollToAnchor(hash: string, delay: number) {
193
+ if (scrollTimeoutId) {
194
+ clearTimeout(scrollTimeoutId);
195
+ }
196
+ scrollTimeoutId = setTimeout(() => {
197
+ scrollTimeoutId = null;
198
+ scrollToAnchorWithOffset(hash);
199
+ }, delay);
200
+ }
190
201
 
191
202
  const effectiveSelectedId = $derived(() => {
192
203
  if (!hydrated || !data) return null;
@@ -296,13 +307,20 @@
296
307
  if (targetItem && targetItem.href && targetItem.href.includes('#')) {
297
308
  const hash = targetItem.href.split('#')[1];
298
309
  if (hash) {
299
- setTimeout(() => scrollToAnchorWithOffset(hash), 100);
310
+ queueScrollToAnchor(hash, 100);
300
311
  }
301
312
  }
302
313
 
303
314
  // Mark page load as complete
304
315
  pageLoadComplete = true;
305
316
  subNavReady.set(true);
317
+
318
+ return () => {
319
+ if (scrollTimeoutId) {
320
+ clearTimeout(scrollTimeoutId);
321
+ scrollTimeoutId = null;
322
+ }
323
+ };
306
324
  });
307
325
 
308
326
  function handleItemClick(event: Event, item: SubNavItem) {
@@ -332,9 +350,7 @@
332
350
  window.location.hash = hash;
333
351
  if (window.innerWidth > 640) {
334
352
  // Desktop: use delay for consistent timing
335
- setTimeout(() => {
336
- scrollToAnchorWithOffset(hash);
337
- }, 350);
353
+ queueScrollToAnchor(hash, 350);
338
354
  } else {
339
355
  // Mobile: immediate scroll without delay
340
356
  window.dispatchEvent(
@@ -382,6 +398,7 @@
382
398
 
383
399
  // Utility: Scroll to anchor with header offset
384
400
  function scrollToAnchorWithOffset(anchorId: string) {
401
+ if (typeof document === 'undefined') return;
385
402
  const mainContent = document.getElementById('main-content');
386
403
  if (mainContent) {
387
404
  const anchor = mainContent.querySelector(
@@ -1,9 +1,14 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * Controls which nav item is selected on initial server-side render (SSR) or first paint.
4
- * - For the Discover page, set ssrSelectedItem="discover".
5
- * - For other pages, set ssrSelectedItem to the corresponding nav key (e.g., "api", "chat", "console").
6
- * - If not provided, the navigation will default to expanded/open on SSR.
3
+ * Initial selection for SSR/first paint.
4
+ * - Discover page: set ssrSelectedItem="discover".
5
+ * - Other pages: set ssrSelectedItem to the nav key (e.g., "api", "chat", "console").
6
+ * - Omit to default to expanded/open on SSR.
7
+ *
8
+ * controlledSelectedItem overrides the nav store after hydration.
9
+ * - Use for web component integrations where bundle/app stores can diverge.
10
+ * - In Svelte/SvelteKit apps, prefer NavigationInitializer and omit
11
+ * controlledSelectedItem to keep store-driven behavior.
7
12
  */
8
13
  import { onMount } from 'svelte';
9
14
  import {
@@ -23,6 +28,7 @@
23
28
 
24
29
  const {
25
30
  ssrSelectedItem = undefined,
31
+ controlledSelectedItem = undefined,
26
32
  onNavToggle = undefined,
27
33
  onNavItemClick = undefined,
28
34
  showAppIcons = true,
@@ -79,8 +85,16 @@
79
85
  return hydrated ? hydratedValue : ssrValue;
80
86
  }
81
87
 
88
+ const selectedForUi = $derived(() => controlledSelectedItem ?? $selectedItem);
89
+ const ssrSelectedForUi = $derived(
90
+ () => controlledSelectedItem ?? ssrSelectedItem
91
+ );
92
+ const ssrSelected = $derived(() => ssrSelectedForUi());
93
+ const ssrIsDiscover = $derived(() => ssrSelected() === 'discover');
94
+ const isMenuDisabledForUi = $derived(() => selectedForUi() === 'discover');
95
+
82
96
  function isNavSelected(key: NavigationItem) {
83
- return ssrOrHydrated($selectedItem === key, ssrSelectedItem === key);
97
+ return ssrOrHydrated(selectedForUi() === key, ssrSelectedForUi() === key);
84
98
  }
85
99
 
86
100
  function isNavHovered(key: string) {
@@ -88,22 +102,16 @@
88
102
  }
89
103
 
90
104
  const sideNavExpanded = $derived(() =>
91
- ssrOrHydrated(
92
- $isExpanded,
93
- ssrSelectedItem ? ssrSelectedItem !== 'discover' : true
94
- )
105
+ ssrOrHydrated($isExpanded, ssrSelected() ? !ssrIsDiscover() : true)
95
106
  );
96
107
  const menuBtnDisabled = $derived(() =>
97
108
  ssrOrHydrated(
98
- $isMenuButtonDisabled,
99
- ssrSelectedItem ? ssrSelectedItem === 'discover' : false
109
+ isMenuDisabledForUi(),
110
+ ssrSelected() ? ssrIsDiscover() : false
100
111
  )
101
112
  );
102
113
  const menuBtnOpen = $derived(() =>
103
- ssrOrHydrated(
104
- $isExpanded,
105
- ssrSelectedItem ? ssrSelectedItem !== 'discover' : true
106
- )
114
+ ssrOrHydrated($isExpanded, ssrSelected() ? !ssrIsDiscover() : true)
107
115
  );
108
116
  const menuBtnHovered = $derived(
109
117
  () => canHover && menuButtonHovered && !menuBtnDisabled()
@@ -138,7 +146,7 @@
138
146
  navigationStore.setExpanded(false);
139
147
  } else {
140
148
  // Switching to desktop: open navigation if on non-discover page
141
- const currentItem = $selectedItem;
149
+ const currentItem = selectedForUi();
142
150
 
143
151
  if (currentItem && currentItem !== 'discover') {
144
152
  navigationStore.setExpanded(true);
@@ -220,7 +228,7 @@
220
228
  }
221
229
 
222
230
  function toggleNav() {
223
- if ($isMenuButtonDisabled) return;
231
+ if (menuBtnDisabled()) return;
224
232
  navigationStore.toggleExpanded();
225
233
  onNavToggle?.({ expanded: $isExpanded });
226
234
  }
@@ -231,7 +239,9 @@
231
239
  sessionStorage.setItem('navigatingToMainNav', 'true');
232
240
  }
233
241
 
234
- navigationStore.setSelectedItem(item);
242
+ if (controlledSelectedItem === undefined) {
243
+ navigationStore.setSelectedItem(item);
244
+ }
235
245
 
236
246
  // Clear header state for main navigation items (including discover)
237
247
  if (item !== 'discover') {
@@ -10,6 +10,7 @@
10
10
  // Props for the web component
11
11
  let {
12
12
  ssrSelectedItem,
13
+ controlledSelectedItem,
13
14
  onNavToggle,
14
15
  onNavItemClick,
15
16
  showAppIcons = true,
@@ -25,6 +26,7 @@
25
26
 
26
27
  <DesktopSideNav
27
28
  {ssrSelectedItem}
29
+ {controlledSelectedItem}
28
30
  {onNavToggle}
29
31
  {onNavItemClick}
30
32
  {showAppIcons}
@@ -14,6 +14,7 @@
14
14
  chatUrl,
15
15
  consoleUrl,
16
16
  discoverUrl,
17
+ controlledSelectedItem = undefined,
17
18
  }: {
18
19
  onNavItemClick?: (data: {
19
20
  item: NavigationItem;
@@ -25,8 +26,11 @@
25
26
  chatUrl?: string;
26
27
  consoleUrl?: string;
27
28
  discoverUrl?: string;
29
+ controlledSelectedItem?: NavigationItem;
28
30
  } = $props();
29
31
 
32
+ const selectedForUi = $derived(() => controlledSelectedItem ?? $selectedItem);
33
+
30
34
  const allNavItems = [
31
35
  {
32
36
  id: 'discover',
@@ -55,7 +59,9 @@
55
59
  sessionStorage.setItem('navigatingToMainNav', 'true');
56
60
  }
57
61
 
58
- navigationStore.setSelectedItem(item);
62
+ if (controlledSelectedItem === undefined) {
63
+ navigationStore.setSelectedItem(item);
64
+ }
59
65
 
60
66
  // Clear header state for main navigation items (including discover)
61
67
  if (item !== 'discover') {
@@ -130,12 +136,12 @@
130
136
  <a
131
137
  href={navItem.href}
132
138
  class={`nav-item ${navItem.id}-item`}
133
- class:active={$selectedItem === navItem.id}
139
+ class:active={selectedForUi() === navItem.id}
134
140
  onclick={() => handleNavItemClick(navItem.id)}
135
141
  part={`${navItem.id}-item`}
136
142
  >
137
143
  <div class="icon">
138
- <navItem.icon isSelected={$selectedItem === navItem.id} />
144
+ <navItem.icon isSelected={selectedForUi() === navItem.id} />
139
145
  </div>
140
146
  <span class="label">{navItem.label}</span>
141
147
  </a>
@@ -16,6 +16,7 @@
16
16
  chatUrl,
17
17
  consoleUrl,
18
18
  discoverUrl,
19
+ controlledSelectedItem,
19
20
  } = $props();
20
21
  </script>
21
22
 
@@ -27,4 +28,5 @@
27
28
  {chatUrl}
28
29
  {consoleUrl}
29
30
  {discoverUrl}
31
+ {controlledSelectedItem}
30
32
  />