@internetarchive/ia-topnav 1.4.0 → 1.4.1-alpha-webdev8259.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 (53) hide show
  1. package/.prettierignore +1 -1
  2. package/LICENSE +661 -661
  3. package/README.md +147 -147
  4. package/demo/index.html +28 -28
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/src/data/menus.js.map +1 -1
  8. package/dist/src/dropdown-menu.js +26 -26
  9. package/dist/src/dropdown-menu.js.map +1 -1
  10. package/dist/src/ia-topnav.d.ts +4 -1
  11. package/dist/src/ia-topnav.js +96 -81
  12. package/dist/src/ia-topnav.js.map +1 -1
  13. package/dist/src/lib/keyboard-navigation.js.map +1 -1
  14. package/dist/src/login-button.js +17 -17
  15. package/dist/src/login-button.js.map +1 -1
  16. package/dist/src/media-menu.js +21 -21
  17. package/dist/src/media-menu.js.map +1 -1
  18. package/dist/src/models.d.ts +1 -0
  19. package/dist/src/models.js.map +1 -1
  20. package/dist/src/primary-nav.d.ts +3 -1
  21. package/dist/src/primary-nav.js +118 -95
  22. package/dist/src/primary-nav.js.map +1 -1
  23. package/dist/src/styles/login-button.js +87 -87
  24. package/dist/src/styles/login-button.js.map +1 -1
  25. package/dist/src/styles/primary-nav.js +343 -308
  26. package/dist/src/styles/primary-nav.js.map +1 -1
  27. package/dist/src/user-menu.js +13 -13
  28. package/dist/src/user-menu.js.map +1 -1
  29. package/dist/test/ia-topnav.test.js +39 -9
  30. package/dist/test/ia-topnav.test.js.map +1 -1
  31. package/dist/test/primary-nav.test.js +55 -7
  32. package/dist/test/primary-nav.test.js.map +1 -1
  33. package/eslint.config.mjs +53 -53
  34. package/index.ts +4 -3
  35. package/package.json +72 -72
  36. package/prettier.config.js +9 -9
  37. package/src/data/menus.ts +652 -652
  38. package/src/dropdown-menu.ts +132 -132
  39. package/src/ia-topnav.ts +383 -366
  40. package/src/lib/keyboard-navigation.ts +166 -166
  41. package/src/login-button.ts +78 -78
  42. package/src/media-menu.ts +143 -143
  43. package/src/models.ts +65 -63
  44. package/src/primary-nav.ts +324 -296
  45. package/src/styles/login-button.ts +90 -90
  46. package/src/styles/primary-nav.ts +346 -311
  47. package/src/user-menu.ts +32 -32
  48. package/ssl/server.key +28 -28
  49. package/test/ia-topnav.test.ts +381 -343
  50. package/test/primary-nav.test.ts +163 -94
  51. package/tsconfig.json +31 -31
  52. package/web-dev-server.config.mjs +32 -32
  53. package/web-test-runner.config.mjs +41 -41
