@internetarchive/ia-topnav 1.4.1-alpha-webdev8259.10 → 1.4.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.
package/src/ia-topnav.ts CHANGED
@@ -11,9 +11,11 @@ import {
11
11
  IATopNavSecondIdentitySlotMode,
12
12
  } from './models';
13
13
  import './primary-nav';
14
+ import './search-menu';
14
15
  import './signed-out-dropdown';
15
16
  import iaTopNavCSS from './styles/ia-topnav';
16
17
  import './user-menu';
18
+ import KeyboardNavigation from './lib/keyboard-navigation';
17
19
 
18
20
  @customElement('ia-topnav')
19
21
  export class IATopNav extends LitElement {
@@ -41,6 +43,10 @@ export class IATopNav extends LitElement {
41
43
 
42
44
  @property({ type: String }) screenName: string = '';
43
45
 
46
+ @property({ type: String }) searchIn = '';
47
+
48
+ @property({ type: String }) searchQuery = '';
49
+
44
50
  @property({ type: String }) selectedMenuOption = '';
45
51
 
46
52
  @property({ type: String }) username: string = '';
@@ -57,10 +63,10 @@ export class IATopNav extends LitElement {
57
63
  };
58
64
 
59
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;
60
68
 
61
- private boundHandleKeydown = this.handleDocumentKeydown.bind(this);
62
-
63
- private boundHandleClick = this.handleDocumentClick.bind(this);
69
+ private keyboardNavigation?: KeyboardNavigation;
64
70
 
65
71
  private get normalizedBaseHost() {
66
72
  return !this.localLinks ? this.baseHost : '';
@@ -80,32 +86,37 @@ export class IATopNav extends LitElement {
80
86
  ) {
81
87
  this.menuSetup();
82
88
  }
83
- }
84
89
 
85
- firstUpdated() {
86
- document.addEventListener('keydown', this.boundHandleKeydown);
87
- document.addEventListener('click', this.boundHandleClick);
88
- }
89
-
90
- disconnectedCallback() {
91
- super.disconnectedCallback();
92
- document.removeEventListener('keydown', this.boundHandleKeydown);
93
- document.removeEventListener('click', this.boundHandleClick);
94
- }
95
-
96
- private handleDocumentKeydown(e: KeyboardEvent) {
97
- if (e.key === 'Escape') {
98
- this.openMenu = '';
99
- this.mediaSliderOpen = false;
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
+ }
100
105
  }
101
106
  }
102
107
 
103
- private handleDocumentClick(e: MouseEvent) {
104
- if (!this.openMenu) return;
105
- const path = e.composedPath();
106
- if (!path.includes(this)) {
107
- this.closeMenus();
108
- }
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
+ );
109
120
  }
110
121
 
