@gsa-tts/graymatter-ui 0.4.16 → 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/dist/graymatter-ui.js +1811 -1774
- package/dist/graymatter-ui.umd.cjs +6 -6
- package/package.json +1 -1
- package/src/components/ApiDocSubNavMenu.svelte +31 -8
- package/src/components/DesktopSideNav.svelte +28 -18
- package/src/components/DesktopSideNav.webcomponent.svelte +2 -0
- package/src/components/MobileBottomNav.svelte +9 -3
- package/src/components/MobileBottomNav.webcomponent.svelte +2 -0
- package/src/utils/generateApiNavData.ts +3 -2
package/package.json
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
import ApiDocSubNavList from './ApiDocSubNavList.svelte';
|
|
6
6
|
|
|
7
7
|
const { data = null } = $props();
|
|
8
|
+
// TODO: remove legacy /api/documentation path once all consuming apps migrate
|
|
9
|
+
const API_DOC_PATHS = ['/api-documentation', '/api/documentation'];
|
|
10
|
+
|
|
11
|
+
function isApiDocumentationPath(path: string | null | undefined) {
|
|
12
|
+
if (!path) return false;
|
|
13
|
+
return API_DOC_PATHS.some(apiPath => path.includes(apiPath));
|
|
14
|
+
}
|
|
8
15
|
|
|
9
16
|
const sections = $derived(() =>
|
|
10
17
|
!Array.isArray(data) &&
|
|
@@ -180,6 +187,17 @@
|
|
|
180
187
|
let currentSelectedId = $state<string | null>(null);
|
|
181
188
|
let hydrated = $state(false);
|
|
182
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
|
+
}
|
|
183
201
|
|
|
184
202
|
const effectiveSelectedId = $derived(() => {
|
|
185
203
|
if (!hydrated || !data) return null;
|
|
@@ -232,8 +250,7 @@
|
|
|
232
250
|
// Only do this if we're on the API documentation page (where this component exists)
|
|
233
251
|
if (data && effectiveSelectedId() && typeof window !== 'undefined') {
|
|
234
252
|
const currentPath = window.location.pathname;
|
|
235
|
-
const isApiDocumentationPage =
|
|
236
|
-
currentPath.includes('/api/documentation');
|
|
253
|
+
const isApiDocumentationPage = isApiDocumentationPath(currentPath);
|
|
237
254
|
|
|
238
255
|
if (isApiDocumentationPage) {
|
|
239
256
|
const restoredItem = findItemById(data, effectiveSelectedId()!);
|
|
@@ -259,13 +276,13 @@
|
|
|
259
276
|
// Check if we're navigating TO the documentation page (vs refreshing while on it)
|
|
260
277
|
if (typeof window !== 'undefined') {
|
|
261
278
|
const currentPath = window.location.pathname;
|
|
262
|
-
const isDocumentationPage = currentPath
|
|
279
|
+
const isDocumentationPage = isApiDocumentationPath(currentPath);
|
|
263
280
|
|
|
264
281
|
// If we're on the documentation page, check if we came from a different page
|
|
265
282
|
if (isDocumentationPage) {
|
|
266
283
|
const referrer = document.referrer;
|
|
267
284
|
const isFromDifferentPage =
|
|
268
|
-
referrer && !referrer
|
|
285
|
+
referrer && !isApiDocumentationPath(referrer);
|
|
269
286
|
|
|
270
287
|
// If we came from a different page, clear sessionStorage to reset to initial values
|
|
271
288
|
if (isFromDifferentPage) {
|
|
@@ -290,13 +307,20 @@
|
|
|
290
307
|
if (targetItem && targetItem.href && targetItem.href.includes('#')) {
|
|
291
308
|
const hash = targetItem.href.split('#')[1];
|
|
292
309
|
if (hash) {
|
|
293
|
-
|
|
310
|
+
queueScrollToAnchor(hash, 100);
|
|
294
311
|
}
|
|
295
312
|
}
|
|
296
313
|
|
|
297
314
|
// Mark page load as complete
|
|
298
315
|
pageLoadComplete = true;
|
|
299
316
|
subNavReady.set(true);
|
|
317
|
+
|
|
318
|
+
return () => {
|
|
319
|
+
if (scrollTimeoutId) {
|
|
320
|
+
clearTimeout(scrollTimeoutId);
|
|
321
|
+
scrollTimeoutId = null;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
300
324
|
});
|
|
301
325
|
|
|
302
326
|
function handleItemClick(event: Event, item: SubNavItem) {
|
|
@@ -326,9 +350,7 @@
|
|
|
326
350
|
window.location.hash = hash;
|
|
327
351
|
if (window.innerWidth > 640) {
|
|
328
352
|
// Desktop: use delay for consistent timing
|
|
329
|
-
|
|
330
|
-
scrollToAnchorWithOffset(hash);
|
|
331
|
-
}, 350);
|
|
353
|
+
queueScrollToAnchor(hash, 350);
|
|
332
354
|
} else {
|
|
333
355
|
// Mobile: immediate scroll without delay
|
|
334
356
|
window.dispatchEvent(
|
|
@@ -376,6 +398,7 @@
|
|
|
376
398
|
|
|
377
399
|
// Utility: Scroll to anchor with header offset
|
|
378
400
|
function scrollToAnchorWithOffset(anchorId: string) {
|
|
401
|
+
if (typeof document === 'undefined') return;
|
|
379
402
|
const mainContent = document.getElementById('main-content');
|
|
380
403
|
if (mainContent) {
|
|
381
404
|
const anchor = mainContent.querySelector(
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* -
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
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(
|
|
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
|
-
|
|
99
|
-
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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={
|
|
144
|
+
<navItem.icon isSelected={selectedForUi() === navItem.id} />
|
|
139
145
|
</div>
|
|
140
146
|
<span class="label">{navItem.label}</span>
|
|
141
147
|
</a>
|
|
@@ -82,7 +82,8 @@ function headingsToNavItems(
|
|
|
82
82
|
|
|
83
83
|
// Generate navigation data dynamically from API documentation metadata
|
|
84
84
|
export function generateApiDocumentationNavData(
|
|
85
|
-
apiDocs: ApiDocMetadata[]
|
|
85
|
+
apiDocs: ApiDocMetadata[],
|
|
86
|
+
basePath = '/api-documentation'
|
|
86
87
|
): SubNavData {
|
|
87
88
|
// Sort by sortOrder
|
|
88
89
|
const sortedDocs = [...apiDocs].sort((a, b) => a.sortOrder - b.sortOrder);
|
|
@@ -97,7 +98,7 @@ export function generateApiDocumentationNavData(
|
|
|
97
98
|
return {
|
|
98
99
|
id: doc.slug,
|
|
99
100
|
title: doc.title,
|
|
100
|
-
href:
|
|
101
|
+
href: `${basePath}#${doc.slug}`,
|
|
101
102
|
children,
|
|
102
103
|
};
|
|
103
104
|
});
|