@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,8 @@
1
+ // @ts-check
2
+ /**
3
+ * @param {any} s candidate for being a string
4
+ * @returns {boolean} true if javascript thinks `s` is a string
5
+ */
6
+ export default function isString(s) {
7
+ return typeof s === 'string' || s instanceof String;
8
+ }
@@ -0,0 +1,9 @@
1
+ // https://www.geeksforgeeks.org/how-to-detect-touch-screen-device-using-javascript/
2
+ export default function isTouchDevice() {
3
+ return (
4
+ ('ontouchstart' in window)
5
+ || (navigator.maxTouchPoints > 0)
6
+ // @ts-ignore
7
+ || (navigator.msMaxTouchPoints > 0)
8
+ );
9
+ }
@@ -0,0 +1,235 @@
1
+ // @ts-check
2
+
3
+ // Types of Events: https://www.w3schools.com/jsref/obj_events.asp
4
+
5
+ /**
6
+ *
7
+ * JsDoc types for the utah header. Types help lower the mental load of object properties as well as
8
+ * helps find "bugs" from invalid typings scenarios.
9
+ *
10
+ * @typedef {Element} ChildNode
11
+ *
12
+ * @typedef {(function(MouseEvent | TouchEvent | KeyboardEvent): void)} EventAction
13
+ *
14
+ * @interface Partial - typescript has "Partial" built-in while JSDoc does not know about "type utilities"
15
+ *
16
+ * @typedef {'SMALL' | 'MEDIUM' | 'LARGE'} Size
17
+ * Should be Synced with the enumerations/sizes object
18
+ *
19
+ * @typedef {'dialog' | 'grid' | 'listbox' | 'menu' | 'tree'} AriaHasPopupType
20
+ *
21
+ * @typedef MainMenuItem {
22
+ * // this started as a copy of MenuItem but can diverge to be its own thing
23
+ * // wordpress has the concept of a menu item that is a link AND has children, but when it goes mobile the link is no longer available
24
+ * // so the wordpress conversion script will take a menu that has a link and children and auto insert the link as the first child
25
+ * // so this could do that work automatically if it detects that scenario instead of throwing an error
26
+ *
27
+ * // should be only one of the following three action types
28
+ * // actionUrl: an <a> with a url for navigation
29
+ * // actionFunction: a <button> that when triggered calls a function
30
+ * // actionFunctionUrl: an <a> that when triggered calls a function (for Single Page Apps)
31
+ * // actionMenu: a <button> that when triggered exposes a sub menu of options
32
+ * @property {MenuItemUrlAction} [actionUrl] - link url
33
+ * @property {EventAction} [actionFunction] - onClick function
34
+ * @property {MenuItemFunctionUrlAction} [actionFunctionUrl] - single page apps render an <a> but call browser push; you should handle cmd click
35
+ * @property {MenuItem[]} [actionMenu] - children menus
36
+ *
37
+ * @property {ChildrenMenuType} [childrenMenuType] - default is "flyout"
38
+ * @property {string} [className] - can be used for `selected` or any other purpose
39
+ * @property {ChildNode} [icon] - icon to show next to this menu item
40
+ * @property {boolean} [isDivider] - this menu item is a divider between other menu items
41
+ * @property {boolean} [isSelected] - is this menu item currently a selected thing (on its page?)
42
+ * @property {string} title - title for the menu item
43
+ * }
44
+ *
45
+ * @typedef {'none' | 'a1' | 'a2' | 'a3' | 'custom' | 'unittest'} Environments
46
+ *
47
+ * @typedef {'utahHeaderLoaded' | 'utahHeaderUnloaded'} Events
48
+ *
49
+ * @typedef MainMenu {
50
+ * @property {MainMenuItem[]} menuItems
51
+ * @property {string} title
52
+ * }
53
+ *
54
+ * For menu items that are links to other locations
55
+ * @typedef MenuItemUrlAction {
56
+ * @property {string} url - the url to which to go when interacted with
57
+ * @property {boolean} [openInNewTab] - true to have the link open in a new window defaults to `false` if not provided
58
+ * }
59
+ *
60
+ * @typedef MenuItemFunctionUrlAction {
61
+ * // actually going to call this function to do what you want
62
+ * @property {EventAction} actionFunction - onClick custom function to call when triggered
63
+ * @property {boolean} [skipHandleEvent] - should handleEvent automatically be used to call your function to stop propagation and prevent default
64
+ *
65
+ * // trick the user to think it's a normal link; you must set these to the correct values to match your custom function
66
+ * @property {boolean} [openInNewTab] - true to have the link say it will open in a new window; defaults to `false`
67
+ * @property {string} url - the url to show when hovered
68
+ * }
69
+ *
70
+ * A menu item in the menu, can have children
71
+ * @typedef MenuItem {
72
+ * // should be only one of the following three action types
73
+ * // actionUrl: an <a> with a url for navigation
74
+ * // actionFunction: a <button> that when triggered calls a function
75
+ * // actionFunctionUrl: an <a> that when triggered calls a function (for Single Page Apps)
76
+ * // actionMenu: a <button> that when triggered exposes a sub menu of options
77
+ * @property {MenuItemUrlAction} [actionUrl] - link url
78
+ * @property {EventAction} [actionFunction] - onClick function
79
+ * @property {MenuItemFunctionUrlAction} [actionFunctionUrl] - single page apps render an <a> but call browser push; you should handle cmd click
80
+ * @property {MenuItem[]} [actionMenu] - children menus
81
+ *
82
+ * @property {string} [className] - can be used for `selected` or any other purpose
83
+ * @property {ChildNode} [icon] - icon to show next to this menu item
84
+ * @property {boolean} [isDivider] - this menu item is a divider between other menu items
85
+ * @property {boolean} [isSelected] - is this menu item currently a selected thing (on its page?)
86
+ * @property {string} title - title for the menu item
87
+ * }
88
+ *
89
+ * @typedef PopupMenu {
90
+ * @property {string} [className] - className to put on the popupMenu
91
+ * @property {MenuItem[]} menuItems - the menu items to show in the menu
92
+ * @property {string} title - the title of the menu
93
+ * }
94
+ *
95
+ * @typedef MediaSizes {
96
+ * @property {number} mobile - mobile sized render area
97
+ * @property {number} tabletLandscape - table landscape sized render area
98
+ * @property {number} tabletPortrait - table portrait sized render area
99
+ * }
100
+ *
101
+ * these match the popper's position options
102
+ * @typedef {'auto' | 'auto-start' | 'auto-end' |
103
+ * 'bottom' | 'bottom-start' | 'bottom-end' |
104
+ * 'left' | 'left-start' | 'left-end' |
105
+ * 'right' | 'right-start' | 'right-end' |
106
+ * 'top' | 'top-start' | 'top-end'
107
+ * } PopupPlacement
108
+ *
109
+ * @typedef PopupFocusHandlerOptions {
110
+ * @property {() => boolean} [isPerformPopup] should the popup pop open? Helpful for utahId that doesn't pop until user loaded
111
+ * @property {function(UIEvent): void} [onClick] custom onclick handler
112
+ * @property {PopupPlacement} [popupPlacement] which side should the popup place itself (defaults to bottom and popper will place where it can)
113
+ * @property {boolean} [preventOnClickHandling] turns off click handling for popup invocation
114
+ * @property {boolean} [shouldFocusOnHover] will perform the popup on hover as well as the focus event
115
+ * }
116
+ *
117
+ * @typedef RenderPopupOptions {
118
+ * @property {boolean} [removePopupArrow] allows removing the popup border arrow for flyouts
119
+ * }
120
+ *
121
+ * @typedef {('flyout' | 'inline' | 'mega-menu')} ChildrenMenuType
122
+ * @typedef RenderPopupMenuOptions {
123
+ * @property {ChildrenMenuType} childrenMenuType
124
+ * - flyout: children in new popup
125
+ * - inline: expandable children
126
+ * - mega-menu: always expanded children
127
+ * @property {boolean} [removePopupArrow] allows removing the popup border arrow for flyouts
128
+ * }
129
+ *
130
+ * @typedef Badge {
131
+ * @property {string} [className] - a class to add to the badge for custom formatting like color
132
+ * @property {string} label - the label for the screen reader to read describing the badge
133
+ * @property {number} [value] - the value to show in the badge
134
+ * }
135
+ *
136
+ * @typedef ActionItem {
137
+ * // should be only one of the following three action types
138
+ * @property {EventAction} [actionFunction] - func: onClick callback
139
+ * @property {PopupMenu} [actionPopupMenu] - Object[]: array of MenuItems
140
+ * @property {function (): HTMLElement | string} [actionDom] - ChildNode: content in a popup.
141
+ *
142
+ * @property {string} [className] - CSS classes for the action item
143
+ * @property {Badge} [badge] - the badge to show in the action item's badge icon
144
+ * @property {HTMLElement | string} icon - DOM or DOM string of icon to show
145
+ * @property {'left' | 'none' | 'right'} [mobileMenuLocation] - positioned right or left of the Utah ID button? not at all? default is none
146
+ * @property {boolean} showTitle - Should the title always be visible?
147
+ * @property {string} title - Title of the action item (required for accessibility)
148
+ * }
149
+ *
150
+ * // must use one and only one of the properties here
151
+ * @typedef DomLocationTarget {
152
+ * @property {string} [cssSelector] - find a target DOM element by document.querySelector(cssSelector) (throws error if not found)
153
+ * @property {HTMLElement} [element] - insert the header as a child of this given element
154
+ * @property {function (): HTMLElement} [elementFunction] - insert the header in to whatever element is returned from this function
155
+ * }
156
+ *
157
+ * @typedef GlobalEventType {
158
+ * @property {(e: MouseEvent) => void} globalOnClick the current event handler for global on click events
159
+ * @property {(e: KeyboardEvent) => void} globalOnKeydown tracks when keys are pressed down
160
+ * @property {(e: KeyboardEvent) => void} globalOnKeyup the current event handler for global on key press events
161
+ * }
162
+ *
163
+ * Partial is a `typescript` utility that takes a type and makes all its properties optional. This works in vs-code IDEs but may
164
+ * be problematic in other IDEs that don't support typescript "out of the box".
165
+ // eslint-disable-next-line jsdoc/no-undefined-types
166
+ * @typedef {Partial<Settings>} SettingsInput
167
+ *
168
+ * // User fields from the UtahId authority
169
+ * @typedef UserInfo {
170
+ * @property {boolean} authenticated - the current information is ratified with the authority
171
+ * @property {boolean | null | undefined} [disabled]
172
+ * @property {string | undefined} [env] - the UtahId environment
173
+ * @property {string | null | undefined} first - the name shown on the UtahId button when logged in
174
+ * @property {string | null | undefined} [id]
175
+ * @property {string | null | undefined} [last]
176
+ * @property {string[] | null | undefined} [mail]
177
+ * @property {string | null | undefined} [middle]
178
+ * @property {string | null | undefined} [status]
179
+ * @property {string | undefined} [type]
180
+ * @property {string | null | undefined} [username]
181
+ * }
182
+ *
183
+ * @typedef {'Automatic' | 'None' | 'Provided'} UtahIdFetchStyle
184
+ *
185
+ * @typedef UtahIdData {
186
+ * @property {boolean | null} isDefinitive - true when the user's state is known, false while the ajax request is inflight
187
+ * @property {string | null} lastError - true when the user's state is known, false while the ajax request is inflight
188
+ * @property {UserInfo | null} userInfo - the current logged in user info or null if not found
189
+ * }
190
+ *
191
+ * // only fill in what you want to change, the rest will be defaults
192
+ * // The UtahID header will auto fetch the user from UtahID if it is not told that your application will be controlling the signed in user
193
+ * // This may cause the header to jump as data comes from UtahID and your application gets data from its data source.
194
+ * // To prevent this jankiness, your application should call setUtahHeaderSettings with a userInfo.currentUser value of `null`. This way
195
+ * // the header knows not to fetch the user and your application can later call setSettings again with the current user.
196
+ * @typedef UtahIDSettings {
197
+ * @property {UserInfo | undefined | null} currentUser - null: app controls the user, undefined: header will fetch current user
198
+ * @property {function(UtahIdData): void | undefined} [onAuthChanged] - auth user changes, eg (newUserData) => { ... do something ... }
199
+ * @property {function(UIEvent): void | undefined} [onProfile] - when the UtahId's menu item for the user's profile is triggered: (e) => { }
200
+ * @property {function(UIEvent): void | undefined} [onSignIn] - when the UtahId button is pressed to sign in: (e) => { }
201
+ * @property {function(UIEvent): void | undefined} [onSignOut] - when the UtahId's menu item for sign out is triggered: (e) => { }
202
+ * @property {MenuItem[] | undefined} [menuItems] - menu items to add to the UtahId menu (user must be logged in to open the menu): (e) => { }
203
+ * }
204
+ *
205
+ * @typedef Logo {
206
+ * @property {HTMLElement | function(): HTMLElement} [element] an HTML Element to render
207
+ * @property {string | function(): string} [htmlString] string containing html that will be rendered
208
+ * @property {string | function(): string} [imageUrl] url to an image
209
+ * }
210
+ *
211
+ * @typedef FooterSettings {
212
+ * @property {DomLocationTarget} [domLocationTarget] - where in the DOM should the footer be inserted? (defaults to the bottom of the body)
213
+ * @property {boolean} [showHorizontalRule] - true to have a dividing horizontal rule placed at the top of the footer for dividing footer content
214
+ * }
215
+ *
216
+ * // !! Make sure to update SettingsShape in the library if this changes !!
217
+ * @typedef Settings {
218
+ * @property {ActionItem[]} [actionItems] - action items to show in the header
219
+ * @property {DomLocationTarget} [domLocationTarget] - where in the DOM should the header be inserted? (defaults to the top of the body)
220
+ * @property {FooterSettings | null} [footer] - null means to not show the footer
221
+ * @property {Logo} [logo] - the logo to show
222
+ * @property {MainMenu | false} [mainMenu] - the main menu to show on a line below the citizen experience/unbrand line
223
+ * @property {MediaSizes} mediaSizes - sizes for triggering media queries
224
+ * @property {((search: string) => void) | false} [onSearch] - if onSearch is provided, the search icon will show in the main menu bar
225
+ * @property {boolean} showTitle - should the title be shown (it will always be on the page for accessibility)
226
+ * @property {string} size - size has to be one of the `Size` types
227
+ * @property {string} title - the title to place at the top of the page (can be hidden) but needs to be there for accessibility
228
+ * @property {string} titleURL - when the agency title is triggered, the browser navigates to this url
229
+ * @property {UtahIDSettings | boolean} [utahId] - settings for the utahId button; true = turned on, false = turned off, object = custom
230
+ * }
231
+ *
232
+ */
233
+
234
+ // without this export, `@typedef import` reports this file 'is not a module'... (눈_눈)
235
+ export default false;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * https://docs.joshuatz.com/cheatsheets/js/jsdoc/#non-null-assertion-in-jsdoc
3
+ * Exclude in JSDoc removes a type from another type. The returned `T` no longer has `null` nor `undefined` in its type
4
+ *
5
+ * @template T
6
+ * @param {T} value
7
+ * @param {string} errorMessage
8
+ * @returns {NonNullable<T>}
9
+ */
10
+ export default function notNull(value, errorMessage) {
11
+ if (value === null || value === undefined) {
12
+ throw new Error(errorMessage);
13
+ }
14
+ return value;
15
+ }
@@ -0,0 +1,209 @@
1
+ // @ts-check
2
+ import { createPopper } from '@popperjs/core';
3
+ import domConstants, { getCssClassSelector } from '../enumerations/domConstants';
4
+ import popupPlacement from '../enumerations/popupPlacement';
5
+ import { hideAllMenus } from '../lifecycle/globalEvents';
6
+ import isTouchDevice from './isTouchDevice';
7
+ import showHideElement from './showHideElement';
8
+ import checkForError from './checkForError';
9
+
10
+ /*
11
+ ___ ___ _ _ _ _____ _____ ___ _ _ ___ _ _
12
+ | \ / _ \ | \| | ( ) |_ _| |_ _| / _ \ | | | | / __| | || |
13
+ | |) | | (_) | | .` | |/ | | | | | (_) | | |_| | | (__ | __ |
14
+ |___/ \___/ |_|\_| |_| |_| \___/ \___/ \___| |_||_|
15
+
16
+ If you do dare touch this, and you probably should if you need to, here are the testing scenarios to consider:
17
+ * have an action item (divisions) that pops open a menu with children
18
+ * tab through the action item so that the popup opens and tab through children so that they toggle open
19
+ * tabbing out of focus of the action item and children menus should then close the popup
20
+ * have two action items with popups (menu popup and dom content popup)
21
+ * click on one of the action icons to open the popup
22
+ * click on the other action icon to open its popup and close the other popup
23
+ * there was a point in time where the original popup was not closing
24
+ * have a header menu item with at least 3 levels deep of menu items
25
+ * tab to the children at the deepest level and make sure that the sub menus flyout
26
+ * accessibility: have an action item (divisions) that has a popup menu with children
27
+ * without tabbing or clicking, use accessibility (ctrl+opt+right-arrow) to move on top of the divisions action item
28
+ * trigger the menu (ctrl+opt+space) to have the menu toggle open
29
+ * do the same test as above but with the Utah ID button after logging in so that it opens the Utah Id menu
30
+ * have a main menu item whose children flyout and make sure hovering causes those menu items to flyout
31
+ * tab to a child menu in a popup menu that also has children menus
32
+ * this "middle" menu (has parent and children) when first focused should not have its chevron open nor show its children
33
+ * tabbing again to see this menu's children then opens the children and toggles the chevron
34
+
35
+ Notes:
36
+ * mobile devices do not have focus events. This caused issues with putting logic in focus that would work in browser but not
37
+ in mobile and vice/versa.
38
+ * when targeting items via accessibility navigation (not tab key), items do not receive focus, so focus events again were
39
+ problematic.
40
+ * Added a timeout to the open/close because the focusin, mousedown, and onclick were fighting with timing issues
41
+ * sometimes onfocusin would fire first, other times onmousedown would fire first
42
+ * and they would cause each other to see different statuses because one would open and another would close the menu
43
+ */
44
+
45
+ /**
46
+ * @typedef {import('../misc/jsDocTypes').AriaHasPopupType} AriaHasPopupType
47
+ * @typedef {import('../misc/jsDocTypes').PopupFocusHandlerOptions} PopupFocusHandlerOptions
48
+ */
49
+
50
+ /**
51
+ * Tracking focus of a wrapper that has a button for toggling and a popup inside of it
52
+ * for both onClicks AND tabbing to focus is pretty complicated. The idea is that while the
53
+ * button OR menu has focus, the menu is shown. Also global events like clicking off the wrapper
54
+ * or pressing escape should also close the popup.
55
+ *
56
+ * @param {HTMLElement} wrapper the wrapper containing the button and popup
57
+ * @param {HTMLElement} button the button that toggles the popup to open/close
58
+ * @param {HTMLElement} popup the actual popup being opened and closed
59
+ * @param {AriaHasPopupType} ariaHasPopup aria tag for popup type
60
+ * @param {PopupFocusHandlerOptions | undefined} options
61
+ */
62
+ export default function popupFocusHandler(wrapper, button, popup, ariaHasPopup, options) {
63
+ let delayPopupTimeoutId = NaN;
64
+ let delayHideTimeoutId = NaN;
65
+ const TIMEOUT_MS_LONG = 300;
66
+ const TIMEOUT_MS_MEDIUM = 150;
67
+ const TIMEOUT_MS_SHORT = 50;
68
+
69
+ button.setAttribute('aria-expanded', 'false');
70
+ button.setAttribute('aria-haspopup', ariaHasPopup);
71
+
72
+ /*
73
+ ___ ___ _ _ _ _____ _____ ___ _ _ ___ _ _
74
+ | \ / _ \ | \| | ( ) |_ _| |_ _| / _ \ | | | | / __| | || |
75
+ | |) | | (_) | | .` | |/ | | | | | (_) | | |_| | | (__ | __ |
76
+ |___/ \___/ |_|\_| |_| |_| \___/ \___/ \___| |_||_|
77
+ */
78
+ /**
79
+ * @param {number} delayMS
80
+ */
81
+ function performPopup(delayMS) {
82
+ clearTimeout(delayPopupTimeoutId);
83
+ clearTimeout(delayHideTimeoutId);
84
+
85
+ if (!options?.isPerformPopup || (options?.isPerformPopup && options.isPerformPopup())) {
86
+ delayPopupTimeoutId = window.setTimeout(
87
+ () => {
88
+ createPopper(button, popup, {
89
+ placement: options?.popupPlacement || popupPlacement.BOTTOM,
90
+ modifiers: [
91
+ {
92
+ name: 'offset',
93
+ options: { offset: [0, 11] },
94
+ },
95
+ ],
96
+ });
97
+ showHideElement(popup, true, domConstants.POPUP__VISIBLE, domConstants.POPUP__HIDDEN);
98
+ button.setAttribute('aria-expanded', 'true');
99
+
100
+ // hide all tooltips
101
+ document.querySelectorAll(getCssClassSelector(domConstants.TOOLTIP__WRAPPER))
102
+ .forEach((tooltip) => tooltip.classList.add(domConstants.TOOLTIP__WRAPPER__HIDDEN));
103
+ },
104
+ delayMS
105
+ );
106
+ }
107
+ }
108
+
109
+ /*
110
+ ___ ___ _ _ _ _____ _____ ___ _ _ ___ _ _
111
+ | \ / _ \ | \| | ( ) |_ _| |_ _| / _ \ | | | | / __| | || |
112
+ | |) | | (_) | | .` | |/ | | | | | (_) | | |_| | | (__ | __ |
113
+ |___/ \___/ |_|\_| |_| |_| \___/ \___/ \___| |_||_|
114
+ */
115
+ /**
116
+ * @param {number} delayMS
117
+ */
118
+ function hidePopup(delayMS) {
119
+ clearTimeout(delayPopupTimeoutId);
120
+ clearTimeout(delayHideTimeoutId);
121
+ if (!options?.isPerformPopup || options.isPerformPopup()) {
122
+ delayHideTimeoutId = window.setTimeout(
123
+ () => {
124
+ showHideElement(popup, false, domConstants.POPUP__VISIBLE, domConstants.POPUP__HIDDEN);
125
+ button.setAttribute('aria-expanded', 'false');
126
+ },
127
+ delayMS
128
+ );
129
+ }
130
+ }
131
+
132
+ /*
133
+ ___ ___ _ _ _ _____ _____ ___ _ _ ___ _ _
134
+ | \ / _ \ | \| | ( ) |_ _| |_ _| / _ \ | | | | / __| | || |
135
+ | |) | | (_) | | .` | |/ | | | | | (_) | | |_| | | (__ | __ |
136
+ |___/ \___/ |_|\_| |_| |_| \___/ \___/ \___| |_||_|
137
+ */
138
+ if (options?.preventOnClickHandling) {
139
+ // eslint-disable-next-line no-param-reassign
140
+ checkForError(!!wrapper.onclick, 'popupFocusHandler: wrapper already has an onclick');
141
+ // eslint-disable-next-line no-param-reassign
142
+ wrapper.onclick = (e) => {
143
+ // for menus that are both a link and have children menus, when doing a popup, prevent clicking
144
+ // from doing anything so that the popup menu doesn't go away on the click. The link for the
145
+ // menu will be a separate menu item.
146
+ e.preventDefault();
147
+ e.stopPropagation();
148
+ };
149
+ } else {
150
+ wrapper.addEventListener('focusin', () => performPopup(TIMEOUT_MS_MEDIUM));
151
+
152
+ wrapper.addEventListener('focusout', () => hidePopup(TIMEOUT_MS_MEDIUM));
153
+ }
154
+
155
+ if (options?.shouldFocusOnHover) {
156
+ wrapper.addEventListener('mouseenter', () => performPopup(TIMEOUT_MS_LONG));
157
+ wrapper.addEventListener('mouseleave', () => hidePopup(TIMEOUT_MS_LONG));
158
+ }
159
+
160
+ /*
161
+ ___ ___ _ _ _ _____ _____ ___ _ _ ___ _ _
162
+ | \ / _ \ | \| | ( ) |_ _| |_ _| / _ \ | | | | / __| | || |
163
+ | |) | | (_) | | .` | |/ | | | | | (_) | | |_| | | (__ | __ |
164
+ |___/ \___/ |_|\_| |_| |_| \___/ \___/ \___| |_||_|
165
+ */
166
+
167
+ if (!options?.preventOnClickHandling) {
168
+ if (button.onclick) {
169
+ throw new Error('popupFocusHandler: button already has onclick');
170
+ }
171
+ // eslint-disable-next-line no-param-reassign
172
+ button.onclick = (e) => {
173
+ const wasAlreadyOpen = button.getAttribute('aria-expanded') === 'true';
174
+ // for click popups
175
+ if (!options?.isPerformPopup || options.isPerformPopup()) {
176
+ e.stopPropagation();
177
+ e.preventDefault();
178
+ /*
179
+ ___ ___ _ _ _ _____ _____ ___ _ _ ___ _ _
180
+ | \ / _ \ | \| | ( ) |_ _| |_ _| / _ \ | | | | / __| | || |
181
+ | |) | | (_) | | .` | |/ | | | | | (_) | | |_| | | (__ | __ |
182
+ |___/ \___/ |_|\_| |_| |_| \___/ \___/ \___| |_||_|
183
+ */
184
+
185
+ // if !wasAlreadyOpen but aria-expanded is "true" then the 'focusin' opened it (tabbed to it?)
186
+ if (wasAlreadyOpen && button.getAttribute('aria-expanded') === 'true') {
187
+ hidePopup(TIMEOUT_MS_SHORT);
188
+ /** @type {HTMLElement | null} */(document.activeElement)?.blur();
189
+ } else {
190
+ if (isTouchDevice()) {
191
+ hideAllMenus();
192
+ }
193
+ performPopup(TIMEOUT_MS_SHORT);
194
+ }
195
+ }
196
+ if (options?.onClick) {
197
+ options.onClick(e);
198
+ }
199
+ };
200
+ }
201
+ }
202
+ /*
203
+ ___ ___ _ _ _ _____ _____ ___ _ _ ___ _ _
204
+ | \ / _ \ | \| | ( ) |_ _| |_ _| / _ \ | | | | / __| | || |
205
+ | |) | | (_) | | .` | |/ | | | | | (_) | | |_| | | (__ | __ |
206
+ |___/ \___/ |_|\_| |_| |_| \___/ \___/ \___| |_||_|
207
+
208
+ -- see top for details --
209
+ */
@@ -0,0 +1,61 @@
1
+ // @ts-check
2
+
3
+ import isString from './isString';
4
+
5
+ /**
6
+ * Render a DOM from a string and return just the DOM child node
7
+ * or HTMLCollection if there is more than one Node rendered
8
+ * @param {string} str - HTML string to become rendered DOM
9
+ * @returns {HTMLCollection | Element}
10
+ */
11
+ function renderDOM(str) {
12
+ const domParser = new DOMParser();
13
+
14
+ /** @type {HTMLCollection | Element} */
15
+ let result;
16
+ if (isString(str)) {
17
+ const rendered = /** @type {Document} */ (isString(str) ? domParser.parseFromString(str, 'text/html') : str);
18
+
19
+ const maybeResult = (
20
+ rendered.body.children.length > 1
21
+ ? rendered.body.children
22
+ : rendered.body.children.item(0)
23
+ );
24
+ if (!maybeResult) {
25
+ // eslint-disable-next-line no-console
26
+ console.error(str);
27
+ throw new Error('renderDOM: nothing rendered');
28
+ }
29
+ result = maybeResult;
30
+ } else if (/** @type {unknown} */ (str) instanceof Element) {
31
+ result = str;
32
+ } else if (!str) {
33
+ throw new Error('renderDOM: falsy string passed; cannot render nothing');
34
+ } else {
35
+ // eslint-disable-next-line no-console
36
+ console.error(str);
37
+ throw new Error(`renderDOM: str is not a string nor a DOM Element : '${str}'`);
38
+ }
39
+ return result;
40
+ }
41
+
42
+ /**
43
+ * Pull first element out of a collection if a collection was rendered
44
+ *
45
+ * @param {string | HTMLElement} str
46
+ * @returns {HTMLElement}
47
+ */
48
+ export default function renderDOMSingle(str) {
49
+ const dom = typeof str === 'string' ? renderDOM(str) : str;
50
+ if (dom instanceof HTMLCollection && dom.length > 1) {
51
+ throw new Error('renderDOMSingle: must render a single element');
52
+ }
53
+ /** @type HTMLElement | null */
54
+ const firstChild = /** @type HTMLElement | null */ (dom instanceof HTMLCollection ? dom[0] : dom);
55
+ if (!firstChild) {
56
+ // eslint-disable-next-line no-console
57
+ console.error(str);
58
+ throw new Error('renderDOMSingle: nothing rendered');
59
+ }
60
+ return firstChild;
61
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @param {Element} element
3
+ * @param {boolean} isShown
4
+ * @param {string} visibleClass
5
+ * @param {string} hiddenClass
6
+ */
7
+ export default function showHideElement(element, isShown, visibleClass, hiddenClass) {
8
+ if (isShown) {
9
+ element.classList.remove(hiddenClass);
10
+ element.classList.add(visibleClass);
11
+ } else {
12
+ element.classList.add(hiddenClass);
13
+ element.classList.remove(visibleClass);
14
+ }
15
+ }
@@ -0,0 +1,18 @@
1
+ /* eslint-disable no-bitwise */
2
+
3
+ /** https://stackoverflow.com/a/8076436/1478933
4
+ * @param {object | string} thing the thing to hash
5
+ */
6
+ export default function toHash(thing) {
7
+ let hash = 0;
8
+ const string = JSON.stringify(thing);
9
+ for (let i = 0; i < string.length; i += 1) {
10
+ const code = string.charCodeAt(i);
11
+ hash = ((hash << 5) - hash) + code;
12
+
13
+ // Convert to 32bit integer
14
+ hash &= hash;
15
+ }
16
+
17
+ return hash;
18
+ }
@@ -0,0 +1,8 @@
1
+ // @ts-nocheck
2
+ export default function uuidv4() {
3
+ return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(
4
+ /[018]/g,
5
+ // eslint-disable-next-line no-bitwise
6
+ (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> c) / 4).toString(16)
7
+ );
8
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @template ValueOrFunctionValueT
3
+ * @param {ValueOrFunctionValueT | function(): ValueOrFunctionValueT} valueOrFunction
4
+ * @returns {ValueOrFunctionValueT}
5
+ */
6
+ export default function valueOrFunctionValue(valueOrFunction) {
7
+ return (
8
+ (typeof valueOrFunction === 'function')
9
+ // @ts-ignore
10
+ ? valueOrFunction()
11
+ : valueOrFunction
12
+ );
13
+ }
@@ -0,0 +1,4 @@
1
+ <span class="utds-new-tab-link-a11y">
2
+ <span class="visually-hidden">, opens in a new tab</span>
3
+ <span class="utds-icon-after-external-link" aria-hidden="true"></span>
4
+ </span>
@@ -0,0 +1,29 @@
1
+ // @ts-check
2
+
3
+ // @ts-ignore
4
+ // eslint-disable-next-line import/no-unresolved
5
+ import ActionItemsWrapper from './html/ActionItemsWrapper.html?raw';
6
+
7
+ import renderDOMSingle from '../../misc/renderDOMSingle';
8
+ import getUtahHeaderSettings from '../../settings/getUtahHeaderSettings';
9
+ import renderActionItem from './renderActionItem';
10
+
11
+ /**
12
+ * @returns {Element | null}
13
+ */
14
+ export default function ActionItems() {
15
+ const { actionItems } = getUtahHeaderSettings();
16
+ /** @type {HTMLElement | null} */
17
+ let actionItemsWrapper = null;
18
+
19
+ if (actionItems?.length) {
20
+ actionItemsWrapper = renderDOMSingle(ActionItemsWrapper);
21
+
22
+ getUtahHeaderSettings()
23
+ .actionItems
24
+ ?.map((actionItemToRender) => renderActionItem(actionItemToRender))
25
+ ?.forEach((renderedActionItem) => actionItemsWrapper?.appendChild(renderedActionItem));
26
+ }
27
+
28
+ return actionItemsWrapper;
29
+ }
@@ -0,0 +1,5 @@
1
+ <div class="utds-header-action-item">
2
+ <button type="button" class="icon-button utds-header-action-item__icon-button">
3
+ <div class="utds-header-action-item__title visually-hidden"></div>
4
+ </button>
5
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class="utds-header-action-item-menu">
2
+ <div class="vertical-menu__wrapper-title"></div>
3
+ </div>
@@ -0,0 +1 @@
1
+ <div class="utds-action-items-wrapper"></div>
@@ -0,0 +1,4 @@
1
+ <div class="utds-badge__wrapper">
2
+ <span class="utds-badge__value"></span>
3
+ <span class="utds-badge__label visually-hidden"></span>
4
+ </div>