@gsa-tts/graymatter-ui 0.4.19 → 0.5.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.
@@ -7,14 +7,13 @@
7
7
  import Logo from './Logo.svelte';
8
8
  import MenuIcon from './icons/MenuIcon.svelte';
9
9
  import HelpIcon from './icons/HelpIcon.svelte';
10
- import { onMount } from 'svelte';
11
10
 
12
11
  let { profileMenuData, logoLinkUrl, logoLinkLabel, supplementalMenuData } =
13
12
  $props();
14
13
 
15
14
  import ProfileMenu from './ProfileMenu.svelte';
16
15
 
17
- const helpUrl = supplementalMenuData?.help?.url?.trim();
16
+ const helpUrl = $derived(() => supplementalMenuData?.help?.url?.trim());
18
17
 
19
18
  // Check if we're on the help page by comparing URLs (help is not a nav/subnav item)
20
19
  function getPathname(url: string, baseOrigin: string) {
@@ -26,7 +25,8 @@
26
25
  }
27
26
 
28
27
  function checkIsHelpPage(): boolean {
29
- if (!helpUrl || helpUrl === '#' || helpUrl.startsWith('#')) {
28
+ const url = helpUrl();
29
+ if (!url || url === '#' || url.startsWith('#')) {
30
30
  return false;
31
31
  }
32
32
  const location = (
@@ -35,33 +35,30 @@
35
35
  }
36
36
  ).location;
37
37
  if (location?.origin && location?.pathname) {
38
- const helpPath = getPathname(helpUrl, location.origin);
38
+ const helpPath = getPathname(url, location.origin);
39
39
  return helpPath !== null && location.pathname === helpPath;
40
40
  }
41
41
  return false;
42
42
  }
43
43
 
44
- const isHelpSelected = $derived(checkIsHelpPage());
44
+ const isHelpSelected = $derived(() => checkIsHelpPage());
45
45
 