111
122
  menuSetup() {
@@ -128,6 +139,23 @@ export class IATopNav extends LitElement {
128
139
  this.closeMediaSlider();
129
140
  }
130
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
+
131
159
  openMediaSlider() {
132
160
  this.mediaSliderOpen = true;
133
161
  }
@@ -142,6 +170,10 @@ export class IATopNav extends LitElement {
142
170
  this.closeMediaSlider();
143
171
  }
144
172
 
173
+ searchInChanged(e: CustomEvent) {
174
+ this.searchIn = e.detail.searchIn;
175
+ }
176
+
145
177
  trackClick(e: CustomEvent) {
146
178
  this.dispatchEvent(
147
179
  new CustomEvent('analyticsClick', {
@@ -171,6 +203,10 @@ export class IATopNav extends LitElement {
171
203
  this.openMediaSlider();
172
204
  }
173
205
 
206
+ get searchMenuOpened() {
207
+ return this.openMenu === 'search';
208
+ }
209
+
174
210
  get signedOutOpened() {
175
211
  return this.openMenu === 'login';
176
212
  }
@@ -179,6 +215,10 @@ export class IATopNav extends LitElement {
179
215
  return this.openMenu === 'user';
180
216
  }
181
217
 
218
+ get searchMenuTabIndex() {
219
+ return this.searchMenuOpened ? '' : '-1';
220
+ }
221
+
182
222
  get userMenuTabIndex() {
183
223
  return this.userMenuOpened ? '' : '-1';
184
224
  }
@@ -251,10 +291,6 @@ export class IATopNav extends LitElement {
251
291
  return this.secondIdentitySlotMode === 'allow';
252
292
  }
253
293
 
254
- get searchSlot() {
255
- return html`<slot name="search" slot="search"></slot>`;
256
- }
257
-
258
294
  get secondLogoSlot() {
259
295
  return this.allowSecondaryIcon
260
296
  ? html`
@@ -277,6 +313,8 @@ export class IATopNav extends LitElement {
277
313
  .config=${this.config}
278
314
  .openMenu=${this.openMenu}
279
315
  .screenName=${this.screenName}
316
+ .searchIn=${this.searchIn}
317
+ .searchQuery=${this.searchQuery}
280
318
  .secondIdentitySlotMode=${this.secondIdentitySlotMode}
281
319
  .selectedMenuOption=${this.selectedMenuOption}
282
320
  .username=${this.username}
@@ -287,8 +325,9 @@ export class IATopNav extends LitElement {
287
325
  @trackClick=${this.trackClick}
288
326
  @trackSubmit=${this.trackSubmit}
289
327
  @menuToggled=${this.menuToggled}
328
+ @navSearchBlur=${this.navSearchBlurEvent}
290
329
  >
291
- ${this.secondLogoSlot} ${this.searchSlot}
330
+ ${this.secondLogoSlot}
292
331
  </primary-nav>
293
332
  <media-slider
294
333
  .baseHost=${this.normalizedBaseHost}
@@ -302,6 +341,16 @@ export class IATopNav extends LitElement {
302
341
  ></media-slider>
303
342
  </div>
304
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>
305
354
  <desktop-subnav
306
355
  .baseHost=${this.normalizedBaseHost}
307
356
  .menuItems=${this.menus.more.links}
@@ -21,7 +21,7 @@ export class LoginButton extends TrackedElement {
21
21
  }
22
22
 
23
23
  get signupPath() {
24
- return formatUrl('/account/signup', this.baseHost);
24
+ return formatUrl('/signup', this.baseHost);
25
25
  }
26
26
 
27
27
  get loginPath() {
@@ -3,9 +3,11 @@ import TrackedElement from './tracked-element';
3
3
  import icons from './assets/img/icons';
4
4
  import './assets/img/hamburger';
5
5
  import './login-button';
6
+ import './nav-search';
6
7
  import './media-menu';
7
8
  import logoWordmarkStacked from './assets/img/wordmark-stacked';
8
9
  import primaryNavCSS from './styles/primary-nav';
10
+ import locationHandler from './lib/location-handler';
9
11
  import formatUrl from './lib/format-url';
10
12
  import { customElement, property } from 'lit/decorators.js';
11
13
  import { IATopNavConfig, IATopNavSecondIdentitySlotMode } from './models';
@@ -19,6 +21,8 @@ export class PrimaryNav extends TrackedElement {
19
21
  @property({ type: Object }) config: IATopNavConfig = defaultTopNavConfig;
20
22
  @property({ type: String }) openMenu = '';
21
23
  @property({ type: String }) screenName = '';
24
+ @property({ type: String }) searchIn = '';
25
+ @property({ type: String }) searchQuery = '';
22
26
  @property({ type: String })
23
27
  secondIdentitySlotMode: IATopNavSecondIdentitySlotMode = '';
24
28
  @property({ type: String }) selectedMenuOption = '';
@@ -162,19 +166,6 @@ export class PrimaryNav extends TrackedElement {
162
166
  return this.secondIdentitySlotMode === 'allow';
163
167
  }
164
168
 
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="search-container ${this.searchMenuOpen ? 'open' : ''}">
173
- <slot name="search"></slot>
174
- </div>
175
- `;
176
- }
177
-
178
169
  get searchMenu() {
179
170
  if (this.hideSearch) return nothing;
180
171
 
@@ -186,9 +177,38 @@ export class PrimaryNav extends TrackedElement {
186
177
  >
187
178
  ${icons.search}
188
179
  </button>
180
+ <nav-search
181
+ .baseHost=${this.baseHost}
182
+ .config=${this.config}
183
+ .locationHandler=${locationHandler}
184
+ .open=${this.searchMenuOpen}
185
+ .openMenu=${this.openMenu}
186
+ .searchIn=${this.searchIn}
187
+ .searchQuery=${this.searchQuery}
188
+ @blur=${this.emitNavSearchBlurEvent}
189
+ ></nav-search>
189
190
  `;
190
191
  }
191
192
 
193
+ private emitNavSearchBlurEvent(e: FocusEvent) {
194
+ const relatedTarget = e.relatedTarget as HTMLElement;
195
+ const isUploadButton = relatedTarget?.classList.contains('upload');
196
+
197
+ if (isUploadButton) {
198
+ (this.shadowRoot?.querySelector('a.upload') as HTMLElement).focus();
199
+ }
200
+
201
+ this.dispatchEvent(
202
+ new CustomEvent('navSearchBlur', {
203
+ detail: {
204
+ isUploadButton: !!isUploadButton,
205
+ },
206
+ bubbles: true,
207
+ composed: true,
208
+ }),
209
+ );
210
+ }
211
+
192
212
  get mobileDonateHeart() {
193
213
  return html`
194
214
  <a
@@ -266,7 +286,6 @@ export class PrimaryNav extends TrackedElement {
266
286
  .openMenu=${this.openMenu}
267
287
  .currentTab=${this.currentTab}
268
288
  ></media-menu>
269
- ${this.searchSlotContainer}
270
289
  <div class="right-side-section">
271
290
  ${this.mobileDonateHeart} ${this.userStateTemplate}
272
291
  ${this.uploadButtonTemplate} ${this.searchMenu}
@@ -24,6 +24,11 @@ export default css`
24
24
  --activeButtonBg: var(--grey20);
25
25
  --iconFill: var(--grey60);
26
26
 
27
+ --searchActiveBg: var(--grey20);
28
+ --searchActiveInputBg: var(--white);
29
+ --searchMenuBg: var(--grey20);
30
+ --desktopSearchIconFill: var(--grey20);
31
+
27
32
  --mediaMenuBg: var(--grey13);
28
33
  --mediaLabelDesktopColor: var(--grey60);
29
34
  --activeDesktopMenuIcon: var(--grey28);
@@ -128,42 +128,6 @@ export default css`
128
128
  z-index: 3;
129
129
  }
130
130
 
131
- .search-container {
132
- display: none;
133
- }
134
-
135
- .search-container.open {
136
- display: flex;
137
- position: absolute;
138
- top: 0;
139
- right: 4rem;
140
- bottom: 0;
141
- left: 4rem;
142
- z-index: 3;
143
- padding: 0.5rem;
144
- border-radius: 1rem 1rem 0 0;
145
- background: var(--primaryNavBg);
146
- align-items: center;
147
- animation: fade-in 0.2s forwards;
148
- }
149
-
150
- .search-container ::slotted(*) {
151
- display: block;
152
- }
153
-
154
- .search-container slot {
155
- width: 100%;
156
- }
157
-
158
- @keyframes fade-in {
159
- 0% {
160
- opacity: 0;
161
- }
162
- 100% {
163
- opacity: 1;
164
- }
165
- }
166
-
167
131
  .upload {
168
132
  display: none;
169
133
  }
@@ -328,25 +292,6 @@ export default css`
328
292
  float: right;
329
293
  margin-left: 1rem;
330
294
  }
331
-
332
- .search-container,
333
- .search-container.open {
334
- display: flex;
335
- position: static;
336
- top: auto;
337
- right: auto;
338
- bottom: auto;
339
- left: auto;
340
- align-items: center;
341
- padding: 0 0 0 1rem;
342
- background: transparent;
343
- border-radius: 0;
344
- z-index: auto;
345
- }
346
-
347
- .search-container slot {
348
- width: auto;
349
- }
350
295
  }
351
296
 
352
297
  @media (min-width: 990px) {
package/ssl/server.key CHANGED
@@ -1,28 +1,28 @@
1
- -----BEGIN PRIVATE KEY-----
2
- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDZqbS9hNS38Atc
3
- A7Uszq7EA7RavuHc8AcpbURa6Y7NORs0SmfDD0oLptQ7DX5pSuHyOeDHzQ3UXRCU
4
- DAhAD1XlaYQ6PPdiJPRAe4ACELzgDzepxVz3pdKPcfXd5/mHV3ghUZya2XW5DSF1
5
- CRYUX2j10a+JORJIG4bhvTg4HxTJZZDuaNfBMC6ZSn7d9YLQoAf1MLLJm43Ycj1W
6
- lOnyG4DHxNEqEk260BlbBywJH+SJA6GA0GR2bnzYZaDnZ0wqea0Zamx8ijpdJxBU
7
- xYOqLfzs2KREVkkGOmsFEu82W6G3D4bKsY1UNaWXb/jcl3UkXIJJij6CdbbMQkUJ
8
- c8dow3sVAgMBAAECggEANgLpITAhcuVDhFk9L3m4H1bF/dCpDmCXfl2pXR/guicm
9
- C4M9HUeheaOzvVWbXThiOe/HyfylpmFTmFEmCPNlPrDAyYzQXE/MNmYO/TQ3Eihk
10
- iSG68I764XKHbsG+Byoa2rW8NSaqEjniZ/7Rtkt4qasXMmdxlGgUP9bq6O45g8HV
11
- kHxneFlA2KbrvmWnBi7NTea9+tp61NWiq5n97UgHacQR6KkYIpbxd7uNSnCSdmXt
12
- pcwzO4ZPabJ2/DKRjePhU5OggPh+9AhJ3jsBo99GwYPgSZDh8E3vh2OWOtLBMUH3
13
- rmAYwlRT2aZ5hy5yi+4QD98WoUQtsr+9n757F8V6MQKBgQDzZycdgaKwWd5d34NN
14
- 0TkFtPQPwxxJTCCs3I3q02uWcS3svQzBK0cWb4nISO04TnJ3MnXo83dgGhCMF+uZ
15
- FCXxAA53z8F92iZo+ELlXFDFwNeNYih/afDA42tWZ6TlVsDnZ4zQgRjHS7jEF/JV
16
- 0ZgwGpw5725JQt64dic8wTOcDQKBgQDk7YYACQcWTnmjDhZQ3PZSX4fcTtzPZKZj
17
- fa1GrXEaUH1hSyc9VmY6qJpUmXexpvtr194nXE+O5wbThOHcBjVlo2Qv+vswmUX3
18
- WEcVzTVN4/fODCLCqFcMNIr+BzwZfwpT+p0u8g9FxDy1gGmvkxwIu8DCpnUT12Xj
19
- hm2wO+UxKQKBgFxCSDBF9+2SUtgQJYv0dwGzwiLLWMhro6MCAoT02D3w7nBihBgg
20
- GFTnuDkDc285ROfrZ4gB6MizeHwxgOrIGU2NMO62/+d9LbvyBiE76Z3bZ5i+kQ0i
21
- kc/7I69fn8ASLxJHTLenh0XbbNBfJ0riJCZvn7HSEGKShysyFdNQhAhtAoGAFYVi
22
- 0IQIv4cXFkYPwQBUw7+pVQOw7GpI3heFf5x0goXIk6nuAW0q5R7Oi192CiRphGTh
23
- xI+ABy4ezSmz1exbfreShpQwowv1sOACpsEI3s6skBlB90y+Ci6yVlk1xCvWO7jW
24
- qAAngWaGUoXE6bWJsCR+ZY4ieYAJWw9bJnMrA6kCgYAzV4Xeoh5ofENZM21wKW3W
25
- iCzRibPObv6Vb/j9A9yT57yzI3BdfvsX5zmuSvOJm1DimgGY9nCJUzUEYG0a1Dhh
26
- /rqObPoVIFGesmvwflVYFktmVHk7ycEDVreSTz23cvmraPz1fnpdKeuEs4sRQJV7
27
- iJhLoxX5SJlJc0RXMhgHGQ==
28
- -----END PRIVATE KEY-----
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDZqbS9hNS38Atc
3
+ A7Uszq7EA7RavuHc8AcpbURa6Y7NORs0SmfDD0oLptQ7DX5pSuHyOeDHzQ3UXRCU
4
+ DAhAD1XlaYQ6PPdiJPRAe4ACELzgDzepxVz3pdKPcfXd5/mHV3ghUZya2XW5DSF1
5
+ CRYUX2j10a+JORJIG4bhvTg4HxTJZZDuaNfBMC6ZSn7d9YLQoAf1MLLJm43Ycj1W
6
+ lOnyG4DHxNEqEk260BlbBywJH+SJA6GA0GR2bnzYZaDnZ0wqea0Zamx8ijpdJxBU
7
+ xYOqLfzs2KREVkkGOmsFEu82W6G3D4bKsY1UNaWXb/jcl3UkXIJJij6CdbbMQkUJ
8
+ c8dow3sVAgMBAAECggEANgLpITAhcuVDhFk9L3m4H1bF/dCpDmCXfl2pXR/guicm
9
+ C4M9HUeheaOzvVWbXThiOe/HyfylpmFTmFEmCPNlPrDAyYzQXE/MNmYO/TQ3Eihk
10
+ iSG68I764XKHbsG+Byoa2rW8NSaqEjniZ/7Rtkt4qasXMmdxlGgUP9bq6O45g8HV
11
+ kHxneFlA2KbrvmWnBi7NTea9+tp61NWiq5n97UgHacQR6KkYIpbxd7uNSnCSdmXt
12
+ pcwzO4ZPabJ2/DKRjePhU5OggPh+9AhJ3jsBo99GwYPgSZDh8E3vh2OWOtLBMUH3
13
+ rmAYwlRT2aZ5hy5yi+4QD98WoUQtsr+9n757F8V6MQKBgQDzZycdgaKwWd5d34NN
14
+ 0TkFtPQPwxxJTCCs3I3q02uWcS3svQzBK0cWb4nISO04TnJ3MnXo83dgGhCMF+uZ
15
+ FCXxAA53z8F92iZo+ELlXFDFwNeNYih/afDA42tWZ6TlVsDnZ4zQgRjHS7jEF/JV
16
+ 0ZgwGpw5725JQt64dic8wTOcDQKBgQDk7YYACQcWTnmjDhZQ3PZSX4fcTtzPZKZj
17
+ fa1GrXEaUH1hSyc9VmY6qJpUmXexpvtr194nXE+O5wbThOHcBjVlo2Qv+vswmUX3
18
+ WEcVzTVN4/fODCLCqFcMNIr+BzwZfwpT+p0u8g9FxDy1gGmvkxwIu8DCpnUT12Xj
19
+ hm2wO+UxKQKBgFxCSDBF9+2SUtgQJYv0dwGzwiLLWMhro6MCAoT02D3w7nBihBgg
20
+ GFTnuDkDc285ROfrZ4gB6MizeHwxgOrIGU2NMO62/+d9LbvyBiE76Z3bZ5i+kQ0i
21
+ kc/7I69fn8ASLxJHTLenh0XbbNBfJ0riJCZvn7HSEGKShysyFdNQhAhtAoGAFYVi
22
+ 0IQIv4cXFkYPwQBUw7+pVQOw7GpI3heFf5x0goXIk6nuAW0q5R7Oi192CiRphGTh
23
+ xI+ABy4ezSmz1exbfreShpQwowv1sOACpsEI3s6skBlB90y+Ci6yVlk1xCvWO7jW
24
+ qAAngWaGUoXE6bWJsCR+ZY4ieYAJWw9bJnMrA6kCgYAzV4Xeoh5ofENZM21wKW3W
25
+ iCzRibPObv6Vb/j9A9yT57yzI3BdfvsX5zmuSvOJm1DimgGY9nCJUzUEYG0a1Dhh
26
+ /rqObPoVIFGesmvwflVYFktmVHk7ycEDVreSTz23cvmraPz1fnpdKeuEs4sRQJV7
27
+ iJhLoxX5SJlJc0RXMhgHGQ==
28
+ -----END PRIVATE KEY-----
@@ -9,9 +9,35 @@ import {
9
9
 
10
10
  import '../src/ia-topnav';
11
11
  import { IATopNav } from '../src/ia-topnav';
12
+ import { SearchMenu } from '../src/search-menu';
12
13
  import { SignedOutDropdown } from '../src/signed-out-dropdown';
13
14
  import UserMenu from '../src/user-menu';
14
15
 
16
+ // const container = (
17
+ // {
18
+ // username = '',
19
+ // screenName = '',
20
+ // config = {},
21
+ // localLinks = true,
22
+ // secondIdentitySlotMode = '',
23
+ // }: {
24
+ // username?: string;
25
+ // screenName?: string;
26
+ // config?: IATopNavConfig;
27
+ // localLinks: boolean;
28
+ // secondIdentitySlotMode?: IATopNavSecondIdentitySlotMode;
29
+ // } = {
30
+ // localLinks: true,
31
+ // },
32
+ // ) =>
33
+ // html`<ia-topnav
34
+ // .screenName=${screenName}
35
+ // username=${username}
36
+ // ?localLinks=${localLinks}
37
+ // .config=${config}
38
+ // .secondIdentitySlotMode=${secondIdentitySlotMode}
39
+ // ></ia-topnav>`;
40
+
15
41
  const verifyClosed = (instance: IATopNav) => {
16
42
  expect(instance.mediaSliderOpen).to.be.false;
17
43
  expect(instance.selectedMenuOption).to.equal('');
@@ -27,6 +53,25 @@ afterEach(() => {
27
53
  });
28
54
 
29
55
  describe('<ia-topnav>', () => {
56
+ it('assigns a value to "search in" from outside event', async () => {
57
+ const el = await fixture<IATopNav>(html` <ia-topnav></ia-topnav>`);
58
+ const query = 'atari';
59
+ const searchMenu = el.shadowRoot?.querySelector(
60
+ 'search-menu',
61
+ ) as SearchMenu;
62
+
63
+ const inputEvent = new InputEvent('input');
64
+ Object.defineProperty(inputEvent, 'target', {
65
+ value: { value: query },
66
+ writable: false,
67
+ });
68
+
69
+ searchMenu?.searchInChanged(inputEvent);
70
+ await el.updateComplete;
71
+
72
+ expect(el.searchIn).to.equal(query);
73
+ });
74
+
30
75
  it('dispatches an analyticsClick event when trackClick event fired', async () => {
31
76
  const el = await fixture<IATopNav>(html` <ia-topnav></ia-topnav>`);
32
77
  const clickEvent = new MouseEvent('click');
@@ -42,6 +87,22 @@ describe('<ia-topnav>', () => {
42
87
  expect(response).to.exist;
43
88
  });
44
89
 
90
+ it('dispatches an analyticsSubmit event when trackSubmit event fired', async () => {
91
+ const el = await fixture<IATopNav>(html` <ia-topnav></ia-topnav>`);
92
+ const submitEvent = new Event('submit');
93
+ const form = el.shadowRoot
94
+ ?.querySelector('primary-nav')
95
+ ?.shadowRoot?.querySelector('nav-search')
96
+ ?.shadowRoot?.querySelector('form');
97
+
98
+ form?.addEventListener('submit', (e) => e.preventDefault());
99
+ (form?.querySelector('[name=query]') as HTMLInputElement).value = 'atari';
100
+ setTimeout(() => form?.dispatchEvent(submitEvent));
101
+ const response = await oneEvent(el, 'trackSubmit');
102
+
103
+ expect(response).to.exist;
104
+ });
105
+
45
106
  it('closes all menus when close-layer clicked', async () => {
46
107
  const el = await fixture<IATopNav>(html` <ia-topnav></ia-topnav>`);
47
108
 
@@ -129,6 +190,17 @@ describe('<ia-topnav>', () => {
129
190
  expect(el.selectedMenuOption).to.equal('');
130
191
  });
131
192
 
193
+ it('toggles search menu tabindex when dropdown open', async () => {
194
+ const el = await fixture<IATopNav>(html` <ia-topnav></ia-topnav>`);
195
+
196
+ el.openMenu = 'search';
197
+ await el.updateComplete;
198
+
199
+ expect(
200
+ el.shadowRoot?.querySelector('search-menu')?.getAttribute('tabindex'),
201
+ ).to.equal('');
202
+ });
203
+
132
204
  it('toggles user menu tabindex when dropdown open', async () => {
133
205
  const el = await fixture<IATopNav>(
134
206
  html` <ia-topnav username="shaneriley" ?localLinks=${false}></ia-topnav>`,
@@ -174,6 +246,11 @@ describe('<ia-topnav>', () => {
174
246
  screenName="shaneriley"
175
247
  ?localLinks=${false}
176
248
  ></ia-topnav>`,
249
+ // container({
250
+ // username: 'shaneriley',
251
+ // screenName: 'shaneriley',
252
+ // localLinks: false,
253
+ // }),
177
254
  );
178
255
 
179
256
  (
@@ -205,6 +282,7 @@ describe('<ia-topnav>', () => {
205
282
  'primary-nav',
206
283
  'media-slider',
207
284
  'desktop-subnav',
285
+ 'search-menu',
208
286
  ];
209
287
  componentSelectors.forEach((selector) => {
210
288
  const component = el.shadowRoot?.querySelector(selector) as unknown as {
@@ -235,24 +313,6 @@ describe('<ia-topnav>', () => {
235
313
  });
236
314
  });
237
315
 
238
- describe('search slot', () => {
239
- it('forwards search slot to primary-nav', async () => {
240
- const el = await fixture<IATopNav>(html`<ia-topnav></ia-topnav>`);
241
- await el.updateComplete;
242
-
243
- const primaryNav = el.shadowRoot?.querySelector('primary-nav');
244
- const slot = primaryNav?.querySelector('slot[name="search"]');
245
- expect(slot).to.exist;
246
- });
247
-
248
- it('does not render search-menu', async () => {
249
- const el = await fixture<IATopNav>(html`<ia-topnav></ia-topnav>`);
250
- await el.updateComplete;
251
-
252
- expect(el.shadowRoot?.querySelector('search-menu')).to.not.exist;
253
- });
254
- });
255
-
256
316
  describe('slot pass throughs', () => {
257
317
  describe('slot for <primary-nav>', () => {
258
318
  it('opens a slot with `secondIdentitySlotMode`', async () => {
@@ -267,14 +327,15 @@ describe('<ia-topnav>', () => {
267
327
 
268
328
  const slot = el.shadowRoot
269
329
  ?.querySelector('primary-nav')
270
- ?.querySelector('slot[name="opt-sec-logo"]');
330
+ ?.querySelector('slot');
271
331
  expect(slot).to.exist;
332
+ expect(slot?.getAttribute('name')).to.equal('opt-sec-logo');
272
333
 
273
334
  el.secondIdentitySlotMode = '';
274
335
  await elementUpdated(el);
275
336
  const noSlot = el.shadowRoot
276
337
  ?.querySelector('primary-nav')
277
- ?.querySelector('slot[name="opt-sec-logo"]');
338
+ ?.querySelector('slot');
278
339
  expect(noSlot).to.not.exist;
279
340
  });
280
341
  });
@@ -50,7 +50,7 @@ describe('<primary-nav>', () => {
50
50
  expect(el.shadowRoot?.querySelector('login-button')).to.not.be.undefined;
51
51
  });
52
52
 
53
- it('does not render search trigger or search slot if hideSearch true', async () => {
53
+ it('does not render search menu toggle and search form if hideSearch true', async () => {
54
54
  const el = await fixture<PrimaryNav>(
55
55
  component({
56
56
  baseHost: 'archive.org',
@@ -61,48 +61,7 @@ describe('<primary-nav>', () => {
61
61
  );
62
62
 
63
63
  expect(el.shadowRoot?.querySelector('.search-trigger')).to.equal(null);
64
- expect(el.shadowRoot?.querySelector('.search-container')).to.not.exist;
65
- });
66
-
67
- it('renders search slot container', async () => {
68
- const el = await fixture<PrimaryNav>(
69
- component({
70
- baseHost: 'archive.org',
71
- username: 'testuser',
72
- screenName: 'testuser',
73
- }),
74
- );
75
-
76
- expect(el.shadowRoot?.querySelector('.search-container')).to.exist;
77
-
78
- const slot = el.shadowRoot?.querySelector<HTMLSlotElement>(
79
- 'slot[name="search"]',
80
- );
81
- expect(slot).to.exist;
82
- });
83
-
84
- it('renders search trigger button for mobile toggle', async () => {
85
- const el = await fixture<PrimaryNav>(
86
- component({
87
- baseHost: 'archive.org',
88
- username: 'testuser',
89
- screenName: 'testuser',
90
- }),
91
- );
92
-
93
- expect(el.shadowRoot?.querySelector('.search-trigger')).to.exist;
94
- });
95
-
96
- it('does not render nav-search', async () => {
97
- const el = await fixture<PrimaryNav>(
98
- component({
99
- baseHost: 'archive.org',
100
- username: 'testuser',
101
- screenName: 'testuser',
102
- }),
103
- );
104
-
105
- expect(el.shadowRoot?.querySelector('nav-search')).to.not.exist;
64
+ expect(el.shadowRoot?.querySelector('nav-search')).to.equal(null);
106
65
  });
107
66
 
108
67
  it('opens a slot with `secondIdentitySlotMode`', async () => {