@utahdts/utah-design-system-header 1.2.1 → 1.3.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.
Files changed (96) hide show
  1. package/dist/index.d.ts +576 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/style.css +1 -1
  4. package/dist/utah-design-system-header.es.js +5 -5
  5. package/dist/utah-design-system-header.umd.js +4 -4
  6. package/package.json +5 -4
  7. package/src/index.js +11 -0
  8. package/src/js/enumerations/childrenMenuTypes.js +13 -0
  9. package/src/js/enumerations/domConstants.js +149 -0
  10. package/src/js/enumerations/environments.js +16 -0
  11. package/src/js/enumerations/events.js +14 -0
  12. package/src/js/enumerations/popupPlacement.js +32 -0
  13. package/src/js/enumerations/sizes.js +12 -0
  14. package/src/js/enumerations/utahIdUrls.js +10 -0
  15. package/src/js/lifecycle/globalEvents.js +81 -0
  16. package/src/js/lifecycle/hookupMobileActionItemKeyboarding.js +114 -0
  17. package/src/js/lifecycle/lifecycle.js +180 -0
  18. package/src/js/misc/checkForError.js +16 -0
  19. package/src/js/misc/findRecursive.js +39 -0
  20. package/src/js/misc/isString.js +8 -0
  21. package/src/js/misc/isTouchDevice.js +9 -0
  22. package/src/js/misc/jsDocTypes.js +235 -0
  23. package/src/js/misc/notNull.js +15 -0
  24. package/src/js/misc/popupFocusHandler.js +209 -0
  25. package/src/js/misc/renderDOMSingle.js +61 -0
  26. package/src/js/misc/showHideElement.js +15 -0
  27. package/src/js/misc/toHash.js +18 -0
  28. package/src/js/misc/uuidv4.js +8 -0
  29. package/src/js/misc/valueOrFunctionValue.js +13 -0
  30. package/src/js/renderables/_html/NewTabAccessibility.html +4 -0
  31. package/src/js/renderables/actionItems/ActionItems.js +29 -0
  32. package/src/js/renderables/actionItems/html/ActionItem.html +5 -0
  33. package/src/js/renderables/actionItems/html/ActionItemMenuContent.html +3 -0
  34. package/src/js/renderables/actionItems/html/ActionItemsWrapper.html +1 -0
  35. package/src/js/renderables/actionItems/html/BadgeWrapperHtml.html +4 -0
  36. package/src/js/renderables/actionItems/html/MobileActionItem.html +7 -0
  37. package/src/js/renderables/actionItems/renderActionItem.js +103 -0
  38. package/src/js/renderables/actionItems/renderActionItemBadge.js +50 -0
  39. package/src/js/renderables/actionItems/renderMobileActionItem.js +97 -0
  40. package/src/js/renderables/actionItems/renderMobileActionItems.js +83 -0
  41. package/src/js/renderables/citizenExperience/CitizenExperience.js +24 -0
  42. package/src/js/renderables/citizenExperience/html/CitizenExperienceWrapper.html +1 -0
  43. package/src/js/renderables/citizenExperience/html/CitizenExperienceWrapperMobile.html +1 -0
  44. package/src/js/renderables/footer/html/Footer.html +41 -0
  45. package/src/js/renderables/footer/renderFooter.js +111 -0
  46. package/src/js/renderables/headerWrapper/HeaderWrapper.js +46 -0
  47. package/src/js/renderables/headerWrapper/html/HeaderLogoWrapper.html +1 -0
  48. package/src/js/renderables/headerWrapper/html/HeaderWrapper.html +1 -0
  49. package/src/js/renderables/headerWrapper/html/VerticalLine.html +1 -0
  50. package/src/js/renderables/icons/html/AlertIcon.html +1 -0
  51. package/src/js/renderables/icons/html/ChevronIcon.html +1 -0
  52. package/src/js/renderables/icons/html/ExternalLinkIcon.html +7 -0
  53. package/src/js/renderables/icons/html/GearIcon.html +1 -0
  54. package/src/js/renderables/icons/html/QuestionIcon.html +1 -0
  55. package/src/js/renderables/icons/html/WaffleIcon.html +1 -0
  56. package/src/js/renderables/logoTitle/LogoTitle.js +63 -0
  57. package/src/js/renderables/logoTitle/html/LogoTitleWrapper.html +4 -0
  58. package/src/js/renderables/logoTitle/html/LogoTitleWrapperLink.html +4 -0
  59. package/src/js/renderables/mainMenu/html/MainMenuItem.html +13 -0
  60. package/src/js/renderables/mainMenu/html/MainMenuWrapper.html +26 -0
  61. package/src/js/renderables/mainMenu/renderMainMenu.js +258 -0
  62. package/src/js/renderables/menu/html/MenuWithTitle.html +3 -0
  63. package/src/js/renderables/menu/renderMenuWithTitle.js +24 -0
  64. package/src/js/renderables/mobile/addMobileMenuContentItem.js +37 -0
  65. package/src/js/renderables/mobile/hookupHamburger.js +112 -0
  66. package/src/js/renderables/mobile/hookupUtahIdInMobileMenu.js +79 -0
  67. package/src/js/renderables/mobile/html/MobileMenuContentItemWrapper.html +2 -0
  68. package/src/js/renderables/mobile/html/MobileMenuWrapper.html +32 -0
  69. package/src/js/renderables/mobile/mobileMenuInteractionHandler.js +155 -0
  70. package/src/js/renderables/mobile/renderMobileMenuHomeMenu.js +15 -0
  71. package/src/js/renderables/mobile/util/getHamburgerElements.js +22 -0
  72. package/src/js/renderables/mobile/util/showHideHamburgerElements.js +32 -0
  73. package/src/js/renderables/popup/html/Popup.html +5 -0
  74. package/src/js/renderables/popup/renderPopup.js +41 -0
  75. package/src/js/renderables/popupMenu/html/PopupMenu.html +1 -0
  76. package/src/js/renderables/popupMenu/html/PopupMenuItem.html +12 -0
  77. package/src/js/renderables/popupMenu/renderPopupMenu.js +341 -0
  78. package/src/js/renderables/search/html/SearchModal.html +25 -0
  79. package/src/js/renderables/search/showSearchModal.js +132 -0
  80. package/src/js/renderables/tooltip/hookupTooltip.js +82 -0
  81. package/src/js/renderables/tooltip/html/Tooltip.html +5 -0
  82. package/src/js/renderables/utahId/UtahId.js +227 -0
  83. package/src/js/renderables/utahId/html/UtahIdButton.html +1 -0
  84. package/src/js/renderables/utahId/html/UtahIdWrapper.html +1 -0
  85. package/src/js/renderables/utahLogo/UtahLogo.js +48 -0
  86. package/src/js/renderables/utahLogo/html/UtahLogoLarge.html +17 -0
  87. package/src/js/renderables/utahLogo/html/UtahLogoMedium.html +17 -0
  88. package/src/js/renderables/utahLogo/html/UtahOfficialWebsiteHoverContent.html +4 -0
  89. package/src/js/renderables/utahLogo/html/UtahOfficialWebsitePopupContent.html +41 -0
  90. package/src/js/renderables/utahLogo/renderOfficialWebsite.js +139 -0
  91. package/src/js/settings/defaultSettings.js +31 -0
  92. package/src/js/settings/getUtahHeaderSettings.js +16 -0
  93. package/src/js/settings/settings.js +69 -0
  94. package/src/js/settings/settingsKeeper.js +47 -0
  95. package/src/js/utahId/httpRequest.js +42 -0
  96. package/src/js/utahId/utahIdData.js +165 -0