package/src/ia-topnav.ts CHANGED
@@ -1,366 +1,383 @@
1
- import { LitElement, PropertyValues, html, nothing } from 'lit';
2
- import { customElement, property, state } from 'lit/decorators.js';
3
-
4
- import { buildTopNavMenus, defaultTopNavConfig } from './data/menus';
5
- import './desktop-subnav';
6
- import './dropdown-menu';
7
- import './media-slider';
8
- import {
9
- IATopNavConfig,
10
- IATopNavMenuConfig,
11
- IATopNavSecondIdentitySlotMode,
12
- } from './models';
13
- import './primary-nav';
14
- import './search-menu';
15
- import './signed-out-dropdown';
16
- import iaTopNavCSS from './styles/ia-topnav';
17
- import './user-menu';
18
- import KeyboardNavigation from './lib/keyboard-navigation';
19
-
20
- @customElement('ia-topnav')
21
- export class IATopNav extends LitElement {
22
- @property({ type: Boolean }) localLinks = false;
23
-
24
- @property({ type: String }) waybackPagesArchived = '';
25
-
26
- @property({ type: String }) baseHost = 'https://archive.org';
27
-
28
- @property({ type: String }) mediaBaseHost = 'https://archive.org';
29
-
30
- @property({ type: Boolean }) admin = false;
31
-
32
- @property({ type: Boolean }) canManageFlags = false;
33
-
34
- @property({ type: Object }) config: IATopNavConfig = defaultTopNavConfig;
35
-
36
- @property({ type: Boolean }) hideSearch = false;
37
-
38
- @property({ type: String }) itemIdentifier = '';
39
-
40
- @property({ type: Boolean }) mediaSliderOpen = false;
41
-
42
- @property({ type: String }) openMenu = '';
43
-
44
- @property({ type: String }) screenName: string = '';
45
-
46
- @property({ type: String }) searchIn = '';
47
-
48
- @property({ type: String }) searchQuery = '';
49
-
50
- @property({ type: String }) selectedMenuOption = '';
51
-
52
- @property({ type: String }) username: string = '';
53
-
54
- @property({ type: String }) userProfileImagePath =
55
- '/services/img/user/profile';
56
-
57
- @property({ type: String })
58
- secondIdentitySlotMode: IATopNavSecondIdentitySlotMode = '';
59
-
60
- @property({ type: Object }) currentTab?: {
61
- mediatype: string;
62
- moveTo: string;
63
- };
64
-
65
- @state() private menus: IATopNavMenuConfig = buildTopNavMenus();
66
- private previousKeydownListener: // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
- ((this: HTMLElement, ev: KeyboardEvent) => any) | undefined;
68
-
69
- private keyboardNavigation?: KeyboardNavigation;
70
-
71
- private get normalizedBaseHost() {
72
- return !this.localLinks ? this.baseHost : '';
73
- }
74
-
75
- static get styles() {
76
- return iaTopNavCSS;
77
- }
78
-
79
- updated(props: PropertyValues) {
80
- if (
81
- props.has('username') ||
82
- props.has('waybackPagesArchived') ||
83
- props.has('itemIdentifier') ||
84
- props.has('localLinks') ||
85
- props.has('baseHost')
86
- ) {
87
- this.menuSetup();
88
- }
89
-
90
- if (this.openMenu === 'search') {
91
- const targetElement =
92
- this.renderRoot.querySelector('search-menu')?.shadowRoot;
93
- if (targetElement) {
94
- this.keyboardNavigation = new KeyboardNavigation(
95
- targetElement as unknown as HTMLElement,
96
- this.openMenu,
97
- );
98
-
99
- if (this.previousKeydownListener) {
100
- this.removeEventListener('keydown', this.previousKeydownListener);
101
- }
102
- this.addEventListener('keydown', this.keyboardNavigation.handleKeyDown);
103
- this.previousKeydownListener = this.keyboardNavigation.handleKeyDown;
104
- }
105
- }
106
- }
107
-
108
- firstUpdated() {
109
- // close open menu on `esc` click
110
- document.addEventListener(
111
- 'keydown',
112
- (e) => {
113
- if (e.key === 'Escape') {
114
- this.openMenu = '';
115
- this.mediaSliderOpen = false;
116
- }
117
- },
118
- false,
119
- );
120
- }
121
-
122
- menuSetup() {
123
- // re/build the nav
124
- this.menus = buildTopNavMenus(
125
- this.username,
126
- this.normalizedBaseHost,
127
- this.waybackPagesArchived,
128
- this.itemIdentifier,
129
- );
130
- }
131
-
132
- menuToggled(e: CustomEvent) {
133
- const currentMenu = this.openMenu;
134
- this.openMenu = currentMenu === e.detail.menuName ? '' : e.detail.menuName;
135
- // Keeps media slider open if media menu is open
136
- if (this.openMenu === 'media') {
137
- return;
138
- }
139
- this.closeMediaSlider();
140
- }
141
-
142
- navSearchBlurEvent(e: CustomEvent) {
143
- if (this.previousKeydownListener) {
144
- this.removeEventListener('keydown', this.previousKeydownListener);
145
- }
146
-
147
- const isUploadButton = e.detail?.isUploadButton;
148
- if (!isUploadButton) {
149
- const searchMenu = this.renderRoot.querySelector(
150
- 'search-menu',
151
- ) as HTMLElement;
152
- const elements = this.keyboardNavigation?.getFocusableElements();
153
- if (searchMenu && elements?.length) {
154
- elements[0].focus();
155
- }
156
- }
157
- }
158
-
159
- openMediaSlider() {
160
- this.mediaSliderOpen = true;
161
- }
162
-
163
- closeMediaSlider() {
164
- this.mediaSliderOpen = false;
165
- this.selectedMenuOption = '';
166
- }
167
-
168
- closeMenus() {
169
- this.openMenu = '';
170
- this.closeMediaSlider();
171
- }
172
-
173
- searchInChanged(e: CustomEvent) {
174
- this.searchIn = e.detail.searchIn;
175
- }
176
-
177
- trackClick(e: CustomEvent) {
178
- this.dispatchEvent(
179
- new CustomEvent('analyticsClick', {
180
- bubbles: true,
181
- composed: true,
182
- detail: e.detail,
183
- }),
184
- );
185
- }
186
-
187
- trackSubmit(e: CustomEvent) {
188
- this.dispatchEvent(
189
- new CustomEvent('analyticsSubmit', {
190
- bubbles: true,
191
- composed: true,
192
- detail: e.detail,
193
- }),
194
- );
195
- }
196
-
197
- mediaTypeSelected(e: CustomEvent) {
198
- if (this.selectedMenuOption === e.detail.mediatype) {
199
- this.closeMediaSlider();
200
- return;
201
- }
202
- this.selectedMenuOption = e.detail.mediatype;
203
- this.openMediaSlider();
204
- }
205
-
206
- get searchMenuOpened() {
207
- return this.openMenu === 'search';
208
- }
209
-
210
- get signedOutOpened() {
211
- return this.openMenu === 'login';
212
- }
213
-
214
- get userMenuOpened() {
215
- return this.openMenu === 'user';
216
- }
217
-
218
- get searchMenuTabIndex() {
219
- return this.searchMenuOpened ? '' : '-1';
220
- }
221
-
222
- get userMenuTabIndex() {
223
- return this.userMenuOpened ? '' : '-1';
224
- }
225
-
226
- get signedOutTabIndex() {
227
- return this.signedOutOpened ? '' : '-1';
228
- }
229
-
230
- get closeLayerClass() {
231
- return !!this.openMenu || this.mediaSliderOpen ? 'visible' : '';
232
- }
233
-
234
- get userMenu() {
235
- return html`
236
- <user-menu
237
- .baseHost=${this.normalizedBaseHost}
238
- .config=${this.config}
239
- .menuItems=${this.userMenuItems}
240
- ?open=${this.openMenu === 'user'}
241
- .username=${this.username}
242
- ?hideSearch=${this.hideSearch}
243
- tabindex="${this.userMenuTabIndex}"
244
- @menuToggled=${this.menuToggled}
245
- @trackClick=${this.trackClick}
246
- @focusToOtherMenuItem=${(e: CustomEvent) =>
247
- (this.currentTab = e.detail)}
248
- ></user-menu>
249
- `;
250
- }
251
-
252
- get signedOutDropdown() {
253
- return html`
254
- <signed-out-dropdown
255
- .baseHost=${this.normalizedBaseHost}
256
- .config=${this.config}
257
- .open=${this.signedOutOpened}
258
- ?hideSearch=${this.hideSearch}
259
- tabindex="${this.signedOutTabIndex}"
260
- .menuItems=${this.signedOutMenuItems}
261
- @focusToOtherMenuItem=${(e: CustomEvent) => {
262
- this.currentTab = e.detail;
263
- }}
264
- ></signed-out-dropdown>
265
- `;
266
- }
267
-
268
- get signedOutMenuItems() {
269
- return this.menus.signedOut;
270
- }
271
-
272
- /**
273
- * Most users just get the basic menu items.
274
- * For users with `/items` priv, additional admin menu items are included too.
275
- * Having the `/flags` priv adds a further admin item for managing flags.
276
- */
277
- get userMenuItems() {
278
- const basicItems = this.menus.user;
279
-
280
- let adminItems = this.menus.userAdmin;
281
- if (this.canManageFlags) {
282
- adminItems = adminItems.concat(this.menus.userAdminFlags);
283
- }
284
-
285
- return this.itemIdentifier && this.admin
286
- ? [basicItems, adminItems]
287
- : [basicItems];
288
- }
289
-
290
- get allowSecondaryIcon() {
291
- return this.secondIdentitySlotMode === 'allow';
292
- }
293
-
294
- get secondLogoSlot() {
295
- return this.allowSecondaryIcon
296
- ? html`
297
- <slot name="opt-sec-logo" slot="opt-sec-logo"></slot>
298
- <slot name="opt-sec-logo-mobile" slot="opt-sec-logo-mobile"></slot>
299
- `
300
- : nothing;
301
- }
302
-
303
- get separatorTemplate() {
304
- return html`<li class="divider" role="presentation"></li>`;
305
- }
306
-
307
- render() {
308
- return html`
309
- <div class="topnav">
310
- <primary-nav
311
- .baseHost=${this.normalizedBaseHost}
312
- .mediaBaseHost=${this.mediaBaseHost}
313
- .config=${this.config}
314
- .openMenu=${this.openMenu}
315
- .screenName=${this.screenName}
316
- .searchIn=${this.searchIn}
317
- .searchQuery=${this.searchQuery}
318
- .secondIdentitySlotMode=${this.secondIdentitySlotMode}
319
- .selectedMenuOption=${this.selectedMenuOption}
320
- .username=${this.username}
321
- .userProfileImagePath=${this.userProfileImagePath}
322
- .currentTab=${this.currentTab}
323
- ?hideSearch=${this.hideSearch}
324
- @mediaTypeSelected=${this.mediaTypeSelected}
325
- @trackClick=${this.trackClick}
326
- @trackSubmit=${this.trackSubmit}
327
- @menuToggled=${this.menuToggled}
328
- @navSearchBlur=${this.navSearchBlurEvent}
329
- >
330
- ${this.secondLogoSlot}
331
- </primary-nav>
332
- <media-slider
333
- .baseHost=${this.normalizedBaseHost}
334
- .config=${this.config}
335
- .selectedMenuOption=${this.selectedMenuOption}
336
- .mediaSliderOpen=${this.mediaSliderOpen}
337
- .menus=${this.menus}
338
- tabindex="${this.mediaSliderOpen ? '1' : '-1'}"
339
- @focusToOtherMenuItem=${(e: CustomEvent) =>
340
- (this.currentTab = e.detail)}
341
- ></media-slider>
342
- </div>
343
- ${this.username ? this.userMenu : this.signedOutDropdown}
344
- <search-menu
345
- .baseHost=${this.normalizedBaseHost}
346
- .config=${this.config}
347
- .openMenu=${this.openMenu}
348
- tabindex="${this.searchMenuTabIndex}"
349
- ?hideSearch=${this.hideSearch}
350
- @searchInChanged=${this.searchInChanged}
351
- @trackClick=${this.trackClick}
352
- @trackSubmit=${this.trackSubmit}
353
- ></search-menu>
354
- <desktop-subnav
355
- .baseHost=${this.normalizedBaseHost}
356
- .menuItems=${this.menus.more.links}
357
- @focus=${this.closeMenus}
358
- ></desktop-subnav>
359
- <div
360
- id="close-layer"
361
- class="${this.closeLayerClass}"
362
- @click=${this.closeMenus}
363
- ></div>
364
- `;
365
- }
366
- }
1
+ import { LitElement, PropertyValues, html, nothing } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+
4
+ import { buildTopNavMenus, defaultTopNavConfig } from './data/menus';
5
+ import './desktop-subnav';
6
+ import './dropdown-menu';
7
+ import './media-slider';
8
+ import {
9
+ IATopNavConfig,
10
+ IATopNavMenuConfig,
11
+ IATopNavSearchSlotMode,
12
+ IATopNavSecondIdentitySlotMode,
13
+ } from './models';
14
+ import './primary-nav';
15
+ import './search-menu';
16
+ import './signed-out-dropdown';
17
+ import iaTopNavCSS from './styles/ia-topnav';
18
+ import './user-menu';
19
+ import KeyboardNavigation from './lib/keyboard-navigation';
20
+
21
+ @customElement('ia-topnav')
22
+ export class IATopNav extends LitElement {
23
+ @property({ type: Boolean }) localLinks = false;
24
+
25
+ @property({ type: String }) waybackPagesArchived = '';
26
+
27
+ @property({ type: String }) baseHost = 'https://archive.org';
28
+
29
+ @property({ type: String }) mediaBaseHost = 'https://archive.org';
30
+
31
+ @property({ type: Boolean }) admin = false;
32
+
33
+ @property({ type: Boolean }) canManageFlags = false;
34
+
35
+ @property({ type: Object }) config: IATopNavConfig = defaultTopNavConfig;
36
+
37
+ @property({ type: Boolean }) hideSearch = false;
38
+
39
+ @property({ type: String }) itemIdentifier = '';
40
+
41
+ @property({ type: Boolean }) mediaSliderOpen = false;
42
+
43
+ @property({ type: String }) openMenu = '';
44
+
45
+ @property({ type: String }) screenName: string = '';
46
+
47
+ @property({ type: String }) searchIn = '';
48
+
49
+ @property({ type: String }) searchQuery = '';
50
+
51
+ @property({ type: String }) selectedMenuOption = '';
52
+
53
+ @property({ type: String }) username: string = '';
54
+
55
+ @property({ type: String }) userProfileImagePath =
56
+ '/services/img/user/profile';
57
+
58
+ @property({ type: String })
59
+ secondIdentitySlotMode: IATopNavSecondIdentitySlotMode = '';
60
+
61
+ @property({ type: String })
62
+ searchSlotMode: IATopNavSearchSlotMode = '';
63
+
64
+ @property({ type: Object }) currentTab?: {
65
+ mediatype: string;
66
+ moveTo: string;
67
+ };
68
+
69
+ @state() private menus: IATopNavMenuConfig = buildTopNavMenus();
70
+ private previousKeydownListener: // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
+ ((this: HTMLElement, ev: KeyboardEvent) => any) | undefined;
72
+
73
+ private keyboardNavigation?: KeyboardNavigation;
74
+
75
+ private get normalizedBaseHost() {
76
+ return !this.localLinks ? this.baseHost : '';
77
+ }
78
+
79
+ static get styles() {
80
+ return iaTopNavCSS;
81
+ }
82
+
83
+ updated(props: PropertyValues) {
84
+ if (
85
+ props.has('username') ||
86
+ props.has('waybackPagesArchived') ||
87
+ props.has('itemIdentifier') ||
88
+ props.has('localLinks') ||
89
+ props.has('baseHost')
90
+ ) {
91
+ this.menuSetup();
92
+ }
93
+
94
+ if (this.openMenu === 'search') {
95
+ const targetElement =
96
+ this.renderRoot.querySelector('search-menu')?.shadowRoot;
97
+ if (targetElement) {
98
+ this.keyboardNavigation = new KeyboardNavigation(
99
+ targetElement as unknown as HTMLElement,
100
+ this.openMenu,
101
+ );
102
+
103
+ if (this.previousKeydownListener) {
104
+ this.removeEventListener('keydown', this.previousKeydownListener);
105
+ }
106
+ this.addEventListener('keydown', this.keyboardNavigation.handleKeyDown);
107
+ this.previousKeydownListener = this.keyboardNavigation.handleKeyDown;
108
+ }
109
+ }
110
+ }
111
+
112
+ firstUpdated() {
113
+ // close open menu on `esc` click
114
+ document.addEventListener(
115
+ 'keydown',
116
+ (e) => {
117
+ if (e.key === 'Escape') {
118
+ this.openMenu = '';
119
+ this.mediaSliderOpen = false;
120
+ }
121
+ },
122
+ false,
123
+ );
124
+ }
125
+
126
+ menuSetup() {
127
+ // re/build the nav
128
+ this.menus = buildTopNavMenus(
129
+ this.username,
130
+ this.normalizedBaseHost,
131
+ this.waybackPagesArchived,
132
+ this.itemIdentifier,
133
+ );
134
+ }
135
+
136
+ menuToggled(e: CustomEvent) {
137
+ const currentMenu = this.openMenu;
138
+ this.openMenu = currentMenu === e.detail.menuName ? '' : e.detail.menuName;
139
+ // Keeps media slider open if media menu is open
140
+ if (this.openMenu === 'media') {
141
+ return;
142
+ }
143
+ this.closeMediaSlider();
144
+ }
145
+
146
+ navSearchBlurEvent(e: CustomEvent) {
147
+ if (this.previousKeydownListener) {
148
+ this.removeEventListener('keydown', this.previousKeydownListener);
149
+ }
150
+
151
+ const isUploadButton = e.detail?.isUploadButton;
152
+ if (!isUploadButton) {
153
+ const searchMenu = this.renderRoot.querySelector(
154
+ 'search-menu',
155
+ ) as HTMLElement;
156
+ const elements = this.keyboardNavigation?.getFocusableElements();
157
+ if (searchMenu && elements?.length) {
158
+ elements[0].focus();
159
+ }
160
+ }
161
+ }
162
+
163
+ openMediaSlider() {
164
+ this.mediaSliderOpen = true;
165
+ }
166
+
167
+ closeMediaSlider() {
168
+ this.mediaSliderOpen = false;
169
+ this.selectedMenuOption = '';
170
+ }
171
+
172
+ closeMenus() {
173
+ this.openMenu = '';
174
+ this.closeMediaSlider();
175
+ }
176
+
177
+ searchInChanged(e: CustomEvent) {
178
+ this.searchIn = e.detail.searchIn;
179
+ }
180
+
181
+ trackClick(e: CustomEvent) {
182
+ this.dispatchEvent(
183
+ new CustomEvent('analyticsClick', {
184
+ bubbles: true,
185
+ composed: true,
186
+ detail: e.detail,
187
+ }),
188
+ );
189
+ }
190
+
191
+ trackSubmit(e: CustomEvent) {
192
+ this.dispatchEvent(
193
+ new CustomEvent('analyticsSubmit', {
194
+ bubbles: true,
195
+ composed: true,
196
+ detail: e.detail,
197
+ }),
198
+ );
199
+ }
200
+
201
+ mediaTypeSelected(e: CustomEvent) {
202
+ if (this.selectedMenuOption === e.detail.mediatype) {
203
+ this.closeMediaSlider();
204
+ return;
205
+ }
206
+ this.selectedMenuOption = e.detail.mediatype;
207
+ this.openMediaSlider();
208
+ }
209
+
210
+ get searchMenuOpened() {
211
+ return this.openMenu === 'search';
212
+ }
213
+
214
+ get signedOutOpened() {
215
+ return this.openMenu === 'login';
216
+ }
217
+
218
+ get userMenuOpened() {
219
+ return this.openMenu === 'user';
220
+ }
221
+
222
+ get searchMenuTabIndex() {
223
+ return this.searchMenuOpened ? '' : '-1';
224
+ }
225
+
226
+ get userMenuTabIndex() {
227
+ return this.userMenuOpened ? '' : '-1';
228
+ }
229
+
230
+ get signedOutTabIndex() {
231
+ return this.signedOutOpened ? '' : '-1';
232
+ }
233
+
234
+ get closeLayerClass() {
235
+ return !!this.openMenu || this.mediaSliderOpen ? 'visible' : '';
236
+ }
237
+
238
+ get userMenu() {
239
+ return html`
240
+ <user-menu
241
+ .baseHost=${this.normalizedBaseHost}
242
+ .config=${this.config}
243
+ .menuItems=${this.userMenuItems}
244
+ ?open=${this.openMenu === 'user'}
245
+ .username=${this.username}
246
+ ?hideSearch=${this.hideSearch}
247
+ tabindex="${this.userMenuTabIndex}"
248
+ @menuToggled=${this.menuToggled}
249
+ @trackClick=${this.trackClick}
250
+ @focusToOtherMenuItem=${(e: CustomEvent) =>
251
+ (this.currentTab = e.detail)}
252
+ ></user-menu>
253
+ `;
254
+ }
255
+
256
+ get signedOutDropdown() {
257
+ return html`
258
+ <signed-out-dropdown
259
+ .baseHost=${this.normalizedBaseHost}
260
+ .config=${this.config}
261
+ .open=${this.signedOutOpened}
262
+ ?hideSearch=${this.hideSearch}
263
+ tabindex="${this.signedOutTabIndex}"
264
+ .menuItems=${this.signedOutMenuItems}
265
+ @focusToOtherMenuItem=${(e: CustomEvent) => {
266
+ this.currentTab = e.detail;
267
+ }}
268
+ ></signed-out-dropdown>
269
+ `;
270
+ }
271
+
272
+ get signedOutMenuItems() {
273
+ return this.menus.signedOut;
274
+ }
275
+
276
+ /**
277
+ * Most users just get the basic menu items.
278
+ * For users with `/items` priv, additional admin menu items are included too.
279
+ * Having the `/flags` priv adds a further admin item for managing flags.
280
+ */
281
+ get userMenuItems() {
282
+ const basicItems = this.menus.user;
283
+
284
+ let adminItems = this.menus.userAdmin;
285
+ if (this.canManageFlags) {
286
+ adminItems = adminItems.concat(this.menus.userAdminFlags);
287
+ }
288
+
289
+ return this.itemIdentifier && this.admin
290
+ ? [basicItems, adminItems]
291
+ : [basicItems];
292
+ }
293
+
294
+ get allowSecondaryIcon() {
295
+ return this.secondIdentitySlotMode === 'allow';
296
+ }
297
+
298
+ get useCustomSearch() {
299
+ return this.searchSlotMode === 'custom';
300
+ }
301
+
302
+ get customSearchSlot() {
303
+ return this.useCustomSearch
304
+ ? html`<slot name="custom-search" slot="custom-search"></slot>`
305
+ : nothing;
306
+ }
307
+
308
+ get secondLogoSlot() {
309
+ return this.allowSecondaryIcon
310
+ ? html`
311
+ <slot name="opt-sec-logo" slot="opt-sec-logo"></slot>
312
+ <slot name="opt-sec-logo-mobile" slot="opt-sec-logo-mobile"></slot>
313
+ `
314
+ : nothing;
315
+ }
316
+
317
+ get separatorTemplate() {
318
+ return html`<li class="divider" role="presentation"></li>`;
319
+ }
320
+
321
+ render() {
322
+ return html`
323
+ <div class="topnav">
324
+ <primary-nav
325
+ .baseHost=${this.normalizedBaseHost}
326
+ .mediaBaseHost=${this.mediaBaseHost}
327
+ .config=${this.config}
328
+ .openMenu=${this.openMenu}
329
+ .screenName=${this.screenName}
330
+ .searchIn=${this.searchIn}
331
+ .searchQuery=${this.searchQuery}
332
+ .searchSlotMode=${this.searchSlotMode}
333
+ .secondIdentitySlotMode=${this.secondIdentitySlotMode}
334
+ .selectedMenuOption=${this.selectedMenuOption}
335
+ .username=${this.username}
336
+ .userProfileImagePath=${this.userProfileImagePath}
337
+ .currentTab=${this.currentTab}
338
+ ?hideSearch=${this.hideSearch}
339
+ @mediaTypeSelected=${this.mediaTypeSelected}
340
+ @trackClick=${this.trackClick}
341
+ @trackSubmit=${this.trackSubmit}
342
+ @menuToggled=${this.menuToggled}
343
+ @navSearchBlur=${this.navSearchBlurEvent}
344
+ >
345
+ ${this.secondLogoSlot} ${this.customSearchSlot}
346
+ </primary-nav>
347
+ <media-slider
348
+ .baseHost=${this.normalizedBaseHost}
349
+ .config=${this.config}
350
+ .selectedMenuOption=${this.selectedMenuOption}
351
+ .mediaSliderOpen=${this.mediaSliderOpen}
352
+ .menus=${this.menus}
353
+ tabindex="${this.mediaSliderOpen ? '1' : '-1'}"
354
+ @focusToOtherMenuItem=${(e: CustomEvent) =>
355
+ (this.currentTab = e.detail)}
356
+ ></media-slider>
357
+ </div>
358
+ ${this.username ? this.userMenu : this.signedOutDropdown}
359
+ ${this.useCustomSearch
360
+ ? nothing
361
+ : html`<search-menu
362
+ .baseHost=${this.normalizedBaseHost}
363
+ .config=${this.config}
364
+ .openMenu=${this.openMenu}
365
+ tabindex="${this.searchMenuTabIndex}"
366
+ ?hideSearch=${this.hideSearch}
367
+ @searchInChanged=${this.searchInChanged}
368
+ @trackClick=${this.trackClick}
369
+ @trackSubmit=${this.trackSubmit}
370
+ ></search-menu>`}
371
+ <desktop-subnav
372
+ .baseHost=${this.normalizedBaseHost}
373
+ .menuItems=${this.menus.more.links}
374
+ @focus=${this.closeMenus}
375
+ ></desktop-subnav>
376
+ <div
377
+ id="close-layer"
378
+ class="${this.closeLayerClass}"
379
+ @click=${this.closeMenus}
380
+ ></div>
381
+ `;
382
+ }
383
+ }