@internetarchive/ia-topnav 1.4.1-alpha-webdev8259.0 → 1.4.1-alpha-webdev8259.1

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 (43) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/data/menus.js.map +1 -1
  4. package/dist/src/dropdown-menu.js +26 -26
  5. package/dist/src/dropdown-menu.js.map +1 -1
  6. package/dist/src/ia-topnav.d.ts +2 -13
  7. package/dist/src/ia-topnav.js +70 -140
  8. package/dist/src/ia-topnav.js.map +1 -1
  9. package/dist/src/lib/keyboard-navigation.js.map +1 -1
  10. package/dist/src/login-button.js +17 -17
  11. package/dist/src/login-button.js.map +1 -1
  12. package/dist/src/media-menu.js +21 -21
  13. package/dist/src/media-menu.js.map +1 -1
  14. package/dist/src/models.d.ts +0 -1
  15. package/dist/src/models.js.map +1 -1
  16. package/dist/src/primary-nav.d.ts +6 -7
  17. package/dist/src/primary-nav.js +98 -142
  18. package/dist/src/primary-nav.js.map +1 -1
  19. package/dist/src/styles/login-button.js +87 -87
  20. package/dist/src/styles/login-button.js.map +1 -1
  21. package/dist/src/styles/primary-nav.js +343 -343
  22. package/dist/src/styles/primary-nav.js.map +1 -1
  23. package/dist/src/user-menu.js +13 -13
  24. package/dist/src/user-menu.js.map +1 -1
  25. package/dist/test/ia-topnav.test.js +15 -87
  26. package/dist/test/ia-topnav.test.js.map +1 -1
  27. package/dist/test/primary-nav.test.js +16 -34
  28. package/dist/test/primary-nav.test.js.map +1 -1
  29. package/index.ts +3 -4
  30. package/package.json +1 -1
  31. package/src/data/menus.ts +652 -652
  32. package/src/dropdown-menu.ts +132 -132
  33. package/src/ia-topnav.ts +301 -383
  34. package/src/lib/keyboard-navigation.ts +166 -166
  35. package/src/login-button.ts +78 -78
  36. package/src/media-menu.ts +143 -143
  37. package/src/models.ts +63 -65
  38. package/src/primary-nav.ts +277 -324
  39. package/src/styles/login-button.ts +90 -90
  40. package/src/styles/primary-nav.ts +346 -346
  41. package/src/user-menu.ts +32 -32
  42. package/test/ia-topnav.test.ts +282 -381
  43. package/test/primary-nav.test.ts +136 -163