@@ -0,0 +1,63 @@
1
+ // @ts-check
2
+ // @ts-ignore
3
+ // eslint-disable-next-line import/no-unresolved
4
+ import LogoTitleWrapper from './html/LogoTitleWrapper.html?raw';
5
+ // @ts-ignore
6
+ // eslint-disable-next-line import/no-unresolved
7
+ import LogoTitleWrapperLink from './html/LogoTitleWrapperLink.html?raw';
8
+
9
+ import domConstants, { getCssClassSelector } from '../../enumerations/domConstants';
10
+ import renderDOMSingle from '../../misc/renderDOMSingle';
11
+ import valueOrFunctionValue from '../../misc/valueOrFunctionValue';
12
+ import getUtahHeaderSettings from '../../settings/getUtahHeaderSettings';
13
+
14
+ /**
15
+ * @returns {Element}
16
+ */
17
+ export default function LogoTitle() {
18
+ const logoTitleURL = getUtahHeaderSettings().titleURL;
19
+ const logoTitleWrapper = !logoTitleURL ? renderDOMSingle(LogoTitleWrapper) : renderDOMSingle(LogoTitleWrapperLink);
20
+ if (!logoTitleWrapper) {
21
+ throw new Error('LogoTitle: titleWrapper is null');
22
+ }
23
+ if (logoTitleURL) {
24
+ logoTitleWrapper.setAttribute('href', logoTitleURL);
25
+ }
26
+
27
+ // Render Logo image
28
+ const logoWrapper = logoTitleWrapper.querySelector(getCssClassSelector(domConstants.TITLE__LOGO));
29
+ if (!logoWrapper) {
30
+ throw new Error('LogoTitle: logoWrapper is null');
31
+ }
32
+
33
+ const settingsLogo = getUtahHeaderSettings().logo;
34
+ const settingsShowTitle = getUtahHeaderSettings().showTitle;
35
+ const settingsTitle = getUtahHeaderSettings().title;
36
+ if (settingsLogo) {
37
+ /** @type {HTMLCollection | Element} */
38
+ let settingsLogoElement;
39
+ if (settingsLogo.htmlString) {
40
+ settingsLogoElement = renderDOMSingle(valueOrFunctionValue(settingsLogo.htmlString));
41
+ } else if (settingsLogo.element) {
42
+ settingsLogoElement = valueOrFunctionValue(settingsLogo.element);
43
+ } else if (settingsLogo.imageUrl) {
44
+ settingsLogoElement = renderDOMSingle(`<img src=${valueOrFunctionValue(settingsLogo.imageUrl)} id="design-system-logo" />`);
45
+ } else {
46
+ throw new Error('LogoTitle: logo set but has no settings');
47
+ }
48
+ settingsLogoElement.setAttribute('role', 'presentation');
49
+ logoWrapper.appendChild(settingsLogoElement);
50
+ } else {
51
+ logoTitleWrapper.removeChild(logoWrapper);
52
+ }
53
+
54
+ // Render Title text
55
+ const title = document.createTextNode(settingsTitle);
56
+ const titleWrapper = logoTitleWrapper.querySelector(getCssClassSelector(domConstants.TITLE__TITLE));
57
+ titleWrapper?.appendChild(title);
58
+ if (!settingsShowTitle && settingsLogo) {
59
+ titleWrapper?.classList.add(domConstants.VISUALLY_HIDDEN);
60
+ }
61
+
62
+ return logoTitleWrapper;
63
+ }
@@ -0,0 +1,4 @@
1
+ <div class="utds-title-wrapper">
2
+ <div class="utds-title-wrapper__logo"></div>
3
+ <div class="utds-title-wrapper__title"></div>
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <a class="utds-title-wrapper">
2
+ <div class="utds-title-wrapper__logo"></div>
3
+ <div class="utds-title-wrapper__title"></div>
4
+ </a>
@@ -0,0 +1,13 @@
1
+ <li class="menu-item">
2
+ <span class="menu-item__title">
3
+ <button class="menu-item__button-title" type="button">
4
+ <span class="menu-item__link-title-span"></span>
5
+ <span class="menu-item__menu-arrow utds-icon-before-chevron-down" aria-hidden="true"></span>
6
+ </button>
7
+ <a class="menu-item__link-title">
8
+ <span class="menu-item__link-title-span"></span>
9
+ <span class="menu-item__menu-arrow utds-icon-before-chevron-down" aria-hidden="true"></span>
10
+ </a>
11
+ <span class="menu-chiclet"></span>
12
+ </span>
13
+ </li>
@@ -0,0 +1,26 @@
1
+ <div class="utah-design-system main-menu__outer">
2
+ <div class="main-menu__wrapper">
3
+ <nav class="horizontal-menu main-menu__nav">
4
+ <h2 class="main-menu__title visually-hidden"></h2>
5
+ <ul class="main-menu__menu-top"></ul>
6
+ </nav>
7
+ <button type="button" class="button icon-button icon-button--borderless main-menu__search">
8
+ <div>
9
+ <span class="utds-icon-before-search" aria-hidden="true"></span>
10
+ <span class="visually-hidden">search</span>
11
+ </div>
12
+ </button>
13
+ <div class="utds-header-mobile__vip-action-items--left">
14
+ </div>
15
+ <div class="utds-header-mobile__utah-id-wrapper">
16
+ </div>
17
+ <div class="utds-header-mobile__vip-action-items--right">
18
+ </div>
19
+ <button type="button" class="button icon-button icon-button--borderless main-menu__hamburger"
20
+ id="utds-main-menu__hamburger">
21
+ <div>
22
+ <span class="utds-icon-before-hamburger" id="utds-main-menu__hamburger-icon" aria-hidden="true"></span>
23
+ </div>
24
+ </button>
25
+ </div>
26
+ </div>
@@ -0,0 +1,258 @@
1
+ // @ts-check
2
+ // @ts-ignore
3
+ // eslint-disable-next-line import/no-unresolved
4
+ import MainMenuItem from './html/MainMenuItem.html?raw';
5
+ // @ts-ignore
6
+ // eslint-disable-next-line import/no-unresolved
7
+ import MainMenuWrapper from './html/MainMenuWrapper.html?raw';
8
+ // @ts-ignore
9
+ // eslint-disable-next-line import/no-unresolved
10
+ import NewTabAccessibility from '../_html/NewTabAccessibility.html?raw';
11
+
12
+ import childrenMenuTypes from '../../enumerations/childrenMenuTypes';
13
+ import domConstants, { getCssClassSelector } from '../../enumerations/domConstants';
14
+ import findRecursive from '../../misc/findRecursive';
15
+ import notNull from '../../misc/notNull';
16
+ import popupFocusHandler from '../../misc/popupFocusHandler';
17
+ import renderDOMSingle from '../../misc/renderDOMSingle';
18
+ import uuidv4 from '../../misc/uuidv4';
19
+ import getUtahHeaderSettings from '../../settings/getUtahHeaderSettings';
20
+ import renderPopupMenu from '../popupMenu/renderPopupMenu';
21
+ import showSearchModal from '../search/showSearchModal';
22
+ import hookupTooltip from '../tooltip/hookupTooltip';
23
+ import { renderUtahIdForMobile } from '../utahId/UtahId';
24
+
25
+ /**
26
+ * @typedef {import('../../misc/jsDocTypes').PopupMenu} PopupMenu
27
+ */
28
+
29
+ /**
30
+ * @returns {{mainMenuWrapper: HTMLElement, utahIdPopup: HTMLElement | null}}
31
+ */
32
+ export default function renderMainMenu() {
33
+ const settings = getUtahHeaderSettings();
34
+ /** @type {HTMLElement} */
35
+ const mainMenuWrapper = renderDOMSingle(MainMenuWrapper);
36
+
37
+ let mainMenuNav = mainMenuWrapper.querySelector(getCssClassSelector(domConstants.MAIN_MENU__NAV));
38
+ if (!mainMenuNav) {
39
+ throw new Error('renderMainMenu(): mainMenu not created');
40
+ }
41
+
42
+ const titleTag = mainMenuNav.querySelector(getCssClassSelector(domConstants.MAIN_MENU__TITLE));
43
+ if (!titleTag) {
44
+ throw new Error('renderMainMenu(): titleTag not found');
45
+ }
46
+
47
+ const mainMenuId = 'main-menu__nav';
48
+ mainMenuNav.setAttribute('aria-labelledby', mainMenuId);
49
+ titleTag.setAttribute('id', mainMenuId);
50
+ if (settings.mainMenu) {
51
+ titleTag.innerHTML = settings.mainMenu.title;
52
+ } else {
53
+ mainMenuNav.remove();
54
+ mainMenuNav = null;
55
+ }
56
+
57
+ if (settings.mainMenu) {
58
+ const mainMenuTop = mainMenuNav?.querySelector(getCssClassSelector(domConstants.MAIN_MENU__MENU_TOP));
59
+ if (!mainMenuTop) {
60
+ throw new Error('renderMainMenu(): mainMenuTop not found');
61
+ }
62
+
63
+ // render top level menu items with popups for their children
64
+ settings.mainMenu.menuItems.forEach((menuItem) => {
65
+ const mainMenuItem = renderDOMSingle(MainMenuItem);
66
+ mainMenuTop.appendChild(mainMenuItem);
67
+
68
+ const mainMenuItemTitle = notNull(
69
+ mainMenuItem.querySelector(getCssClassSelector(domConstants.MENU_ITEM__TITLE)),
70
+ `renderMainMenu(): sub menu title not found for ${menuItem.title}`
71
+ );
72
+
73
+ const mainMenuItemButtonTitle = notNull(
74
+ /** @type {HTMLElement} */(
75
+ mainMenuItemTitle.querySelector(getCssClassSelector(domConstants.MENU_ITEM__BUTTON_TITLE))
76
+ ),
77
+ `renderMainMenu(): button title not found for ${menuItem.title}`
78
+ );
79
+ mainMenuItemButtonTitle.setAttribute('id', `${domConstants.MENU_ITEM__BUTTON_TITLE}__${menuItem.title}-${uuidv4()}`);
80
+
81
+ const mainMenuItemLinkTitle = notNull(
82
+ /** @type {HTMLElement} */(
83
+ mainMenuItemTitle.querySelector(getCssClassSelector(domConstants.MENU_ITEM__LINK_TITLE))
84
+ ),
85
+ `renderMainMenu(): link title not found for ${menuItem.title}`
86
+ );
87
+ mainMenuItemLinkTitle.setAttribute('id', `${domConstants.MENU_ITEM__LINK_TITLE}__${menuItem.title}-${uuidv4()}`);
88
+
89
+ let menuItemTitleElement;
90
+ if (menuItem.actionFunctionUrl || menuItem.actionUrl) {
91
+ menuItemTitleElement = mainMenuItemLinkTitle;
92
+ mainMenuItemButtonTitle.remove();
93
+ } else if (menuItem.actionMenu || menuItem.actionFunction) {
94
+ menuItemTitleElement = mainMenuItemButtonTitle;
95
+ mainMenuItemLinkTitle.remove();
96
+ } else {
97
+ throw new Error(`renderMainMenu(): menuItem is missing an action: ${menuItem.title}`);
98
+ }
99
+ const menuItemTitleSpanElement = notNull(
100
+ menuItemTitleElement.querySelector(getCssClassSelector(domConstants.MENU_ITEM__LINK_TITLE_SPAN)),
101
+ `renderMainMenu(): main menu item title span not found for: ${menuItem.title}`
102
+ );
103
+
104
+ // add selected to title if selected (or any children are selected)
105
+ if (menuItem.isSelected || (menuItem.actionMenu && findRecursive(menuItem.actionMenu, ['actionMenu'], (checkMenuItem) => !!checkMenuItem.isSelected))) {
106
+ menuItemTitleElement.classList.add(domConstants.MENU_ITEM__SELECTED);
107
+ }
108
+
109
+ if (menuItem.actionMenu) {
110
+ // render children menu items
111
+ menuItemTitleSpanElement.innerHTML = menuItem.title;
112
+
113
+ /** @type {PopupMenu} */
114
+ const popupMenu = {
115
+ menuItems: menuItem.actionMenu,
116
+ title: menuItem.title,
117
+ };
118
+ const subMenuPopup = renderPopupMenu(
119
+ popupMenu,
120
+ menuItemTitleElement,
121
+ {
122
+ childrenMenuType: menuItem.childrenMenuType || childrenMenuTypes.FLYOUT,
123
+ }
124
+ );
125
+ mainMenuItem.appendChild(subMenuPopup);
126
+ popupFocusHandler(mainMenuItem, menuItemTitleElement, subMenuPopup, 'menu', { shouldFocusOnHover: true });
127
+ /** @type {string} */
128
+ let menuClass;
129
+ switch (menuItem.childrenMenuType) {
130
+ case childrenMenuTypes.INLINE:
131
+ menuClass = domConstants.MENU_ITEM__INLINE;
132
+ break;
133
+ case childrenMenuTypes.MEGA_MENU:
134
+ menuClass = domConstants.MENU_ITEM__MEGA_MENU;
135
+ break;
136
+ case childrenMenuTypes.FLYOUT:
137
+ default:
138
+ menuClass = domConstants.MENU_ITEM__FLY_OUT;
139
+ break;
140
+ }
141
+ mainMenuItem.classList.add(menuClass);
142
+ } else {
143
+ const mainMenuItemArrow = notNull(
144
+ menuItemTitleElement.querySelector(getCssClassSelector(domConstants.MENU_ITEM__ARROW)),
145
+ `renderMainMenu(): menu arrow not found for ${menuItem.title}`
146
+ );
147
+ mainMenuItemArrow.remove();
148
+ }
149
+
150
+ if (menuItem.actionFunction) {
151
+ // custom function when triggered
152
+ menuItemTitleSpanElement.innerHTML = menuItem.title;
153
+ menuItemTitleElement.onclick = menuItem.actionFunction;
154
+ } else if (menuItem.actionFunctionUrl) {
155
+ menuItemTitleSpanElement.innerHTML = menuItem.title;
156
+ menuItemTitleElement.setAttribute('href', menuItem.actionFunctionUrl.url);
157
+
158
+ menuItemTitleElement.onclick = (e) => {
159
+ if (!menuItem.actionFunctionUrl?.skipHandleEvent) {
160
+ e.stopPropagation();
161
+ e.preventDefault();
162
+ }
163
+ menuItem.actionFunctionUrl?.actionFunction(e);
164
+ };
165
+ } else if (menuItem.actionUrl) {
166
+ // go to url when triggered
167
+ menuItemTitleSpanElement.innerHTML = menuItem.title;
168
+ menuItemTitleElement.setAttribute('href', menuItem.actionUrl.url);
169
+ }
170
+
171
+ if (menuItem.actionUrl?.openInNewTab || menuItem.actionFunctionUrl?.openInNewTab) {
172
+ menuItemTitleElement.setAttribute('target', '_blank');
173
+ menuItemTitleElement.appendChild(renderDOMSingle(NewTabAccessibility));
174
+ }
175
+ });
176
+ }
177
+
178
+ // ===== UTAH ID ===== //
179
+ let utahIdPopup = null;
180
+ if (settings.utahId) {
181
+ const { button: utahIdButton, menu } = renderUtahIdForMobile();
182
+ utahIdPopup = menu;
183
+ const utahIdButtonWrapper = notNull(
184
+ mainMenuWrapper.querySelector(getCssClassSelector(domConstants.MOBILE__UTAH_ID)),
185
+ 'renderMainMenu: utahIdButtonWrapper not found'
186
+ );
187
+ utahIdButtonWrapper.appendChild(utahIdButton);
188
+ }
189
+
190
+ // ===== SEARCH ICON ===== //
191
+ const searchIcon = notNull(
192
+ /** @type {HTMLElement} */(
193
+ mainMenuWrapper.querySelector(getCssClassSelector(domConstants.MAIN_MENU__SEARCH))
194
+ ),
195
+ 'renderMainMenu: searchIcon not found'
196
+ );
197
+ if (settings.onSearch) {
198
+ hookupTooltip(searchIcon, document.createTextNode('Search'));
199
+ if (searchIcon.onclick) {
200
+ throw new Error('searchIcon already has onclick');
201
+ }
202
+ searchIcon.onclick = () => showSearchModal();
203
+
204
+ if (!settings.mainMenu) {
205
+ const citizenExperienceWrapper = notNull(
206
+ document.querySelector(getCssClassSelector(domConstants.CITIZEN_EXPERIENCE)),
207
+ 'renderMainMenu: citizen experience wrapper not found'
208
+ );
209
+ // if actions AND search, but not utahId nor menu
210
+ // Desktop: the search is with the action icons
211
+ // mobile: the search is on main menu bar with hamburger
212
+ // so make a copy of the menu bar search so it can be mobile and citizen
213
+ const mobileSearchIcon = renderDOMSingle(searchIcon.outerHTML);
214
+ hookupTooltip(mobileSearchIcon, document.createTextNode('Search'));
215
+ mobileSearchIcon.onclick = () => showSearchModal();
216
+ searchIcon.classList.add(domConstants.DESKTOP__HIDDEN);
217
+
218
+ // search icon is the far right item in the citizen experience (no utahid button)
219
+ // there is no main menu, so move search in to top header by utahID button UDS-564
220
+ if (settings.utahId !== false) {
221
+ // place search icon just to left of utahId button
222
+ const utahIdWrapper = notNull(document.querySelector(getCssClassSelector(domConstants.UTAH_ID)), 'renderMainMenu: utahId wrapper not found');
223
+ citizenExperienceWrapper.insertBefore(mobileSearchIcon, utahIdWrapper);
224
+ } else {
225
+ citizenExperienceWrapper.appendChild(mobileSearchIcon);
226
+ }
227
+ }
228
+
229
+ // UDS-564 - move search top top right if no main menu
230
+ if (!settings.mainMenu && !settings.actionItems && settings.utahId === false) {
231
+ // create search clone
232
+ const searchIconMobile = renderDOMSingle(/** @type {HTMLElement} */(searchIcon).outerHTML);
233
+ // run same hookup stuff above
234
+ hookupTooltip(searchIconMobile, document.createTextNode('Search'));
235
+ if (searchIconMobile.onclick) {
236
+ throw new Error('searchIconMobile already has onclick');
237
+ }
238
+ searchIconMobile.onclick = () => showSearchModal();
239
+
240
+ // place in mobile citizen experience
241
+ const citizenExperienceMobile = notNull(
242
+ document.querySelector(getCssClassSelector(domConstants.CITIZEN_EXPERIENCE_MOBILE)),
243
+ 'renderMainMenu: citizen-experience--mobile not found'
244
+ );
245
+ // move search to top right citizen experience because main menu is toast
246
+ citizenExperienceMobile.appendChild(searchIconMobile);
247
+ }
248
+ }
249
+ if (!settings.onSearch) {
250
+ if (settings.mainMenu || (!settings.mainMenu && settings.utahId)) {
251
+ // add a blank div to consume space (only if there is a main menu)
252
+ searchIcon.parentElement?.insertBefore(renderDOMSingle('<div class="main-menu__search-placeholder">'), searchIcon);
253
+ }
254
+ searchIcon.remove();
255
+ }
256
+
257
+ return { mainMenuWrapper, utahIdPopup };
258
+ }
@@ -0,0 +1,3 @@
1
+ <div class="vertical-menu__wrapper">
2
+ <div class="vertical-menu__wrapper-title"></div>
3
+ </div>
@@ -0,0 +1,24 @@
1
+ // @ts-check
2
+ // @ts-ignore
3
+ // eslint-disable-next-line import/no-unresolved
4
+ import MenuWithTitle from './html/MenuWithTitle.html?raw';
5
+
6
+ import renderDOMSingle from '../../misc/renderDOMSingle';
7
+ import domConstants, { getCssClassSelector } from '../../enumerations/domConstants';
8
+
9
+ /**
10
+ * @param {HTMLElement} menu
11
+ * @param {string} title
12
+ * @returns {HTMLElement}
13
+ */
14
+ export default function renderMenuWithTitle(menu, title) {
15
+ const menuWithTitle = renderDOMSingle(MenuWithTitle);
16
+ const menuTitleDiv = menuWithTitle.querySelector(getCssClassSelector(domConstants.POPUP_MENU_WRAPPER__WRAPPER_TITLE));
17
+ if (!menuTitleDiv) {
18
+ throw new Error('renderMenuWithTitle: menuTitleDiv not found');
19
+ }
20
+ menuTitleDiv.appendChild(document.createTextNode(title));
21
+
22
+ menuWithTitle.appendChild(menu);
23
+ return menuWithTitle;
24
+ }
@@ -0,0 +1,37 @@
1
+ // @ts-check
2
+ // @ts-ignore
3
+ // eslint-disable-next-line import/no-unresolved
4
+ import MobileMenuContentItemWrapper from './html/MobileMenuContentItemWrapper.html?raw';
5
+
6
+ import domConstants, { getCssClassSelector } from '../../enumerations/domConstants';
7
+ import renderDOMSingle from '../../misc/renderDOMSingle';
8
+ import uuidv4 from '../../misc/uuidv4';
9
+
10
+ /**
11
+ * @param {HTMLElement} mobileMenuContentItem
12
+ * @returns {HTMLElement} the already added element
13
+ */
14
+ export default function addMobileMenuContentItem(mobileMenuContentItem) {
15
+ const mobileMenuWrapper = document.querySelector(getCssClassSelector(domConstants.MOBILE_MENU__WRAPPER));
16
+ if (!mobileMenuWrapper) {
17
+ throw new Error('addMobileMenuContentItem: mobileMenuWrapper not found');
18
+ }
19
+
20
+ const mobileContentWrapper = mobileMenuWrapper.querySelector(getCssClassSelector(domConstants.MOBILE_MENU__CONTENT));
21
+ if (!mobileContentWrapper) {
22
+ throw new Error('addMobileMenuContentItem: mobileContentWrapper not found');
23
+ }
24
+
25
+ // wrap the mobileMenuContentItem in a content_item div
26
+ const contentItemWrapper = renderDOMSingle(MobileMenuContentItemWrapper);
27
+
28
+ // put content in the div
29
+ contentItemWrapper.appendChild(mobileMenuContentItem);
30
+
31
+ // put content_item div in mobile menu content area
32
+ mobileContentWrapper.appendChild(contentItemWrapper);
33
+
34
+ // always have an ID for easier aria hookup
35
+ contentItemWrapper.setAttribute('id', uuidv4());
36
+ return contentItemWrapper;
37
+ }
@@ -0,0 +1,112 @@
1
+ // @ts-check
2
+ import domConstants, { getCssClassSelector } from '../../enumerations/domConstants';
3
+ import checkForError from '../../misc/checkForError';
4
+ import notNull from '../../misc/notNull';
5
+ import getUtahHeaderSettings from '../../settings/getUtahHeaderSettings';
6
+ import renderActionItemBadge from '../actionItems/renderActionItemBadge';
7
+ import { closeOfficialWebsite } from '../utahLogo/renderOfficialWebsite';
8
+ import mobileMenuInteractionHandler from './mobileMenuInteractionHandler';
9
+ import getHamburgerElements from './util/getHamburgerElements';
10
+ import { hideMobileMenu } from './util/showHideHamburgerElements';
11
+
12
+ /**
13
+ * @param {HTMLElement} mobileMainMenuContentItem
14
+ */
15
+ export default function hookupHamburger(mobileMainMenuContentItem) {
16
+ const { hamburger } = getHamburgerElements('hookupHamburger');
17
+ const hamburgerNotNull = /** @param {string} context */ (context) => notNull(hamburger, `hookupHamburger: hamburger required but is null: ${context}`);
18
+
19
+ const settings = getUtahHeaderSettings();
20
+ const actionItemHasBadge = settings.actionItems?.some((actionItem) => !!actionItem.badge);
21
+ if (actionItemHasBadge) {
22
+ const badge = renderActionItemBadge({ label: 'Home Badge' });
23
+ if (badge) {
24
+ hamburgerNotNull('home badge').appendChild(badge);
25
+ }
26
+ }
27
+
28
+ hideMobileMenu();
29
+
30
+ function firstBarActionItem() {
31
+ const actionItem = notNull(
32
+ /** @type {HTMLElement} */(document.querySelectorAll(`.${domConstants.MOBILE_MENU__ACTION_BAR} .${domConstants.MOBILE_MENU_ACTION_BAR__ACTION_ITEM_WRAPPER}`)[0]),
33
+ 'hookupHamburger: no action items to select on mobile hamburger open'
34
+ );
35
+ const iconButton = actionItem.querySelector('button');
36
+ const contentWrapperId = iconButton?.getAttribute('aria-controls');
37
+ return { actionItem, actionItemWrapper: contentWrapperId ? document.getElementById(contentWrapperId) : null };
38
+ }
39
+
40
+ // hookup aria to home menu (can't in mobile menu interaction handler because it maybe removed later)
41
+ const actionItem = notNull(
42
+ /** @type {HTMLElement} */(document.querySelectorAll(`.${domConstants.MOBILE_MENU__ACTION_BAR} .${domConstants.MOBILE_MENU_ACTION_BAR__ACTION_ITEM_WRAPPER}`)[0]),
43
+ 'hookupHamburger: no action items to select on mobile hamburger open'
44
+ );
45
+ const actionItemButton = actionItem.querySelector?.('button');
46
+ const actionItemId = (actionItemButton || actionItem).getAttribute('id');
47
+ if (!actionItemId) {
48
+ throw new Error('mobileMenuInteractionHandler: actionItemId not found');
49
+ }
50
+ if (mobileMainMenuContentItem) {
51
+ const mobileMainMenuContentItemId = mobileMainMenuContentItem.getAttribute('id');
52
+ if (!mobileMainMenuContentItemId) {
53
+ throw new Error('mobileMenuInteractionHandler: mobileMainMenuContentItemId not found');
54
+ }
55
+ (actionItemButton || actionItem).setAttribute('aria-controls', mobileMainMenuContentItemId);
56
+ mobileMainMenuContentItem.setAttribute('aria-labelledby', actionItemId);
57
+ }
58
+
59
+ if (hamburger) {
60
+ mobileMenuInteractionHandler(
61
+ hamburger,
62
+ () => firstBarActionItem().actionItemWrapper,
63
+ () => firstBarActionItem().actionItem,
64
+ {
65
+ additionalOnClick: () => closeOfficialWebsite(),
66
+ ariaHasPopupType: 'menu',
67
+ shouldOnClickCloseMenu: true,
68
+ }
69
+ );
70
+ }
71
+
72
+ // when hamburger is in an "open" state (mobile menu is open) and the hamburger loses focus (tabbed off of)
73
+ // then move focus to the first action item in the mobile action item bar. UDS-234
74
+ if (hamburger) {
75
+ checkForError(!!hamburger.onblur, 'hookupHamburger: hamburger already has an onblur event');
76
+ hamburger.onblur = () => {
77
+ const { mobileMenu } = getHamburgerElements('showMobileMenu');
78
+ if (mobileMenu.classList.contains(domConstants.IS_OPEN)) {
79
+ const actionBarClass = getCssClassSelector(domConstants.MOBILE_MENU__ACTION_BAR);
80
+ const actionItemWrapperClass = getCssClassSelector(domConstants.MOBILE_MENU_ACTION_BAR__ACTION_ITEM_WRAPPER);
81
+ const actionItemButtonClass = getCssClassSelector(domConstants.ICON_BUTTON);
82
+ const firstMobileActionItem = (
83
+ /** @type {HTMLElement | null} */
84
+ (document.querySelector(`${actionBarClass} ${actionItemWrapperClass}:first-child ${actionItemButtonClass}`))
85
+ );
86
+ firstMobileActionItem?.focus();
87
+ }
88
+ };
89
+ }
90
+
91
+ // if no bar, move hamburger to mobile citizen experience UDS-564
92
+ if (((settings.mainMenu || settings.actionItems) && !settings.onSearch && settings.utahId === false)) {
93
+ const citizenExperienceMobile = notNull(
94
+ document.querySelector(getCssClassSelector(domConstants.CITIZEN_EXPERIENCE_MOBILE)),
95
+ 'hookupHamburger: citizen experience mobile not found'
96
+ );
97
+ citizenExperienceMobile.appendChild(hamburgerNotNull('adding to citizen experience'));
98
+ const mainMenu = notNull(
99
+ document.querySelector(getCssClassSelector(domConstants.MAIN_MENU)),
100
+ 'hookupHamburger: main menu not found'
101
+ );
102
+ mainMenu.classList.add(domConstants.MOBILE__HIDDEN);
103
+ }
104
+
105
+ if (settings.onSearch && !settings.mainMenu) {
106
+ const mainMenu = notNull(
107
+ document.querySelector(getCssClassSelector(domConstants.MAIN_MENU)),
108
+ 'renderMainMenu: main menu not found'
109
+ );
110
+ mainMenu.classList.add(domConstants.DESKTOP__HIDDEN);
111
+ }
112
+ }
@@ -0,0 +1,79 @@
1
+ // @ts-check
2
+ import domConstants, { getCssClassSelector } from '../../enumerations/domConstants';
3
+ import utahIdUrls from '../../enumerations/utahIdUrls';
4
+ import getUtahHeaderSettings from '../../settings/getUtahHeaderSettings';
5
+ import { getCurrentUtahIdData } from '../../utahId/utahIdData';
6
+ import addMobileMenuContentItem from './addMobileMenuContentItem';
7
+ import mobileMenuInteractionHandler from './mobileMenuInteractionHandler';
8
+
9
+ export function removeUtahIdInMobileMenu() {
10
+ const profileActionItem = document.getElementById(domConstants.MOBILE_MENU_ACTON_BAR__PROFILE_ID);
11
+ if (!profileActionItem) {
12
+ throw new Error('removeUtahIdInMobileMenu: profileActionItem not found');
13
+ }
14
+ const profileActionItemWrapper = /** @type {HTMLElement} */ (
15
+ profileActionItem.closest(getCssClassSelector(domConstants.MOBILE_MENU_ACTION_BAR__ACTION_ITEM_WRAPPER))
16
+ );
17
+ if (!profileActionItemWrapper) {
18
+ throw new Error('removeUtahIdInMobileMenu: profileActionItemWrapper not found');
19
+ }
20
+ profileActionItemWrapper.remove();
21
+ }
22
+
23
+ /**
24
+ * @param {HTMLElement} mobileMenuWrapper
25
+ * @param {HTMLElement} utahIdPopup
26
+ */
27
+ export function hookupUtahIdInMobileMenu(mobileMenuWrapper, utahIdPopup) {
28
+ // get utahIdButton in the Mobile Menu
29
+ const utahIdWrapper = document.querySelector(getCssClassSelector(domConstants.MOBILE__UTAH_ID));
30
+ if (!utahIdWrapper) {
31
+ throw new Error('hookupUtahIdInMobileMenu: utahIdWrapper not found');
32
+ }
33
+ const utahIdButton = /** @type {HTMLElement} */ (utahIdWrapper.querySelector(getCssClassSelector(domConstants.UTAH_ID__BUTTON)));
34
+ if (!utahIdButton) {
35
+ throw new Error('hookupUtahIdInMobileMenu: utahIdButton not found');
36
+ }
37
+ const mobileContentWrapper = mobileMenuWrapper.querySelector(getCssClassSelector(domConstants.MOBILE_MENU__CONTENT));
38
+ if (!mobileContentWrapper) {
39
+ throw new Error('hookupUtahIdInMobileMenu: mobileContentWrapper not found');
40
+ }
41
+ const profileActionItem = document.getElementById(domConstants.MOBILE_MENU_ACTON_BAR__PROFILE_ID);
42
+ if (!profileActionItem) {
43
+ throw new Error('hookupUtahIdInMobileMenu: profileActionItem not found');
44
+ }
45
+ const profileActionItemWrapper = /** @type {HTMLElement} */ (
46
+ profileActionItem.closest(getCssClassSelector(domConstants.MOBILE_MENU_ACTION_BAR__ACTION_ITEM_WRAPPER))
47
+ );
48
+ if (!profileActionItemWrapper) {
49
+ throw new Error('hookupHamburger: profileActionItemWrapper not found');
50
+ }
51
+ // clicking button goes to mobile menu content menu
52
+ const utahIdPopupContentWrapper = addMobileMenuContentItem(utahIdPopup);
53
+ mobileMenuInteractionHandler(
54
+ utahIdButton,
55
+ utahIdPopupContentWrapper,
56
+ profileActionItemWrapper,
57
+ {
58
+ ariaHasPopupType: 'menu',
59
+ onClickHandler: (e) => {
60
+ const currentUtahIdData = getCurrentUtahIdData();
61
+ const settings = getUtahHeaderSettings();
62
+ let result = false;
63
+ if (!currentUtahIdData?.isDefinitive || !currentUtahIdData?.userInfo?.authenticated) {
64
+ result = true;
65
+ const onSignIn = (settings.utahId !== false && settings.utahId !== true && settings.utahId?.onSignIn);
66
+ if (onSignIn) {
67
+ onSignIn(e);
68
+ } else {
69
+ e.preventDefault();
70
+ e.stopPropagation();
71
+ window.location.href = utahIdUrls.SIGN_IN;
72
+ }
73
+ }
74
+ return result;
75
+ },
76
+ shouldOnClickCloseMenu: true,
77
+ }
78
+ );
79
+ }
@@ -0,0 +1,2 @@
1
+ <div class="utds-header-mobile-menu__content-item" role="tabpanel">
2
+ </div>
@@ -0,0 +1,32 @@
1
+ <div class="utah-design-system utds-header-mobile-menu" id="utds-header-mobile-menu">
2
+ <div class="utds-header-mobile-menu__backdrop" aria-hidden></div>
3
+ <div class="utds-header-mobile-menu__wrapper">
4
+ <div class="utds-header-mobile-menu__action-bar" role="tablist">
5
+
6
+ <div class="utds-header-mobile-menu__action-item" role="tab">
7
+ <button type="button" class="icon-button icon-button--borderless utds-header-action-item__icon-button icon-home"
8
+ id="utds-header-mobile-menu_action-bar__home">
9
+ <div class="utds-header-action-item__title visually-hidden">Main Menu</div>
10
+ <span class="utds-icon-before-home-menu" aria-hidden="true" role="presentation">
11
+ </span>
12
+ </button>
13
+ <span class="menu-chiclet"></span>
14
+ </div>
15
+ <div class="utds-header-mobile-menu__action-item" role="tab">
16
+ <button type="button"
17
+ class="icon-button icon-button--borderless utds-header-action-item__icon-button icon-profile"
18
+ id="utds-header-mobile-menu_action-bar__profile">
19
+ <div class="utds-header-action-item__title visually-hidden">Profile</div>
20
+ <span class="utds-icon-before-account" aria-hidden="true" role="presentation">
21
+ </span>
22
+ </button>
23
+ <span class="menu-chiclet"></span>
24
+ </div>
25
+
26
+ </div>
27
+ <div class="utds-header-mobile-menu__content"> </div>
28
+ <div aria-hidden tabindex="0" class="utds-header-mobile-menu__hidden-last-focusable visually-hidden">
29
+ end of mobile content form
30
+ </div>
31
+ </div>
32
+ </div>