@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,139 @@
1
+ // @ts-ignore
2
+ // eslint-disable-next-line import/no-unresolved
3
+ import UtahOfficialWebsitePopupContentHtml from './html/UtahOfficialWebsitePopupContent.html?raw';
4
+
5
+ import domConstants, { getCssClassSelector } from '../../enumerations/domConstants';
6
+ import notNull from '../../misc/notNull';
7
+ import renderDOMSingle from '../../misc/renderDOMSingle';
8
+ import uuidv4 from '../../misc/uuidv4';
9
+ import { hideMobileMenu } from '../mobile/util/showHideHamburgerElements';
10
+
11
+ export function closeOfficialWebsite() {
12
+ const officialWebsiteWrapper = notNull(
13
+ document.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_WRAPPER)),
14
+ 'openOfficialWebsite: official wrapper not found'
15
+ );
16
+ const logoWrapper = notNull(
17
+ document.querySelector(getCssClassSelector(domConstants.LOGO)),
18
+ 'openOfficialWebsite: logoWrapper not found'
19
+ );
20
+ const logoButton = notNull(
21
+ logoWrapper.querySelector(getCssClassSelector(domConstants.LOGO_SVG)),
22
+ 'openOfficialWebsite: logoButton not found'
23
+ );
24
+ const closeButton = notNull(
25
+ officialWebsiteWrapper.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_CLOSE_BUTTON)),
26
+ 'openOfficialWebsite: official close button not found'
27
+ );
28
+ officialWebsiteWrapper.classList.add(domConstants.VISUALLY_HIDDEN);
29
+
30
+ logoButton.setAttribute('aria-expanded', 'false');
31
+ officialWebsiteWrapper.setAttribute('aria-hidden', 'true');
32
+ // shift tabbing from mobile menu item content's first action item button would tab in to this
33
+ // hidden "official website" content. setting the tabindex to -1 makes this not happen
34
+ closeButton.setAttribute('tabIndex', '-1');
35
+ officialWebsiteWrapper.setAttribute('tabIndex', '-1');
36
+
37
+ // hide all tooltips when button is clicked because the popup opens
38
+ const toolTips = document.querySelectorAll(getCssClassSelector(domConstants.TOOLTIP__WRAPPER));
39
+ toolTips?.forEach((tooltip) => {
40
+ tooltip.classList.add(domConstants.TOOLTIP__WRAPPER__HIDDEN);
41
+ tooltip.classList.remove(domConstants.TOOLTIP__WRAPPER__VISIBLE);
42
+ });
43
+ }
44
+
45
+ export function openOfficialWebsite() {
46
+ const officialWebsiteWrapper = notNull(
47
+ document.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_WRAPPER)),
48
+ 'openOfficialWebsite: official wrapper not found'
49
+ );
50
+ const logoWrapper = notNull(
51
+ document.querySelector(getCssClassSelector(domConstants.LOGO)),
52
+ 'openOfficialWebsite: logoWrapper not found'
53
+ );
54
+ const logoButton = notNull(
55
+ logoWrapper.querySelector(getCssClassSelector(domConstants.LOGO_SVG)),
56
+ 'openOfficialWebsite: logoButton not found'
57
+ );
58
+ const closeButton = notNull(
59
+ officialWebsiteWrapper.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_CLOSE_BUTTON)),
60
+ 'openOfficialWebsite: official close button not found'
61
+ );
62
+ officialWebsiteWrapper.classList.remove(domConstants.VISUALLY_HIDDEN);
63
+
64
+ closeButton.removeAttribute('tabIndex');
65
+ officialWebsiteWrapper.removeAttribute('tabIndex');
66
+ logoButton.setAttribute('aria-expanded', 'true');
67
+ officialWebsiteWrapper.setAttribute('aria-hidden', 'false');
68
+ // @ts-ignore
69
+ officialWebsiteWrapper.focus();
70
+
71
+ // hide all tooltips when button is clicked because the popup opens
72
+ const toolTips = document.querySelectorAll(getCssClassSelector(domConstants.TOOLTIP__WRAPPER));
73
+ toolTips?.forEach((tooltip) => {
74
+ tooltip.classList.add(domConstants.TOOLTIP__WRAPPER__HIDDEN);
75
+ tooltip.classList.remove(domConstants.TOOLTIP__WRAPPER__VISIBLE);
76
+ });
77
+
78
+ hideMobileMenu();
79
+ }
80
+
81
+ /**
82
+ * @returns {Element}
83
+ */
84
+ export default function renderOfficialWebsite() {
85
+ const officialWebsiteWrapper = renderDOMSingle(UtahOfficialWebsitePopupContentHtml);
86
+
87
+ const logoWrapper = document.querySelector(getCssClassSelector(domConstants.LOGO));
88
+ const logoButton = /** @type {HTMLElement} */(logoWrapper?.querySelector(getCssClassSelector(domConstants.LOGO_SVG)));
89
+ if (!logoButton) {
90
+ throw new Error('renderOfficialWebsite: logoButton not found');
91
+ }
92
+ if (logoButton.onclick) {
93
+ throw new Error('renderOfficialWebsite: logoButton already has an onclick');
94
+ }
95
+
96
+ const closeButton = officialWebsiteWrapper.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_CLOSE_BUTTON));
97
+ if (!closeButton) {
98
+ throw new Error('renderOfficialWebsite: closeButton not found');
99
+ }
100
+
101
+ logoButton.onclick = () => {
102
+ officialWebsiteWrapper.classList.toggle(domConstants.VISUALLY_HIDDEN);
103
+ if (officialWebsiteWrapper.classList.contains(domConstants.VISUALLY_HIDDEN)) {
104
+ closeOfficialWebsite();
105
+ } else {
106
+ openOfficialWebsite();
107
+ }
108
+
109
+ // hide all tooltips when button is clicked because the popup opens
110
+ const toolTips = document.querySelectorAll(getCssClassSelector(domConstants.TOOLTIP__WRAPPER));
111
+ toolTips?.forEach((tooltip) => {
112
+ tooltip.classList.add(domConstants.TOOLTIP__WRAPPER__HIDDEN);
113
+ tooltip.classList.remove(domConstants.TOOLTIP__WRAPPER__VISIBLE);
114
+ });
115
+ };
116
+
117
+ // @ts-ignore
118
+ closeButton.onclick = () => {
119
+ officialWebsiteWrapper.classList.toggle(domConstants.VISUALLY_HIDDEN);
120
+ logoButton.focus();
121
+ logoButton.setAttribute('aria-expanded', 'false');
122
+ officialWebsiteWrapper.setAttribute('aria-hidden', 'true');
123
+ };
124
+
125
+ if (!closeButton.id) {
126
+ closeButton.id = uuidv4();
127
+ }
128
+ if (!officialWebsiteWrapper.id) {
129
+ officialWebsiteWrapper.id = uuidv4();
130
+ }
131
+
132
+ // hook up aria
133
+ logoButton.setAttribute('aria-controls', officialWebsiteWrapper.id);
134
+ logoButton.setAttribute('aria-expanded', 'false');
135
+ officialWebsiteWrapper.setAttribute('aria-hidden', 'true');
136
+ officialWebsiteWrapper.setAttribute('aria-labelledby', logoButton.id);
137
+
138
+ return officialWebsiteWrapper;
139
+ }
@@ -0,0 +1,31 @@
1
+ // @ts-check
2
+ import sizes from '../enumerations/sizes';
3
+
4
+ /**
5
+ * @typedef {import('../misc/jsDocTypes').Settings} Settings
6
+ */
7
+
8
+ /**
9
+ * !~! Do not use defaultSettings directly !~!
10
+ * Interact with `settings` using getUtahHeaderSettings() and setUtahHeaderSettings().
11
+ * There is a SettingsInput jsDoc type that allows all settings fields to be undefined.
12
+ * That way, an app can pass in a blank object and still get a header. This `defaultSettings`
13
+ * then are defaults for all the required fields in case they really did pass a blank object
14
+ * to setUtahHeaderSettings. So make sure out all the required fields in this object with sensible
15
+ * default values as a starting place for a new app.
16
+ * @type {Settings} the current settings of the header
17
+ */
18
+ export default {
19
+ onSearch: false,
20
+ mainMenu: false,
21
+ mediaSizes: {
22
+ mobile: 640,
23
+ tabletPortrait: 768,
24
+ tabletLandscape: 1024,
25
+ },
26
+ showTitle: true,
27
+ size: sizes.MEDIUM,
28
+ title: 'My Utah.gov Site',
29
+ titleURL: '/',
30
+ utahId: false,
31
+ };
@@ -0,0 +1,16 @@
1
+ // @ts-check
2
+ import settingsKeeper from './settingsKeeper';
3
+
4
+ /**
5
+ * @typedef {import('../misc/jsDocTypes').Settings} Settings
6
+ */
7
+
8
+ /**
9
+ * an easy to use function, this is what the world expects.
10
+ * the functional world isn't ready for objects. it tried.
11
+ * it didn't stick. back to functions.
12
+ * @returns {Settings} the current settings information
13
+ */
14
+ export default function getUtahHeaderSettings() {
15
+ return settingsKeeper.getSettings();
16
+ }
@@ -0,0 +1,69 @@
1
+ // @ts-check
2
+ import events from '../enumerations/events';
3
+ import { loadHeader, removeHeader } from '../lifecycle/lifecycle';
4
+ import defaultSettings from './defaultSettings';
5
+ import settingsKeeper from './settingsKeeper';
6
+
7
+ /**
8
+ * @typedef {import('../misc/jsDocTypes').FooterSettings} FooterSettings
9
+ * @typedef {import('../misc/jsDocTypes').Settings} Settings
10
+ * @typedef {import('../misc/jsDocTypes').SettingsInput} SettingsInput
11
+ */
12
+
13
+ function doLoadHeader() {
14
+ removeHeader(false);
15
+
16
+ loadHeader();
17
+ }
18
+
19
+ // Trigger a custom event ('utahHeaderLoaded') that developers can listen for
20
+ // in their applications.
21
+ // The event needs to wait for the UMD library to load the global window.utahHeader
22
+ // module. Use setInterval to wait for this script to finish running before firing
23
+ // the `utahHeaderLoaded` event.
24
+ let isSetUtahHeaderSettingsCalled = false;
25
+ const MAX_EVENT_FIRES = 15000;
26
+ let numberEventFires = 0;
27
+
28
+ const intervalId = setInterval(
29
+ () => {
30
+ numberEventFires += 1;
31
+ if (numberEventFires >= MAX_EVENT_FIRES || isSetUtahHeaderSettingsCalled) {
32
+ clearInterval(intervalId);
33
+ } else {
34
+ // please, developer, call setUtahHeaderSettings() as soon as you receive this event... the header
35
+ // can't load if you don't give it any settings.
36
+ document.dispatchEvent(new Event(events.HEADER_LOADED));
37
+ }
38
+ },
39
+ 2
40
+ );
41
+
42
+ /**
43
+ * @param {SettingsInput} newSettings
44
+ * @returns {Settings}
45
+ */
46
+ export function setUtahHeaderSettings(newSettings) {
47
+ // note that if newSettings has a key/value where the value is undefined it WILL override the value to undefined
48
+ // but if newSettings is missing a key then the `undefined` value of the missing key will not override the default.
49
+ // this is only a shallow copy, so merging nested settings does not happen.
50
+ settingsKeeper.setSettings(newSettings);
51
+
52
+ isSetUtahHeaderSettingsCalled = true;
53
+
54
+ if (document?.body) {
55
+ doLoadHeader();
56
+ } else {
57
+ window.addEventListener('load', () => doLoadHeader());
58
+ }
59
+
60
+ return settingsKeeper.getSettings();
61
+ }
62
+
63
+ /**
64
+ * @param {FooterSettings} [footerSettings]
65
+ * @returns {FooterSettings | undefined}
66
+ */
67
+ export function setUtahFooterSettings(footerSettings) {
68
+ return setUtahHeaderSettings({ ...defaultSettings, ...settingsKeeper.getSettings(), footer: footerSettings });
69
+ }
@@ -0,0 +1,47 @@
1
+ // @ts-check
2
+ import defaultSettings from './defaultSettings';
3
+
4
+ /**
5
+ * @typedef {import('../misc/jsDocTypes').FooterSettings} FooterSettings
6
+ * @typedef {import('../misc/jsDocTypes').Settings} Settings
7
+ * @typedef {import('../misc/jsDocTypes').SettingsInput} SettingsInput
8
+ */
9
+
10
+ /**
11
+ * @param {Settings} settingsToValidate
12
+ */
13
+ function validateSettings(settingsToValidate) {
14
+ if (!settingsToValidate.showTitle && !settingsToValidate.logo) {
15
+ throw new Error('validateSettings: A title must be shown if there is no logo. Please change the `showTitle` setting to be `true` or provide a logo image.');
16
+ }
17
+ }
18
+
19
+ /**
20
+ * settings had to be stored external to settings.js
21
+ * and they had to be global
22
+ * and they couldn't/shouldn't be put on the window variable
23
+ * so wrapped them in an class object
24
+ */
25
+ class SettingsKeeper {
26
+ constructor() {
27
+ this.settings = { ...defaultSettings };
28
+ }
29
+
30
+ /**
31
+ * @param {SettingsInput} settings
32
+ */
33
+ setSettings(settings) {
34
+ const newSettings = { ...defaultSettings, ...this.settings, ...settings };
35
+ validateSettings(newSettings);
36
+ this.settings = newSettings;
37
+ }
38
+
39
+ /**
40
+ * @returns {Settings}
41
+ */
42
+ getSettings() {
43
+ return this.settings;
44
+ }
45
+ }
46
+
47
+ export default new SettingsKeeper();
@@ -0,0 +1,42 @@
1
+ // @ts-check
2
+ /**
3
+ * An XMLHttpRequest that fires callback functions when the request is complete
4
+ * @param {Object} params
5
+ * @param {string} params.url - URL and parameters of the request
6
+ * @param {string} params.method - 'GET' or 'POST' - defaults to 'GET'
7
+ * @param {Object.<string, string>} params.headers - key:value pairs to set request headers
8
+ * @param {number} params.timeout - The timeout length
9
+ * @param {function} params.onResolve - Function to call on a successful request
10
+ * @param {function} params.onReject - Function to call on error or timeout
11
+ */
12
+ export default function httpRequest({
13
+ url,
14
+ method,
15
+ headers,
16
+ timeout,
17
+ onResolve,
18
+ onReject,
19
+ }) {
20
+ // Create the XHR request
21
+ const request = new XMLHttpRequest();
22
+
23
+ request.addEventListener('load', (e) => onResolve(e.target));
24
+ request.addEventListener('error', (e) => onReject(e.target));
25
+ request.addEventListener('timeout', (e) => onReject(e.target));
26
+
27
+ // Setup our HTTP request
28
+ request.open(method || 'GET', url, true);
29
+
30
+ request.withCredentials = true;
31
+
32
+ if (timeout) {
33
+ request.timeout = timeout;
34
+ }
35
+
36
+ if (headers) {
37
+ Object.keys(headers).forEach((key) => request.setRequestHeader(key, headers[key] || ''));
38
+ }
39
+
40
+ // Send the request
41
+ request.send();
42
+ }
@@ -0,0 +1,165 @@
1
+ // @ts-check
2
+ import utahIdUrls from '../enumerations/utahIdUrls';
3
+ import { authChangedEventHandler } from '../renderables/utahId/UtahId';
4
+ import getUtahHeaderSettings from '../settings/getUtahHeaderSettings';
5
+ /**
6
+ * @typedef {import('../misc/jsDocTypes').UtahIdData} UtahIdData
7
+ * @typedef {import('../misc/jsDocTypes').UtahIdFetchStyle} UtahIdFetchStyle
8
+ * @typedef {import('../misc/jsDocTypes').UtahIDSettings} UtahIDSettings
9
+ * @typedef {import('../misc/jsDocTypes').UserInfo} UserInfo
10
+ */
11
+
12
+ /** @enum {UtahIdFetchStyle} */
13
+ const UtahIdFetchStyle = {
14
+ AUTOMATIC: /** @type {UtahIdFetchStyle} */ ('Automatic'),
15
+ NONE: /** @type {UtahIdFetchStyle} */ ('None'),
16
+ PROVIDED: /** @type {UtahIdFetchStyle} */ ('Provided'),
17
+ };
18
+ let lastFetchStyle = UtahIdFetchStyle.NONE;
19
+ /**
20
+ * @param {UtahIDSettings | boolean | undefined} utahIdData
21
+ * @returns {UtahIdFetchStyle}
22
+ */
23
+ function determineFetchStyle(utahIdData) {
24
+ /** @type {UtahIdFetchStyle} */
25
+ let fetchStyle;
26
+ if (utahIdData === true) {
27
+ fetchStyle = UtahIdFetchStyle.AUTOMATIC;
28
+ } else if (utahIdData === false) {
29
+ fetchStyle = UtahIdFetchStyle.NONE;
30
+ } else if (utahIdData === undefined || utahIdData.currentUser === undefined) {
31
+ fetchStyle = UtahIdFetchStyle.AUTOMATIC;
32
+ } else if (utahIdData.currentUser === null || utahIdData.currentUser) {
33
+ fetchStyle = UtahIdFetchStyle.PROVIDED;
34
+ } else {
35
+ throw new Error('determineFetchStyle: Unknown utah id fetch style');
36
+ }
37
+ return fetchStyle;
38
+ }
39
+
40
+ /**
41
+ * @type {UtahIdData}
42
+ */
43
+ const utahIdData = {
44
+ // null = not yet loaded, false = ajaxing, true = have a result (may be error or user data)
45
+ isDefinitive: null,
46
+ lastError: null,
47
+ userInfo: null,
48
+ };
49
+
50
+ /**
51
+ * when auth status changes, call this to notify the world including the Sign In button
52
+ *
53
+ * @param {UtahIdData} newUtahIdData the current information to store
54
+ */
55
+ function maybeTriggerAuthEvent(newUtahIdData) {
56
+ // something asked for new information, so fire off that new information has arrived
57
+ if (newUtahIdData.isDefinitive) {
58
+ // call auth changed so name updates in button
59
+ authChangedEventHandler(newUtahIdData);
60
+
61
+ // give settings callback a crack at the auth change
62
+ const utahId = getUtahHeaderSettings()?.utahId;
63
+ if (typeof utahId === 'object') {
64
+ utahId.onAuthChanged?.(newUtahIdData);
65
+ }
66
+ }
67
+ }
68
+
69
+ // give the application a bit of time to call setUtahHeaderSettings() so that they
70
+ // can tell the header if the application will be controlling the logged in user
71
+ // if the application controls the user, then the user is not fetched from UtahID
72
+ // within this "waitForLaunch" window, the application must call setUtahHeaderSettings()
73
+ // if the current user is not yet known, make sure to set `settings.utahId.currentUser = null`
74
+ // this way the header knows the user is controlled by the app and to not go fetch the user.
75
+ let waitingForLaunch = true;
76
+ const WAIT_FOR_LAUNCH_MS = 500;
77
+
78
+ /** @type {number} */
79
+ let fetchUserTimeoutId = NaN;
80
+
81
+ /**
82
+ * @returns {Promise<UtahIdData>}
83
+ */
84
+ export async function fetchUtahIdUserDataAsync() {
85
+ /** @type {Promise<UtahIdData>} */
86
+ let result = Promise.resolve(utahIdData);
87
+ const settings = getUtahHeaderSettings();
88
+ const fetchStyle = determineFetchStyle(settings.utahId);
89
+
90
+ if (utahIdData.isDefinitive === false) {
91
+ // working on it... come back later...
92
+ result = Promise.resolve(utahIdData);
93
+ } else if (waitingForLaunch) {
94
+ clearTimeout(fetchUserTimeoutId);
95
+ result = new Promise((resolve) => {
96
+ fetchUserTimeoutId = window.setTimeout(
97
+ () => {
98
+ // if the app hasn't called setUtahHeaderSettings() by now, too bad for them...
99
+ waitingForLaunch = false;
100
+ fetchUtahIdUserDataAsync()
101
+ .then((data) => resolve(data))
102
+ // eslint-disable-next-line no-console
103
+ .catch((e) => console.error(e));
104
+ },
105
+ WAIT_FOR_LAUNCH_MS
106
+ );
107
+ });
108
+ } else if (settings.utahId === false) {
109
+ // if utahId is set and currentUser is undefined then the header has control of the user
110
+ // otherwise, if utahId is false then it is turned off
111
+ // otherwise, if utahId is an object and currentUser is not undefined, then the application will control the current user
112
+ // utahId is turned off (probably shouldn't even have gotten here?)
113
+ result = Promise.resolve({
114
+ isDefinitive: true,
115
+ lastError: 'Utah ID is off',
116
+ userInfo: null,
117
+ });
118
+ } else if (settings.utahId === true || settings.utahId?.currentUser === undefined) {
119
+ // 👆 catches true && null cases, both of which allow a refetch 👆
120
+
121
+ // header is "on" OR utahId settings has an `undefined` user: Header controls the user!
122
+ if (fetchStyle !== lastFetchStyle || utahIdData.isDefinitive === null) {
123
+ utahIdData.isDefinitive = false;
124
+ result = fetch(utahIdUrls.USER_INFO, { credentials: 'include' })
125
+ .then((resp) => resp.json())
126
+ .then((authResult) => {
127
+ if (authResult.status === 200) {
128
+ utahIdData.lastError = null;
129
+ utahIdData.userInfo = /** @type {UserInfo} */ (authResult.data);
130
+ } else {
131
+ throw new Error(authResult.err);
132
+ }
133
+ return utahIdData;
134
+ })
135
+ .catch((error) => {
136
+ utahIdData.lastError = error;
137
+ utahIdData.userInfo = null;
138
+ return utahIdData;
139
+ })
140
+ .finally(() => {
141
+ utahIdData.isDefinitive = true;
142
+ maybeTriggerAuthEvent(utahIdData);
143
+ return utahIdData;
144
+ });
145
+ }
146
+ } else {
147
+ // utahId settings have currentUser as null or a user, either way the application will be controlling the user, not the header
148
+ const resultData = {
149
+ isDefinitive: true,
150
+ lastError: null,
151
+ userInfo: settings.utahId?.currentUser,
152
+ };
153
+ result = Promise.resolve(resultData);
154
+ maybeTriggerAuthEvent(resultData);
155
+ }
156
+ lastFetchStyle = fetchStyle;
157
+ return result;
158
+ }
159
+
160
+ /**
161
+ * @returns {UtahIdData}
162
+ */
163
+ export function getCurrentUtahIdData() {
164
+ return utahIdData;
165
+ }