@@ -1,324 +1,277 @@
1
- import { html, nothing, PropertyValues } from 'lit';
2
- import TrackedElement from './tracked-element';
3
- import icons from './assets/img/icons';
4
- import './assets/img/hamburger';
5
- import './login-button';
6
- import './nav-search';
7
- import './media-menu';
8
- import logoWordmarkStacked from './assets/img/wordmark-stacked';
9
- import primaryNavCSS from './styles/primary-nav';
10
- import locationHandler from './lib/location-handler';
11
- import formatUrl from './lib/format-url';
12
- import { customElement, property } from 'lit/decorators.js';
13
- import {
14
- IATopNavConfig,
15
- IATopNavSearchSlotMode,
16
- IATopNavSecondIdentitySlotMode,
17
- } from './models';
18
- import { defaultTopNavConfig } from './data/menus';
19
-
20
- @customElement('primary-nav')
21
- export class PrimaryNav extends TrackedElement {
22
- @property({ type: String }) mediaBaseHost = 'https://archive.org';
23
- @property({ type: String }) baseHost = '';
24
- @property({ type: Boolean }) hideSearch = false;
25
- @property({ type: Object }) config: IATopNavConfig = defaultTopNavConfig;
26
- @property({ type: String }) openMenu = '';
27
- @property({ type: String }) screenName = '';
28
- @property({ type: String }) searchIn = '';
29
- @property({ type: String }) searchQuery = '';
30
- @property({ type: String })
31
- searchSlotMode: IATopNavSearchSlotMode = '';
32
- @property({ type: String })
33
- secondIdentitySlotMode: IATopNavSecondIdentitySlotMode = '';
34
- @property({ type: String }) selectedMenuOption = '';
35
- @property({ type: Boolean }) signedOutMenuOpen = false;
36
- @property({ type: Boolean }) userMenuOpen = false;
37
- @property({ type: Boolean }) mediaMenuAnimate = false;
38
- @property({ type: String }) username = '';
39
- @property({ type: String }) userProfileImagePath = '';
40
- @property({ type: Object }) currentTab:
41
- | { mediatype: string; moveTo: string }
42
- | undefined;
43
- signedOutMenuToggled: unknown;
44
-
45
- static get styles() {
46
- return primaryNavCSS;
47
- }
48
-
49
- toggleMediaMenu(e: Event) {
50
- this.trackClick(e);
51
- this.dispatchEvent(
52
- new CustomEvent('menuToggled', {
53
- detail: {
54
- menuName: 'media',
55
- },
56
- }),
57
- );
58
- }
59
-
60
- toggleSearchMenu(e: Event) {
61
- this.trackClick(e);
62
- this.dispatchEvent(
63
- new CustomEvent('menuToggled', {
64
- detail: {
65
- menuName: 'search',
66
- },
67
- }),
68
- );
69
- }
70
-
71
- toggleUserMenu(e: Event) {
72
- this.trackClick(e);
73
- this.dispatchEvent(
74
- new CustomEvent('menuToggled', {
75
- detail: {
76
- menuName: 'user',
77
- },
78
- }),
79
- );
80
- }
81
-
82
- updated(props: PropertyValues) {
83
- if (props.has('currentTab')) {
84
- // early return
85
- if (!this.currentTab || Object.keys(this.currentTab).length === 0)
86
- return nothing;
87
-
88
- const isUserMenuTab =
89
- this.currentTab && this.currentTab.mediatype === 'usermenu';
90
- if (isUserMenuTab) {
91
- const mediaButtons = Array.from(
92
- this.shadowRoot
93
- ?.querySelector('media-menu')
94
- ?.shadowRoot?.querySelectorAll('media-button') ?? [],
95
- );
96
- const lastMediaButton = mediaButtons.filter((element) => {
97
- return element.shadowRoot
98
- ?.querySelector('a')
99
- ?.classList.contains('images');
100
- });
101
-
102
- let nextElement;
103
- if (this.username) {
104
- nextElement = this.shadowRoot?.querySelector('a.upload');
105
- } else {
106
- nextElement = this.shadowRoot
107
- ?.querySelector('login-button')
108
- ?.shadowRoot?.querySelector('span a');
109
- }
110
-
111
- const menuItemElement =
112
- lastMediaButton[0]?.shadowRoot?.querySelector('a.menu-item');
113
-
114
- const focusElement =
115
- this.currentTab.moveTo === 'next' ? nextElement : menuItemElement;
116
-
117
- if (focusElement) {
118
- (focusElement as HTMLElement).focus();
119
- }
120
- } else if (this.currentTab.moveTo === 'next') {
121
- if (this.shadowRoot?.querySelector('.user-menu')) {
122
- (this.shadowRoot?.querySelector('.user-menu') as HTMLElement).focus();
123
- } else {
124
- (
125
- this.shadowRoot
126
- ?.querySelector('login-button')
127
- ?.shadowRoot?.querySelectorAll('span a')[0] as HTMLElement
128
- )?.focus();
129
- }
130
- }
131
- }
132
- }
133
-
134
- get userIcon() {
135
- const userMenuClass = this.openMenu === 'user' ? 'active' : '';
136
- const userMenuToolTip =
137
- this.openMenu === 'user' ? 'Close user menu' : 'Expand user menu';
138
-
139
- return html`
140
- <button
141
- class="user-menu ${userMenuClass}"
142
- title="${userMenuToolTip}"
143
- @click="${this.toggleUserMenu}"
144
- data-event-click-tracking="${this.config?.eventCategory}|NavUserMenu"
145
- >
146
- <img
147
- src="${this.mediaBaseHost}${this.userProfileImagePath}"
148
- alt="Profile picture for ${this.screenName}"
149
- />
150
- <span class="screen-name" dir="auto">${this.screenName}</span>
151
- </button>
152
- `;
153
- }
154
-
155
- get loginIcon() {
156
- return html`
157
- <login-button
158
- .baseHost=${this.baseHost}
159
- .config=${this.config}
160
- .dropdownOpen=${this.signedOutMenuOpen}
161
- .openMenu=${this.openMenu}
162
- @signedOutMenuToggled=${this.signedOutMenuToggled}
163
- ></login-button>
164
- `;
165
- }
166
-
167
- get searchMenuOpen() {
168
- return this.openMenu === 'search';
169
- }
170
-
171
- get useCustomSearch() {
172
- return this.searchSlotMode === 'custom';
173
- }
174
-
175
- get allowSecondaryIcon() {
176
- return this.secondIdentitySlotMode === 'allow';
177
- }
178
-
179
- get searchMenu() {
180
- if (this.hideSearch) return nothing;
181
-
182
- if (this.useCustomSearch) {
183
- return html`
184
- <button
185
- class="search-trigger"
186
- @click="${this.toggleSearchMenu}"
187
- data-event-click-tracking="${this.config
188
- ?.eventCategory}|NavSearchOpen"
189
- >
190
- ${icons.search}
191
- </button>
192
- <div
193
- class="custom-search-container ${this.searchMenuOpen ? 'open' : ''}"
194
- >
195
- <slot name="custom-search"></slot>
196
- </div>
197
- `;
198
- }
199
-
200
- return html`
201
- <button
202
- class="search-trigger"
203
- @click="${this.toggleSearchMenu}"
204
- data-event-click-tracking="${this.config?.eventCategory}|NavSearchOpen"
205
- >
206
- ${icons.search}
207
- </button>
208
- <nav-search
209
- .baseHost=${this.baseHost}
210
- .config=${this.config}
211
- .locationHandler=${locationHandler}
212
- .open=${this.searchMenuOpen}
213
- .openMenu=${this.openMenu}
214
- .searchIn=${this.searchIn}
215
- .searchQuery=${this.searchQuery}
216
- @blur=${this.emitNavSearchBlurEvent}
217
- ></nav-search>
218
- `;
219
- }
220
-
221
- private emitNavSearchBlurEvent(e: FocusEvent) {
222
- const relatedTarget = e.relatedTarget as HTMLElement;
223
- const isUploadButton = relatedTarget?.classList.contains('upload');
224
-
225
- if (isUploadButton) {
226
- (this.shadowRoot?.querySelector('a.upload') as HTMLElement).focus();
227
- }
228
-
229
- this.dispatchEvent(
230
- new CustomEvent('navSearchBlur', {
231
- detail: {
232
- isUploadButton: !!isUploadButton,
233
- },
234
- bubbles: true,
235
- composed: true,
236
- }),
237
- );
238
- }
239
-
240
- get mobileDonateHeart() {
241
- return html`
242
- <a
243
- class="mobile-donate-link"
244
- .href=${formatUrl(
245
- '/donate/?origin=iawww-mbhrt' as string & Location,
246
- this.baseHost,
247
- )}
248
- >
249
- ${icons.donateUnpadded}
250
- <span class="sr-only">"Donate to the archive"</span>
251
- </a>
252
- `;
253
- }
254
-
255
- get uploadButtonTemplate() {
256
- return html` <a
257
- .href="${formatUrl('/upload' as string & Location, this.baseHost)}"
258
- class="upload"
259
- @focus=${this.toggleMediaMenu}
260
- >
261
- ${icons.upload}
262
- <span>Upload</span>
263
- </a>`;
264
- }
265
-
266
- get userStateTemplate() {
267
- return html`<div class="user-info">
268
- ${this.username ? this.userIcon : this.loginIcon}
269
- </div>`;
270
- }
271
-
272
- get secondLogoSlot() {
273
- return this.allowSecondaryIcon
274
- ? html`
275
- <slot name="opt-sec-logo"></slot>
276
- <slot name="opt-sec-logo-mobile"></slot>
277
- `
278
- : nothing;
279
- }
280
-
281
- get secondLogoClass() {
282
- return this.allowSecondaryIcon ? 'second-logo' : '';
283
- }
284
-
285
- render() {
286
- // const mediaMenuTabIndex = this.openMenu === 'media' ? '' : '-1';
287
- return html`
288
- <nav class=${this.hideSearch ? 'hide-search' : ''}>
289
- <button
290
- class="hamburger"
291
- @click="${this.toggleMediaMenu}"
292
- data-event-click-tracking="${this.config?.eventCategory}|NavHamburger"
293
- title="Open main menu"
294
- >
295
- <icon-hamburger ?active=${this.openMenu === 'media'}></icon-hamburger>
296
- </button>
297
-
298
- <div class=${`branding ${this.secondLogoClass}`}>
299
- <a
300
- .href=${formatUrl('/' as string & Location, this.baseHost)}
301
- @click=${this.trackClick}
302
- data-event-click-tracking="${this.config?.eventCategory}|NavHome"
303
- title="Go home"
304
- class="link-home"
305
- >${icons.iaLogo}${logoWordmarkStacked}</a
306
- >
307
- ${this.secondLogoSlot}
308
- </div>
309
- <media-menu
310
- .baseHost=${this.baseHost}
311
- .config=${this.config}
312
- ?mediaMenuAnimate=${this.mediaMenuAnimate}
313
- .selectedMenuOption=${this.selectedMenuOption}
314
- .openMenu=${this.openMenu}
315
- .currentTab=${this.currentTab}
316
- ></media-menu>
317
- <div class="right-side-section">
318
- ${this.mobileDonateHeart} ${this.userStateTemplate}
319
- ${this.uploadButtonTemplate} ${this.searchMenu}
320
- </div>
321
- </nav>
322
- `;
323
- }
324
- }
1
+ import { html, nothing, PropertyValues } from 'lit';
2
+ import TrackedElement from './tracked-element';
3
+ import icons from './assets/img/icons';
4
+ import './assets/img/hamburger';
5
+ import './login-button';
6
+ import './media-menu';
7
+ import logoWordmarkStacked from './assets/img/wordmark-stacked';
8
+ import primaryNavCSS from './styles/primary-nav';
9
+ import formatUrl from './lib/format-url';
10
+ import { customElement, property } from 'lit/decorators.js';
11
+ import { IATopNavConfig, IATopNavSecondIdentitySlotMode } from './models';
12
+ import { defaultTopNavConfig } from './data/menus';
13
+
14
+ @customElement('primary-nav')
15
+ export class PrimaryNav extends TrackedElement {
16
+ @property({ type: String }) mediaBaseHost = 'https://archive.org';
17
+ @property({ type: String }) baseHost = '';
18
+ @property({ type: Boolean }) hideSearch = false;
19
+ @property({ type: Object }) config: IATopNavConfig = defaultTopNavConfig;
20
+ @property({ type: String }) openMenu = '';
21
+ @property({ type: String }) screenName = '';
22
+ @property({ type: String })
23
+ secondIdentitySlotMode: IATopNavSecondIdentitySlotMode = '';
24
+ @property({ type: String }) selectedMenuOption = '';
25
+ @property({ type: Boolean }) signedOutMenuOpen = false;
26
+ @property({ type: Boolean }) userMenuOpen = false;
27
+ @property({ type: Boolean }) mediaMenuAnimate = false;
28
+ @property({ type: String }) username = '';
29
+ @property({ type: String }) userProfileImagePath = '';
30
+ @property({ type: Object }) currentTab:
31
+ | { mediatype: string; moveTo: string }
32
+ | undefined;
33
+ signedOutMenuToggled: unknown;
34
+
35
+ static get styles() {
36
+ return primaryNavCSS;
37
+ }
38
+
39
+ toggleMediaMenu(e: Event) {
40
+ this.trackClick(e);
41
+ this.dispatchEvent(
42
+ new CustomEvent('menuToggled', {
43
+ detail: {
44
+ menuName: 'media',
45
+ },
46
+ }),
47
+ );
48
+ }
49
+
50
+ toggleSearchMenu(e: Event) {
51
+ this.trackClick(e);
52
+ this.dispatchEvent(
53
+ new CustomEvent('menuToggled', {
54
+ detail: {
55
+ menuName: 'search',
56
+ },
57
+ }),
58
+ );
59
+ }
60
+
61
+ toggleUserMenu(e: Event) {
62
+ this.trackClick(e);
63
+ this.dispatchEvent(
64
+ new CustomEvent('menuToggled', {
65
+ detail: {
66
+ menuName: 'user',
67
+ },
68
+ }),
69
+ );
70
+ }
71
+
72
+ updated(props: PropertyValues) {
73
+ if (props.has('currentTab')) {
74
+ // early return
75
+ if (!this.currentTab || Object.keys(this.currentTab).length === 0)
76
+ return nothing;
77
+
78
+ const isUserMenuTab =
79
+ this.currentTab && this.currentTab.mediatype === 'usermenu';
80
+ if (isUserMenuTab) {
81
+ const mediaButtons = Array.from(
82
+ this.shadowRoot
83
+ ?.querySelector('media-menu')
84
+ ?.shadowRoot?.querySelectorAll('media-button') ?? [],
85
+ );
86
+ const lastMediaButton = mediaButtons.filter((element) => {
87
+ return element.shadowRoot
88
+ ?.querySelector('a')
89
+ ?.classList.contains('images');
90
+ });
91
+
92
+ let nextElement;
93
+ if (this.username) {
94
+ nextElement = this.shadowRoot?.querySelector('a.upload');
95
+ } else {
96
+ nextElement = this.shadowRoot
97
+ ?.querySelector('login-button')
98
+ ?.shadowRoot?.querySelector('span a');
99
+ }
100
+
101
+ const menuItemElement =
102
+ lastMediaButton[0]?.shadowRoot?.querySelector('a.menu-item');
103
+
104
+ const focusElement =
105
+ this.currentTab.moveTo === 'next' ? nextElement : menuItemElement;
106
+
107
+ if (focusElement) {
108
+ (focusElement as HTMLElement).focus();
109
+ }
110
+ } else if (this.currentTab.moveTo === 'next') {
111
+ if (this.shadowRoot?.querySelector('.user-menu')) {
112
+ (this.shadowRoot?.querySelector('.user-menu') as HTMLElement).focus();
113
+ } else {
114
+ (
115
+ this.shadowRoot
116
+ ?.querySelector('login-button')
117
+ ?.shadowRoot?.querySelectorAll('span a')[0] as HTMLElement
118
+ )?.focus();
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ get userIcon() {
125
+ const userMenuClass = this.openMenu === 'user' ? 'active' : '';
126
+ const userMenuToolTip =
127
+ this.openMenu === 'user' ? 'Close user menu' : 'Expand user menu';
128
+
129
+ return html`
130
+ <button
131
+ class="user-menu ${userMenuClass}"
132
+ title="${userMenuToolTip}"
133
+ @click="${this.toggleUserMenu}"
134
+ data-event-click-tracking="${this.config?.eventCategory}|NavUserMenu"
135
+ >
136
+ <img
137
+ src="${this.mediaBaseHost}${this.userProfileImagePath}"
138
+ alt="Profile picture for ${this.screenName}"
139
+ />
140
+ <span class="screen-name" dir="auto">${this.screenName}</span>
141
+ </button>
142
+ `;
143
+ }
144
+
145
+ get loginIcon() {
146
+ return html`
147
+ <login-button
148
+ .baseHost=${this.baseHost}
149
+ .config=${this.config}
150
+ .dropdownOpen=${this.signedOutMenuOpen}
151
+ .openMenu=${this.openMenu}
152
+ @signedOutMenuToggled=${this.signedOutMenuToggled}
153
+ ></login-button>
154
+ `;
155
+ }
156
+
157
+ get searchMenuOpen() {
158
+ return this.openMenu === 'search';
159
+ }
160
+
161
+ get allowSecondaryIcon() {
162
+ return this.secondIdentitySlotMode === 'allow';
163
+ }
164
+
165
+ /**
166
+ * The search slot container, rendered between media-menu and
167
+ * right-side-section so it sits left of the Upload button on desktop.
168
+ */
169
+ get searchSlotContainer() {
170
+ if (this.hideSearch) return nothing;
171
+ return html`
172
+ <div class="custom-search-container ${this.searchMenuOpen ? 'open' : ''}">
173
+ <slot name="custom-search"></slot>
174
+ </div>
175
+ `;
176
+ }
177
+
178
+ get searchMenu() {
179
+ if (this.hideSearch) return nothing;
180
+
181
+ return html`
182
+ <button
183
+ class="search-trigger"
184
+ @click="${this.toggleSearchMenu}"
185
+ data-event-click-tracking="${this.config?.eventCategory}|NavSearchOpen"
186
+ >
187
+ ${icons.search}
188
+ </button>
189
+ `;
190
+ }
191
+
192
+ get mobileDonateHeart() {
193
+ return html`
194
+ <a
195
+ class="mobile-donate-link"
196
+ .href=${formatUrl(
197
+ '/donate/?origin=iawww-mbhrt' as string & Location,
198
+ this.baseHost,
199
+ )}
200
+ >
201
+ ${icons.donateUnpadded}
202
+ <span class="sr-only">"Donate to the archive"</span>
203
+ </a>
204
+ `;
205
+ }
206
+
207
+ get uploadButtonTemplate() {
208
+ return html` <a
209
+ .href="${formatUrl('/upload' as string & Location, this.baseHost)}"
210
+ class="upload"
211
+ @focus=${this.toggleMediaMenu}
212
+ >
213
+ ${icons.upload}
214
+ <span>Upload</span>
215
+ </a>`;
216
+ }
217
+
218
+ get userStateTemplate() {
219
+ return html`<div class="user-info">
220
+ ${this.username ? this.userIcon : this.loginIcon}
221
+ </div>`;
222
+ }
223
+
224
+ get secondLogoSlot() {
225
+ return this.allowSecondaryIcon
226
+ ? html`
227
+ <slot name="opt-sec-logo"></slot>
228
+ <slot name="opt-sec-logo-mobile"></slot>
229
+ `
230
+ : nothing;
231
+ }
232
+
233
+ get secondLogoClass() {
234
+ return this.allowSecondaryIcon ? 'second-logo' : '';
235
+ }
236
+
237
+ render() {
238
+ // const mediaMenuTabIndex = this.openMenu === 'media' ? '' : '-1';
239
+ return html`
240
+ <nav class=${this.hideSearch ? 'hide-search' : ''}>
241
+ <button
242
+ class="hamburger"
243
+ @click="${this.toggleMediaMenu}"
244
+ data-event-click-tracking="${this.config?.eventCategory}|NavHamburger"
245
+ title="Open main menu"
246
+ >
247
+ <icon-hamburger ?active=${this.openMenu === 'media'}></icon-hamburger>
248
+ </button>
249
+
250
+ <div class=${`branding ${this.secondLogoClass}`}>
251
+ <a
252
+ .href=${formatUrl('/' as string & Location, this.baseHost)}
253
+ @click=${this.trackClick}
254
+ data-event-click-tracking="${this.config?.eventCategory}|NavHome"
255
+ title="Go home"
256
+ class="link-home"
257
+ >${icons.iaLogo}${logoWordmarkStacked}</a
258
+ >
259
+ ${this.secondLogoSlot}
260
+ </div>
261
+ <media-menu
262
+ .baseHost=${this.baseHost}
263
+ .config=${this.config}
264
+ ?mediaMenuAnimate=${this.mediaMenuAnimate}
265
+ .selectedMenuOption=${this.selectedMenuOption}
266
+ .openMenu=${this.openMenu}
267
+ .currentTab=${this.currentTab}
268
+ ></media-menu>
269
+ ${this.searchSlotContainer}
270
+ <div class="right-side-section">
271
+ ${this.mobileDonateHeart} ${this.userStateTemplate}
272
+ ${this.uploadButtonTemplate} ${this.searchMenu}
273
+ </div>
274
+ </nav>
275
+ `;
276
+ }
277
+ }