46
- onMount(() => {
46
+ $effect(() => {
47
+ if (typeof window === 'undefined') return;
47
48
  // Listen for section visibility changes to update header
48
49
  const handleSectionVisible = (event: Event) => {
49
50
  const customEvent = event as CustomEvent;
50
51
  if (customEvent.detail && customEvent.detail.title) {
51
52
  selectedSubNavItemTitle.set(customEvent.detail.title);
52
- if (typeof window !== 'undefined') {
53
- sessionStorage.setItem(
54
- 'subNav-selectedTitle',
55
- JSON.stringify(customEvent.detail.title)
56
- );
57
- }
53
+ sessionStorage.setItem(
54
+ 'subNav-selectedTitle',
55
+ JSON.stringify(customEvent.detail.title)
56
+ );
58
57
  }
59
58
  };
60
59
 
61
60
  // Set default header based on first submenu item
62
61
  const setDefaultHeaderFromFirstSubmenu = () => {
63
- if (typeof window === 'undefined') return;
64
-
65
62
  const mainContent = document.getElementById('main-content');
66
63
  if (!mainContent) return;
67
64
 
@@ -75,12 +72,10 @@
75
72
  // Only set if no header is currently set
76
73
  if (!$selectedSubNavItemTitle) {
77
74
  selectedSubNavItemTitle.set(firstSectionTitle);
78
- if (typeof window !== 'undefined') {
79
- sessionStorage.setItem(
80
- 'subNav-selectedTitle',
81
- JSON.stringify(firstSectionTitle)
82
- );
83
- }
75
+ sessionStorage.setItem(
76
+ 'subNav-selectedTitle',
77
+ JSON.stringify(firstSectionTitle)
78
+ );
84
79
  }
85
80
  }
86
81
  };
@@ -98,7 +93,7 @@
98
93
  // Listen for navigation changes to update help button state
99
94
  const handleNavigationChange = () => {
100
95
  // Force reactivity update by accessing the derived value
101
- void isHelpSelected;
96
+ void isHelpSelected();
102
97
  };
103
98
 
104
99
  window.addEventListener('popstate', handleNavigationChange);
@@ -115,7 +110,7 @@
115
110
 
116
111
  function handleHelpClick(event: MouseEvent) {
117
112
  // If it's a hash link, prevent default to stay on same page
118
- if (helpUrl === '#' || helpUrl?.startsWith('#')) {
113
+ if (helpUrl() === '#' || helpUrl()?.startsWith('#')) {
119
114
  event.preventDefault();
120
115
  }
121
116
  }
@@ -145,18 +140,18 @@
145
140
  <span class="brand-text">{$selectedSubNavItemTitle || ''}</span>
146
141
  </div>
147
142
 
148
- {#if helpUrl}
143
+ {#if helpUrl()}
149
144
  <a
150
145
  class="help-button"
151
- class:selected={isHelpSelected}
152
- aria-current={isHelpSelected ? 'page' : undefined}
153
- href={helpUrl}
146
+ class:selected={isHelpSelected()}
147
+ aria-current={isHelpSelected() ? 'page' : undefined}
148
+ href={helpUrl()}
154
149
  title="Help"
155
150
  aria-label="Help"
156
151
  onclick={handleHelpClick}
157
152
  >
158
153
  <span class="icon-container">
159
- <HelpIcon isSelected={isHelpSelected} />
154
+ <HelpIcon isSelected={isHelpSelected()} />
160
155
  </span>
161
156
  </a>
162
157
  {/if}
@@ -1,14 +1,17 @@
1
1
  <script lang="ts">
2
- import { onMount } from 'svelte';
3
2
  import {
4
3
  navigationStore,
5
4
  type NavigationItem,
6
5
  } from '../stores/navigationStore.js';
7
6
 
8
- export let item: NavigationItem;
9
- export let autoExpand: boolean = false;
7
+ interface Props {
8
+ item: NavigationItem;
9
+ autoExpand?: boolean;
10
+ }
10
11
 
11
- onMount(() => {
12
+ let { item, autoExpand = false }: Props = $props();
13
+
14
+ $effect(() => {
12
15
  if (item) {
13
16
  navigationStore.setSelectedItem(item);
14
17
  // Auto-expand for non-discover pages on desktop
@@ -1,6 +1,4 @@
1
1
  <script lang="ts">
2
- import { onMount } from 'svelte';
3
-
4
2
  /**
5
3
  * ProfileMenu Component - Event-Driven Architecture with Shadow DOM Support
6
4
  *
@@ -71,14 +69,22 @@
71
69
  * Array of menu items to render in the profile menu.
72
70
  * Each item: { label: string, icon: string, action: string }
73
71
  */
74
- export let menuItems: Array<{ label: string; icon: string; action: string }> =
75
- [];
76
- export let userMeta: { initials: string };
77
- export let ariaLabel: string = 'Open profile menu';
78
- export let menuAlign: 'desktop' | 'mobile' = 'desktop';
72
+ interface Props {
73
+ menuItems?: Array<{ label: string; icon: string; action: string }>;
74
+ userMeta: { initials: string };
75
+ ariaLabel?: string;
76
+ menuAlign?: 'desktop' | 'mobile';
77
+ }
79
78
 
80
- let menuId = `profile-menu-${Math.random().toString(36).slice(2, 10)}`;
81
- let menuOpen = false;
79
+ let {
80
+ menuItems = [],
81
+ userMeta,
82
+ ariaLabel = 'Open profile menu',
83
+ menuAlign = 'desktop',
84
+ }: Props = $props();
85
+
86
+ const menuId = `profile-menu-${Math.random().toString(36).slice(2, 10)}`;
87
+ let menuOpen = $state(false);
82
88
  function toggleMenu() {
83
89
  menuOpen = !menuOpen;
84
90
  }
@@ -118,12 +124,14 @@
118
124
  }
119
125
  }
120
126
 
121
- let profileButton: HTMLButtonElement;
127
+ let profileButton = $state<HTMLButtonElement | null>(null);
122
128
 
123
129
  // For keyboard navigation, keep refs to all menu item buttons
124
- let menuItemButtons: HTMLButtonElement[] = [];
125
- let btns: (HTMLButtonElement | null)[] = [];
126
- $: menuItemButtons = btns.filter(Boolean) as HTMLButtonElement[];
130
+ let menuItemButtons = $state<HTMLButtonElement[]>([]);
131
+ let btns = $state<(HTMLButtonElement | null)[]>([]);
132
+ $effect(() => {
133
+ menuItemButtons = btns.filter(Boolean) as HTMLButtonElement[];
134
+ });
127
135
 
128
136
  function handleMenuKeydown(e: KeyboardEvent, idx: number) {
129
137
  if (e.key === 'ArrowDown') {
@@ -149,7 +157,8 @@
149
157
  }
150
158
  }
151
159
 
152
- onMount(() => {
160
+ $effect(() => {
161
+ if (typeof window === 'undefined') return;
153
162
  window.addEventListener('keydown', handleGlobalEscape, true);
154
163
  return () => {
155
164
  window.removeEventListener('keydown', handleGlobalEscape, true);
@@ -165,8 +174,8 @@
165
174
  aria-haspopup="menu"
166
175
  aria-expanded={menuOpen}
167
176
  aria-controls={menuId}
168
- on:click={toggleMenu}
169
- on:blur={handleBlur}
177
+ onclick={toggleMenu}
178
+ onblur={handleBlur}
170
179
  bind:this={profileButton}
171
180
  >
172
181
  <div class="user-profile-container">
@@ -189,12 +198,12 @@
189
198
  <button
190
199
  type="button"
191
200
  class="menu-item"
192
- on:click={() => {
201
+ onclick={() => {
193
202
  executeMenuAction(item.action);
194
203
  closeMenu();
195
204
  }}
196
205
  bind:this={btns[idx]}
197
- on:keydown={e => handleMenuKeydown(e, idx)}
206
+ onkeydown={e => handleMenuKeydown(e, idx)}
198
207
  role="menuitem"
199
208
  >
200
209
  {#if item.icon}
@@ -13,7 +13,7 @@
13
13
  widescreen: 'ai-maxw-widescreen',
14
14
  };
15
15
 
16
- const mappedSize = sizeMap[maxWidth] || sizeMap.none;
16
+ const mappedSize = $derived(() => sizeMap[maxWidth] || sizeMap.none);
17
17
  </script>
18
18
 
19
19
  <section
@@ -26,7 +26,7 @@
26
26
  class="usa-banner__header ai-banner__header"
27
27
  aria-label="USA banner"
28
28
  >
29
- <div class="usa-banner__inner {mappedSize}">
29
+ <div class="usa-banner__inner {mappedSize()}">
30
30
  <div class="grid-col-auto">
31
31
  <img
32
32
  aria-hidden="true"
@@ -54,7 +54,7 @@
54
54
  </div>
55
55
  </header>
56
56
  <div
57
- class="usa-banner__content usa-accordion__content ai-banner__content {mappedSize}"
57
+ class="usa-banner__content usa-accordion__content ai-banner__content {mappedSize()}"
58
58
  id="gov-banner-default"
59
59
  >
60
60
  <div class="grid-row grid-gap-lg">
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- export let isSelected = false;
2
+ let { isSelected = false } = $props();
3
3
  </script>
4
4
 
5
5
  <svg
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- export let isSelected = false;
2
+ let { isSelected = false } = $props();
3
3
  </script>
4
4
 
5
5
  <svg
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- export let isSelected = false;
2
+ let { isSelected = false } = $props();
3
3
  </script>
4
4
 
5
5
  <svg
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- export let isSelected = false;
2
+ let { isSelected = false } = $props();
3
3
  </script>
4
4
 
5
5
  <svg
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- export let isSelected = false;
2
+ let { isSelected = false } = $props();
3
3
  </script>
4
4
 
5
5
  <svg
@@ -1,7 +1,18 @@
1
1
  <script lang="ts">
2
- export let isDisabled: boolean = false;
3
- export let isHovered: boolean = false;
4
- export let isOpen: boolean = false;
2
+ interface Props {
3
+ isDisabled?: boolean;
4
+ isHovered?: boolean;
5
+ isOpen?: boolean;
6
+ [key: string]: unknown;
7
+ }
8
+
9
+ let {
10
+ isDisabled = false,
11
+ isHovered = false,
12
+ isOpen = false,
13
+ // eslint-disable-next-line svelte/valid-compile -- allow passthrough svg props.
14
+ ...restProps
15
+ }: Props = $props();
5
16
  </script>
6
17
 
7
18
  <svg
@@ -10,7 +21,7 @@
10
21
  height="24"
11
22
  viewBox="0 0 24 24"
12
23
  fill="none"
13
- {...$$restProps}
24
+ {...restProps}
14
25
  >
15
26
  {#if isDisabled}
16
27
  <!-- Disabled state -->