@gsa-tts/graymatter-ui 0.0.2 → 0.2.0
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/assets/images/ai-icons/chevron-up-down.svg +4 -0
- package/dist/graymatter-ui.js +4082 -0
- package/dist/graymatter-ui.umd.cjs +40 -0
- package/index.ts +3 -0
- package/package.json +10 -5
- package/src/components/Button.svelte +11 -23
- package/src/components/Button.webcomponent.svelte +44 -0
- package/src/components/DesktopHeader.svelte +101 -0
- package/src/components/DesktopHeader.webcomponent.svelte +17 -0
- package/src/components/DesktopSideNav.svelte +645 -0
- package/src/components/DesktopSideNav.webcomponent.svelte +19 -0
- package/src/components/GoogleTagManager.astro +16 -0
- package/src/components/Logo.svelte +8 -7
- package/src/components/Logo.webcomponent.svelte +26 -0
- package/src/components/MobileBottomNav.svelte +145 -0
- package/src/components/MobileBottomNav.webcomponent.svelte +14 -0
- package/src/components/MobileSideMenu.svelte +225 -0
- package/src/components/MobileSideMenu.webcomponent.svelte +11 -0
- package/src/components/MobileTopNav.svelte +235 -0
- package/src/components/MobileTopNav.webcomponent.svelte +14 -0
- package/src/components/NavigationInitializer.svelte +19 -0
- package/src/components/icons/ApiIcon.svelte +49 -0
- package/src/components/icons/ChatIcon.svelte +27 -0
- package/src/components/icons/CloseIcon.svelte +18 -0
- package/src/components/icons/ConsoleIcon.svelte +25 -0
- package/src/components/icons/DiscoverIcon.svelte +30 -0
- package/src/components/icons/MenuIcon.svelte +38 -0
- package/src/components/icons/index.ts +6 -0
- package/src/components/index.ts +9 -0
- package/src/index.ts +16 -0
- package/src/layouts/AppLayout.astro +1 -1
- package/src/layouts/BaseLayout.astro +4 -0
- package/src/layouts/GlobalAppLayout.astro +594 -0
- package/src/stores/navigationStore.ts +138 -0
- package/src/stores/subNavReadyStore.ts +3 -0
- package/src/styles/base/global.css +8 -1
- package/src/styles/base/typography.css +15 -14
- package/src/styles/components/globalAppLayout.css +73 -0
- package/src/styles/components/globalAppNav.css +95 -0
- package/src/utils/getAppUrls.ts +29 -0
- package/src/utils/index.ts +1 -0
- package/assets/images/gsai-logo-black.svg +0 -18
- package/assets/images/gsai-logo-white.svg +0 -18
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
---
|
|
2
|
+
import BaseLayout from '@gsa-tts/graymatter-ui/layouts/BaseLayout.astro';
|
|
3
|
+
import '../styles/base/global.css';
|
|
4
|
+
import { getPageTitle } from '@gsa-tts/graymatter-ui/helpers';
|
|
5
|
+
import UsaBanner from '../components/UsaBanner.astro';
|
|
6
|
+
import DesktopSideNav from '../components/DesktopSideNav.svelte';
|
|
7
|
+
import DesktopHeader from '../components/DesktopHeader.svelte';
|
|
8
|
+
import MobileTopNav from '../components/MobileTopNav.svelte';
|
|
9
|
+
import MobileBottomNav from '../components/MobileBottomNav.svelte';
|
|
10
|
+
import MobileSideMenu from '../components/MobileSideMenu.svelte';
|
|
11
|
+
import Logo from '../components/Logo.svelte';
|
|
12
|
+
|
|
13
|
+
const { componentOptions = {}, ssrSelectedItem, gtmID } = Astro.props;
|
|
14
|
+
|
|
15
|
+
const title = getPageTitle(Astro);
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<BaseLayout title={title} gtmID={gtmID}>
|
|
19
|
+
<div class="app">
|
|
20
|
+
<div class="banner">
|
|
21
|
+
<UsaBanner isInverted={componentOptions.usaBanner?.theme === 'inverse'} />
|
|
22
|
+
</div>
|
|
23
|
+
<!-- Desktop Navigation -->
|
|
24
|
+
<div class="layout-container">
|
|
25
|
+
<nav aria-label="Main navigation">
|
|
26
|
+
<DesktopSideNav client:load ssrSelectedItem={ssrSelectedItem}>
|
|
27
|
+
<slot name="sub-nav" />
|
|
28
|
+
</DesktopSideNav>
|
|
29
|
+
</nav>
|
|
30
|
+
<div class="main-container">
|
|
31
|
+
<header>
|
|
32
|
+
<DesktopHeader client:load />
|
|
33
|
+
</header>
|
|
34
|
+
<main id="main-content">
|
|
35
|
+
<slot />
|
|
36
|
+
</main>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- Mobile Navigation -->
|
|
41
|
+
<nav aria-label="Mobile navigation">
|
|
42
|
+
<MobileTopNav client:load />
|
|
43
|
+
<MobileSideMenu client:load>
|
|
44
|
+
<slot name="sub-nav" />
|
|
45
|
+
</MobileSideMenu>
|
|
46
|
+
<MobileBottomNav client:load />
|
|
47
|
+
</nav>
|
|
48
|
+
|
|
49
|
+
<!-- Fixed Logo (Desktop only) -->
|
|
50
|
+
<div class="logo-fixed-container display-none tablet:display-flex">
|
|
51
|
+
<Logo width={75} height={22} />
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<!-- Mobile Main Content -->
|
|
55
|
+
<main id="main-content-mobile" class="main-content-mobile">
|
|
56
|
+
<slot />
|
|
57
|
+
</main>
|
|
58
|
+
</div>
|
|
59
|
+
</BaseLayout>
|
|
60
|
+
|
|
61
|
+
<script is:inline>
|
|
62
|
+
if (typeof window !== 'undefined') {
|
|
63
|
+
// Handle URL hash scrolling immediately to prevent initial render at top
|
|
64
|
+
if (window.location.hash) {
|
|
65
|
+
const anchor = window.location.hash.substring(1);
|
|
66
|
+
|
|
67
|
+
// Set a flag to prevent SubNavMenu from also scrolling
|
|
68
|
+
sessionStorage.setItem('globalLayoutScrolling', 'true');
|
|
69
|
+
|
|
70
|
+
// Use requestAnimationFrame to scroll as early as possible
|
|
71
|
+
requestAnimationFrame(() => {
|
|
72
|
+
if (window.innerWidth <= 640) {
|
|
73
|
+
// Mobile: scroll in mobile content container
|
|
74
|
+
const mobileContent = document.getElementById('main-content-mobile');
|
|
75
|
+
if (mobileContent) {
|
|
76
|
+
const mobileAnchor = mobileContent.querySelector(
|
|
77
|
+
`[id="${anchor}"]`
|
|
78
|
+
);
|
|
79
|
+
if (mobileAnchor) {
|
|
80
|
+
const rect = mobileAnchor.getBoundingClientRect();
|
|
81
|
+
const mobileRect = mobileContent.getBoundingClientRect();
|
|
82
|
+
const scrollTop =
|
|
83
|
+
mobileContent.scrollTop + rect.top - mobileRect.top;
|
|
84
|
+
console.log(
|
|
85
|
+
'[GlobalAppLayout] Immediate mobile scroll to:',
|
|
86
|
+
scrollTop
|
|
87
|
+
);
|
|
88
|
+
mobileContent.scrollTo({ top: scrollTop, behavior: 'auto' });
|
|
89
|
+
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
let topLevelTitle = null;
|
|
92
|
+
if (typeof window !== 'undefined') {
|
|
93
|
+
topLevelTitle = sessionStorage.getItem(
|
|
94
|
+
'subNav-topLevelTitle'
|
|
95
|
+
);
|
|
96
|
+
if (topLevelTitle) {
|
|
97
|
+
try {
|
|
98
|
+
topLevelTitle = JSON.parse(topLevelTitle);
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
window.dispatchEvent(
|
|
103
|
+
new CustomEvent('sectionVisible', {
|
|
104
|
+
detail: {
|
|
105
|
+
title: topLevelTitle || mobileAnchor.textContent?.trim(),
|
|
106
|
+
id: mobileAnchor.id,
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
sessionStorage.removeItem('globalLayoutScrolling');
|
|
111
|
+
}, 100);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// Desktop: scroll in desktop content container
|
|
116
|
+
const mainContent = document.getElementById('main-content');
|
|
117
|
+
if (mainContent) {
|
|
118
|
+
const desktopAnchor = mainContent.querySelector(`[id="${anchor}"]`);
|
|
119
|
+
if (desktopAnchor) {
|
|
120
|
+
console.log(
|
|
121
|
+
'[GlobalAppLayout] Immediate desktop scroll for:',
|
|
122
|
+
anchor
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Add temporary scroll margin to account for header
|
|
127
|
+
const originalScrollMargin =
|
|
128
|
+
desktopAnchor.style.scrollMarginTop;
|
|
129
|
+
desktopAnchor.style.scrollMarginTop = '56px';
|
|
130
|
+
|
|
131
|
+
desktopAnchor.scrollIntoView({
|
|
132
|
+
behavior: 'auto',
|
|
133
|
+
block: 'start',
|
|
134
|
+
inline: 'nearest',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Restore original scroll margin after a delay
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
desktopAnchor.style.scrollMarginTop = originalScrollMargin;
|
|
140
|
+
}, 1000);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
// Fallback to manual calculation
|
|
143
|
+
const headerHeight = 56;
|
|
144
|
+
const rect = desktopAnchor.getBoundingClientRect();
|
|
145
|
+
const scrollTop = window.scrollY + rect.top - headerHeight;
|
|
146
|
+
const finalScrollTop = Math.max(0, scrollTop);
|
|
147
|
+
window.scrollTo({ top: finalScrollTop, behavior: 'auto' });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
let topLevelTitle = null;
|
|
152
|
+
if (typeof window !== 'undefined') {
|
|
153
|
+
topLevelTitle = sessionStorage.getItem(
|
|
154
|
+
'subNav-topLevelTitle'
|
|
155
|
+
);
|
|
156
|
+
if (topLevelTitle) {
|
|
157
|
+
try {
|
|
158
|
+
topLevelTitle = JSON.parse(topLevelTitle);
|
|
159
|
+
} catch {}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
window.dispatchEvent(
|
|
163
|
+
new CustomEvent('sectionVisible', {
|
|
164
|
+
detail: {
|
|
165
|
+
title: topLevelTitle || desktopAnchor.textContent?.trim(),
|
|
166
|
+
id: desktopAnchor.id,
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
sessionStorage.removeItem('globalLayoutScrolling');
|
|
171
|
+
}, 100);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
179
|
+
// Check navigation flag first, before any components mount
|
|
180
|
+
const isMainNavNavigation =
|
|
181
|
+
sessionStorage.getItem('navigatingToMainNav') === 'true';
|
|
182
|
+
console.log(
|
|
183
|
+
'[GlobalAppLayout] DOMContentLoaded - navigation flag:',
|
|
184
|
+
isMainNavNavigation
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (isMainNavNavigation) {
|
|
188
|
+
// Clear the flag immediately
|
|
189
|
+
sessionStorage.removeItem('navigatingToMainNav');
|
|
190
|
+
console.log(
|
|
191
|
+
'[GlobalAppLayout] Cleared navigation flag for main nav navigation'
|
|
192
|
+
);
|
|
193
|
+
// Don't clear header state here - let navigation components handle it
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Handle URL hash scrolling (fallback for cases where immediate scroll didn't work)
|
|
197
|
+
if (window.location.hash) {
|
|
198
|
+
const anchor = window.location.hash.substring(1);
|
|
199
|
+
console.log(
|
|
200
|
+
'[GlobalAppLayout] DOMContentLoaded fallback hash scroll for:',
|
|
201
|
+
anchor
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Only proceed if we haven't already scrolled
|
|
205
|
+
if (sessionStorage.getItem('globalLayoutScrolling') !== 'true') {
|
|
206
|
+
sessionStorage.setItem('globalLayoutScrolling', 'true');
|
|
207
|
+
|
|
208
|
+
if (window.innerWidth <= 640) {
|
|
209
|
+
// Mobile: scroll in mobile content container
|
|
210
|
+
const mobileContent = document.getElementById(
|
|
211
|
+
'main-content-mobile'
|
|
212
|
+
);
|
|
213
|
+
if (mobileContent) {
|
|
214
|
+
const mobileAnchor = mobileContent.querySelector(
|
|
215
|
+
`[id="${anchor}"]`
|
|
216
|
+
);
|
|
217
|
+
if (mobileAnchor) {
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
const rect = mobileAnchor.getBoundingClientRect();
|
|
220
|
+
const mobileRect = mobileContent.getBoundingClientRect();
|
|
221
|
+
const scrollTop =
|
|
222
|
+
mobileContent.scrollTop + rect.top - mobileRect.top;
|
|
223
|
+
console.log(
|
|
224
|
+
'[GlobalAppLayout] Fallback mobile scroll to:',
|
|
225
|
+
scrollTop
|
|
226
|
+
);
|
|
227
|
+
mobileContent.scrollTo({ top: scrollTop, behavior: 'auto' });
|
|
228
|
+
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
let topLevelTitle = null;
|
|
231
|
+
if (typeof window !== 'undefined') {
|
|
232
|
+
topLevelTitle = sessionStorage.getItem(
|
|
233
|
+
'subNav-topLevelTitle'
|
|
234
|
+
);
|
|
235
|
+
if (topLevelTitle) {
|
|
236
|
+
try {
|
|
237
|
+
topLevelTitle = JSON.parse(topLevelTitle);
|
|
238
|
+
} catch {}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
window.dispatchEvent(
|
|
242
|
+
new CustomEvent('sectionVisible', {
|
|
243
|
+
detail: {
|
|
244
|
+
title:
|
|
245
|
+
topLevelTitle || mobileAnchor.textContent?.trim(),
|
|
246
|
+
id: mobileAnchor.id,
|
|
247
|
+
},
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
// Clear the flag after scrolling
|
|
251
|
+
if (typeof window !== 'undefined') {
|
|
252
|
+
sessionStorage.removeItem('globalLayoutScrolling');
|
|
253
|
+
}
|
|
254
|
+
}, 100);
|
|
255
|
+
}, 100);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
// Desktop: scroll in desktop content container
|
|
260
|
+
const mainContent = document.getElementById('main-content');
|
|
261
|
+
if (mainContent) {
|
|
262
|
+
const desktopAnchor = mainContent.querySelector(
|
|
263
|
+
`[id="${anchor}"]`
|
|
264
|
+
);
|
|
265
|
+
if (desktopAnchor) {
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
console.log(
|
|
268
|
+
'[GlobalAppLayout] Fallback desktop scroll for:',
|
|
269
|
+
anchor
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Try scrollIntoView first with a temporary offset
|
|
273
|
+
try {
|
|
274
|
+
// Add temporary scroll margin to account for header
|
|
275
|
+
const originalScrollMargin =
|
|
276
|
+
desktopAnchor.style.scrollMarginTop;
|
|
277
|
+
desktopAnchor.style.scrollMarginTop = '56px';
|
|
278
|
+
|
|
279
|
+
desktopAnchor.scrollIntoView({
|
|
280
|
+
behavior: 'auto',
|
|
281
|
+
block: 'start',
|
|
282
|
+
inline: 'nearest',
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
console.log(
|
|
286
|
+
'[GlobalAppLayout] Used fallback scrollIntoView for desktop'
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Restore original scroll margin after a delay
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
desktopAnchor.style.scrollMarginTop =
|
|
292
|
+
originalScrollMargin;
|
|
293
|
+
}, 1000);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.log(
|
|
296
|
+
'[GlobalAppLayout] Fallback scrollIntoView failed, using manual calculation:',
|
|
297
|
+
e
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// Fallback to manual calculation
|
|
301
|
+
const headerHeight = 56;
|
|
302
|
+
const rect = desktopAnchor.getBoundingClientRect();
|
|
303
|
+
const scrollTop = window.scrollY + rect.top - headerHeight;
|
|
304
|
+
console.log(
|
|
305
|
+
'[GlobalAppLayout] Fallback desktop scroll calculation:',
|
|
306
|
+
{
|
|
307
|
+
anchor,
|
|
308
|
+
rectTop: rect.top,
|
|
309
|
+
windowScrollY: window.scrollY,
|
|
310
|
+
headerHeight,
|
|
311
|
+
scrollTop,
|
|
312
|
+
elementText: desktopAnchor.textContent?.trim(),
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Ensure scrollTop is not negative
|
|
317
|
+
const finalScrollTop = Math.max(0, scrollTop);
|
|
318
|
+
console.log(
|
|
319
|
+
'[GlobalAppLayout] Fallback final scroll position:',
|
|
320
|
+
finalScrollTop
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
window.scrollTo({ top: finalScrollTop, behavior: 'auto' });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
setTimeout(() => {
|
|
327
|
+
let topLevelTitle = null;
|
|
328
|
+
if (typeof window !== 'undefined') {
|
|
329
|
+
topLevelTitle = sessionStorage.getItem(
|
|
330
|
+
'subNav-topLevelTitle'
|
|
331
|
+
);
|
|
332
|
+
if (topLevelTitle) {
|
|
333
|
+
try {
|
|
334
|
+
topLevelTitle = JSON.parse(topLevelTitle);
|
|
335
|
+
} catch {}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
window.dispatchEvent(
|
|
339
|
+
new CustomEvent('sectionVisible', {
|
|
340
|
+
detail: {
|
|
341
|
+
title:
|
|
342
|
+
topLevelTitle || desktopAnchor.textContent?.trim(),
|
|
343
|
+
id: desktopAnchor.id,
|
|
344
|
+
},
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
// Clear the flag after scrolling
|
|
348
|
+
if (typeof window !== 'undefined') {
|
|
349
|
+
sessionStorage.removeItem('globalLayoutScrolling');
|
|
350
|
+
}
|
|
351
|
+
}, 100);
|
|
352
|
+
}, 100);
|
|
353
|
+
} else {
|
|
354
|
+
console.log(
|
|
355
|
+
'[GlobalAppLayout] Fallback desktop anchor not found in main-content container:',
|
|
356
|
+
anchor
|
|
357
|
+
);
|
|
358
|
+
// Log all available anchors for debugging
|
|
359
|
+
const allAnchors = mainContent.querySelectorAll('[id]');
|
|
360
|
+
console.log(
|
|
361
|
+
'[GlobalAppLayout] Available anchors in main-content:',
|
|
362
|
+
Array.from(allAnchors).map(el => el.id)
|
|
363
|
+
);
|
|
364
|
+
// Clear the flag if anchor not found
|
|
365
|
+
if (typeof window !== 'undefined') {
|
|
366
|
+
sessionStorage.removeItem('globalLayoutScrolling');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
console.log(
|
|
371
|
+
'[GlobalAppLayout] Fallback desktop main-content container not found'
|
|
372
|
+
);
|
|
373
|
+
// Clear the flag if container not found
|
|
374
|
+
if (typeof window !== 'undefined') {
|
|
375
|
+
sessionStorage.removeItem('globalLayoutScrolling');
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Handle layout changes (mobile ↔ desktop)
|
|
385
|
+
if (typeof window !== 'undefined') {
|
|
386
|
+
let lastWidth = window.innerWidth;
|
|
387
|
+
let resizeTimeout = null;
|
|
388
|
+
|
|
389
|
+
window.addEventListener('resize', () => {
|
|
390
|
+
// Debounce resize events
|
|
391
|
+
if (resizeTimeout) {
|
|
392
|
+
clearTimeout(resizeTimeout);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
resizeTimeout = setTimeout(() => {
|
|
396
|
+
const currentWidth = window.innerWidth;
|
|
397
|
+
const wasMobile = lastWidth <= 640;
|
|
398
|
+
const isMobile = currentWidth <= 640;
|
|
399
|
+
|
|
400
|
+
console.log('[GlobalAppLayout] Resize detected:', {
|
|
401
|
+
lastWidth,
|
|
402
|
+
currentWidth,
|
|
403
|
+
wasMobile,
|
|
404
|
+
isMobile,
|
|
405
|
+
changed: wasMobile !== isMobile,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (wasMobile !== isMobile) {
|
|
409
|
+
console.log(
|
|
410
|
+
'[GlobalAppLayout] Layout changed from',
|
|
411
|
+
wasMobile ? 'mobile' : 'desktop',
|
|
412
|
+
'to',
|
|
413
|
+
isMobile ? 'mobile' : 'desktop'
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
// Handle anchor scrolling for layout changes
|
|
417
|
+
if (window.location.hash) {
|
|
418
|
+
const anchor = window.location.hash.substring(1);
|
|
419
|
+
console.log(
|
|
420
|
+
'[GlobalAppLayout] Layout change with URL hash:',
|
|
421
|
+
anchor
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// Wait for navigation state to update, then scroll
|
|
425
|
+
setTimeout(() => {
|
|
426
|
+
if (isMobile) {
|
|
427
|
+
// Mobile: scroll in mobile content container
|
|
428
|
+
const mobileContent = document.getElementById(
|
|
429
|
+
'main-content-mobile'
|
|
430
|
+
);
|
|
431
|
+
if (mobileContent) {
|
|
432
|
+
const mobileAnchor = mobileContent.querySelector(
|
|
433
|
+
`[id="${anchor}"]`
|
|
434
|
+
);
|
|
435
|
+
if (mobileAnchor) {
|
|
436
|
+
const rect = mobileAnchor.getBoundingClientRect();
|
|
437
|
+
const mobileRect = mobileContent.getBoundingClientRect();
|
|
438
|
+
const scrollTop =
|
|
439
|
+
mobileContent.scrollTop + rect.top - mobileRect.top;
|
|
440
|
+
console.log(
|
|
441
|
+
'[GlobalAppLayout] Scrolling mobile to anchor:',
|
|
442
|
+
scrollTop
|
|
443
|
+
);
|
|
444
|
+
mobileContent.scrollTo({
|
|
445
|
+
top: scrollTop,
|
|
446
|
+
behavior: 'auto',
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
setTimeout(() => {
|
|
450
|
+
let topLevelTitle = null;
|
|
451
|
+
if (typeof window !== 'undefined') {
|
|
452
|
+
topLevelTitle = sessionStorage.getItem(
|
|
453
|
+
'subNav-topLevelTitle'
|
|
454
|
+
);
|
|
455
|
+
if (topLevelTitle) {
|
|
456
|
+
try {
|
|
457
|
+
topLevelTitle = JSON.parse(topLevelTitle);
|
|
458
|
+
} catch {}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
window.dispatchEvent(
|
|
462
|
+
new CustomEvent('sectionVisible', {
|
|
463
|
+
detail: {
|
|
464
|
+
title:
|
|
465
|
+
topLevelTitle || mobileAnchor.textContent?.trim(),
|
|
466
|
+
id: mobileAnchor.id,
|
|
467
|
+
},
|
|
468
|
+
})
|
|
469
|
+
);
|
|
470
|
+
}, 500);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
// Desktop: scroll in desktop content container
|
|
475
|
+
const mainContent = document.getElementById('main-content');
|
|
476
|
+
if (mainContent) {
|
|
477
|
+
const desktopAnchor = mainContent.querySelector(
|
|
478
|
+
`[id="${anchor}"]`
|
|
479
|
+
);
|
|
480
|
+
if (desktopAnchor) {
|
|
481
|
+
console.log(
|
|
482
|
+
'[GlobalAppLayout] Desktop anchor found for layout change:',
|
|
483
|
+
{
|
|
484
|
+
anchor,
|
|
485
|
+
elementText: desktopAnchor.textContent?.trim(),
|
|
486
|
+
elementId: desktopAnchor.id,
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
// Try scrollIntoView first with a temporary offset
|
|
491
|
+
try {
|
|
492
|
+
// Add temporary scroll margin to account for header
|
|
493
|
+
const originalScrollMargin =
|
|
494
|
+
desktopAnchor.style.scrollMarginTop;
|
|
495
|
+
desktopAnchor.style.scrollMarginTop = '56px';
|
|
496
|
+
|
|
497
|
+
desktopAnchor.scrollIntoView({
|
|
498
|
+
behavior: 'auto',
|
|
499
|
+
block: 'start',
|
|
500
|
+
inline: 'nearest',
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
console.log(
|
|
504
|
+
'[GlobalAppLayout] Used scrollIntoView for desktop layout change with scroll margin'
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
// Restore original scroll margin after a delay
|
|
508
|
+
setTimeout(() => {
|
|
509
|
+
desktopAnchor.style.scrollMarginTop =
|
|
510
|
+
originalScrollMargin;
|
|
511
|
+
}, 1000);
|
|
512
|
+
} catch (e) {
|
|
513
|
+
console.log(
|
|
514
|
+
'[GlobalAppLayout] scrollIntoView failed for layout change, using manual calculation:',
|
|
515
|
+
e
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
// Fallback to manual calculation
|
|
519
|
+
const headerHeight = 56;
|
|
520
|
+
const rect = desktopAnchor.getBoundingClientRect();
|
|
521
|
+
const scrollTop =
|
|
522
|
+
window.scrollY + rect.top - headerHeight;
|
|
523
|
+
console.log(
|
|
524
|
+
'[GlobalAppLayout] Desktop scroll calculation for layout change:',
|
|
525
|
+
{
|
|
526
|
+
anchor,
|
|
527
|
+
rectTop: rect.top,
|
|
528
|
+
windowScrollY: window.scrollY,
|
|
529
|
+
headerHeight,
|
|
530
|
+
scrollTop,
|
|
531
|
+
elementText: desktopAnchor.textContent?.trim(),
|
|
532
|
+
}
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
// Ensure scrollTop is not negative
|
|
536
|
+
const finalScrollTop = Math.max(0, scrollTop);
|
|
537
|
+
console.log(
|
|
538
|
+
'[GlobalAppLayout] Final scroll position for layout change:',
|
|
539
|
+
finalScrollTop
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
window.scrollTo({
|
|
543
|
+
top: finalScrollTop,
|
|
544
|
+
behavior: 'auto',
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
setTimeout(() => {
|
|
549
|
+
let topLevelTitle = null;
|
|
550
|
+
if (typeof window !== 'undefined') {
|
|
551
|
+
topLevelTitle = sessionStorage.getItem(
|
|
552
|
+
'subNav-topLevelTitle'
|
|
553
|
+
);
|
|
554
|
+
if (topLevelTitle) {
|
|
555
|
+
try {
|
|
556
|
+
topLevelTitle = JSON.parse(topLevelTitle);
|
|
557
|
+
} catch {}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
window.dispatchEvent(
|
|
561
|
+
new CustomEvent('sectionVisible', {
|
|
562
|
+
detail: {
|
|
563
|
+
title:
|
|
564
|
+
topLevelTitle ||
|
|
565
|
+
desktopAnchor.textContent?.trim(),
|
|
566
|
+
id: desktopAnchor.id,
|
|
567
|
+
},
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
}, 500);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}, 350); // Wait for navigation state to settle
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Dispatch custom event for layout transition
|
|
578
|
+
window.dispatchEvent(
|
|
579
|
+
new CustomEvent('layoutTransition', {
|
|
580
|
+
detail: { isMobile },
|
|
581
|
+
})
|
|
582
|
+
);
|
|
583
|
+
console.log(
|
|
584
|
+
'[GlobalAppLayout] Dispatched layoutTransition event for',
|
|
585
|
+
isMobile ? 'mobile' : 'desktop',
|
|
586
|
+
'layout'
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
lastWidth = currentWidth;
|
|
591
|
+
}, 250); // Debounce delay
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
</script>
|