@internetarchive/ia-topnav 1.3.5-alpha1 → 1.3.5-alpha10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-topnav",
3
- "version": "1.3.5-alpha1",
3
+ "version": "1.3.5-alpha10",
4
4
  "description": "Top nav for Internet Archive",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "index.js",
@@ -57,6 +57,7 @@ class DropdownMenu extends TrackedElement {
57
57
  return html`<a
58
58
  href="${formatUrl(link.url, this.baseHost)}"
59
59
  class="${link.class}"
60
+ tabindex="${this.open ? '' : '-1'}"
60
61
  @click=${this.trackClick}
61
62
  data-event-click-tracking="${this.config.eventCategory}|Nav${link.analyticsEvent}"
62
63
  aria-label=${calloutText ? `New feature: ${link.title}` : nothing}>
package/src/ia-topnav.js CHANGED
@@ -62,6 +62,7 @@ export default class IATopNav extends LitElement {
62
62
  username: { type: String },
63
63
  userProfileImagePath: { type: String },
64
64
  secondIdentitySlotMode: { type: String },
65
+ currentTab: { type: Object },
65
66
  };
66
67
  }
67
68
 
@@ -77,11 +78,12 @@ export default class IATopNav extends LitElement {
77
78
  this.searchIn = '';
78
79
  this.selectedMenuOption = '';
79
80
  this.secondIdentitySlotMode = '';
81
+ this.currentTab = {};
80
82
  }
81
83
 
82
84
  updated(props) {
83
85
  if (props.has('username') || props.has('localLinks') || props.has('baseHost') ||
84
- props.has('waybackPagesArchived') || props.has('itemIdentifier')) {
86
+ props.has('waybackPagesArchived') || props.has('itemIdentifier')) {
85
87
  this.menuSetup();
86
88
  }
87
89
  }
@@ -199,6 +201,7 @@ export default class IATopNav extends LitElement {
199
201
  tabindex="${this.userMenuTabIndex}"
200
202
  @menuToggled=${this.menuToggled}
201
203
  @trackClick=${this.trackClick}
204
+ @moveFocusToOthers=${(e) => this.currentTab = e.detail}
202
205
  ></user-menu>
203
206
  `;
204
207
  }
@@ -274,6 +277,7 @@ export default class IATopNav extends LitElement {
274
277
  .selectedMenuOption=${this.selectedMenuOption}
275
278
  .username=${this.username}
276
279
  .userProfileImagePath=${this.userProfileImagePath}
280
+ .currentTab=${this.currentTab}
277
281
  ?hideSearch=${this.hideSearch}
278
282
  @mediaTypeSelected=${this.mediaTypeSelected}
279
283
  @toggleSearchMenu=${this.toggleSearchMenu}
@@ -289,6 +293,8 @@ export default class IATopNav extends LitElement {
289
293
  .selectedMenuOption=${this.selectedMenuOption}
290
294
  .mediaSliderOpen=${this.mediaSliderOpen}
291
295
  .menus=${this.menus}
296
+ tabindex="${this.mediaSliderOpen ? '1' : ''}"
297
+ @moveFocusToOthers=${(e) => this.currentTab = e.detail}
292
298
  ></media-slider>
293
299
  </div>
294
300
  ${this.username ? this.userMenu : this.signedOutDropdown}
@@ -305,6 +311,7 @@ export default class IATopNav extends LitElement {
305
311
  <desktop-subnav
306
312
  .baseHost=${this.baseHost}
307
313
  .menuItems=${this.desktopSubnavMenuItems}
314
+ @focus=${this.closeMenus}
308
315
  ></desktop-subnav>
309
316
  <div id="close-layer" class="${this.closeLayerClass}" @click=${this.closeMenus}></div>
310
317
  `;
@@ -0,0 +1,130 @@
1
+
2
+
3
+
4
+ export default class KeyboardNavigation {
5
+ /**
6
+ * Constructor for the KeyboardNavigation class.
7
+ * @param {HTMLElement} elementsContainer - The container element that holds the focusable elements.
8
+ * @param {string} menuOption - The type of menu option ('web' or 'usermenu').
9
+ */
10
+ constructor(elementsContainer, menuOption) {
11
+ this.elementsContainer = elementsContainer;
12
+ this.menuOption = menuOption;
13
+ this.focusableElements = this.getFocusableElements();
14
+ this.focusedIndex = this.getInitialFocusedIndex();
15
+
16
+ this.focusableElements[this.focusedIndex]?.focus();
17
+ this.handleKeyDown = this.handleKeyDown.bind(this);
18
+ }
19
+
20
+ /**
21
+ * Returns the initial focused index based on the menu option.
22
+ * @returns {number} The initial focused index (0 for 'web', 1 for 'usermenu').
23
+ */
24
+ getInitialFocusedIndex() {
25
+ return this.menuOption === 'usermenu' ? 1 : 0;
26
+ }
27
+
28
+ /**
29
+ * Gets an array of focusable elements within the container.
30
+ * @returns {HTMLElement[]} An array of focusable elements.
31
+ */
32
+ getFocusableElements() {
33
+ const focusableTagSelectors = 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])';
34
+ const isDisabledOrHidden = el => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden');
35
+
36
+ let elements;
37
+ if (this.menuOption === 'web') {
38
+ const waybackSlider = this.elementsContainer.querySelector('wayback-slider').shadowRoot;
39
+ const waybackSearch = waybackSlider.querySelector('wayback-search');
40
+ const waybackSearchElements = Array.from(waybackSearch.shadowRoot.querySelectorAll(focusableTagSelectors));
41
+
42
+ const normalElements = Array.from(waybackSlider.querySelectorAll(focusableTagSelectors));
43
+
44
+ const savePageForm = waybackSlider.querySelector('save-page-form');
45
+ const savePageFormElements = Array.from(savePageForm.shadowRoot.querySelectorAll(focusableTagSelectors));
46
+
47
+ elements = [...waybackSearchElements, ...normalElements, ...savePageFormElements];
48
+ } else {
49
+ elements = this.elementsContainer.querySelectorAll(focusableTagSelectors);
50
+ }
51
+
52
+ return Array.from(elements).filter(isDisabledOrHidden);
53
+ }
54
+
55
+ /**
56
+ * Handles keyboard events and focuses the appropriate element.
57
+ * @param {KeyboardEvent} event - The keyboard event object.
58
+ */
59
+ handleKeyDown(event) {
60
+ const { key } = event;
61
+ const isArrowKey = ['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'].includes(key);
62
+ const isTabKey = key === 'Tab';
63
+
64
+ if (isArrowKey) {
65
+ this.handleArrowKey(key);
66
+ event.preventDefault();
67
+ } else if (isTabKey) {
68
+ this.handleTabKey(event);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Handles arrow key events and focuses the next or previous element.
74
+ * @param {string} key - The key that was pressed ('ArrowDown', 'ArrowRight', 'ArrowUp', or 'ArrowLeft').
75
+ */
76
+ handleArrowKey(key) {
77
+ const isDownOrRight = ['ArrowDown', 'ArrowRight'].includes(key);
78
+ isDownOrRight ? this.focusNext() : this.focusPrevious();
79
+ }
80
+
81
+ /**
82
+ * Focuses the previous focusable element in the container.
83
+ */
84
+ focusPrevious() {
85
+ if (this.focusableElements.length === 0) return;
86
+ this.focusedIndex = (this.focusedIndex - 1 + this.focusableElements.length) % this.focusableElements.length;
87
+ this.focusableElements[this.focusedIndex]?.focus();
88
+ }
89
+
90
+ /**
91
+ * Focuses the next focusable element in the container.
92
+ */
93
+ focusNext() {
94
+ if (this.focusableElements.length === 0) return;
95
+ this.focusedIndex = (this.focusedIndex + 1) % this.focusableElements.length;
96
+ this.focusableElements[this.focusedIndex]?.focus();
97
+ }
98
+
99
+ /**
100
+ * Handles the Tab key event and focuses the next or previous menu item.
101
+ * @param {KeyboardEvent} event - The keyboard event object.
102
+ */
103
+ handleTabKey(event) {
104
+ console.log(this)
105
+ if (this.menuOption) {
106
+ const isShiftPressed = event.shiftKey;
107
+ this.focusNextMenuItem(isShiftPressed);
108
+ }
109
+
110
+ this.focusableElements[this.focusedIndex]?.blur();
111
+ event.preventDefault();
112
+ }
113
+
114
+ /**
115
+ * Focuses the next or previous menu item based on the provided flag.
116
+ * @param {boolean} isPrevious - A flag indicating whether to focus the previous menu item.
117
+ */
118
+ focusNextMenuItem(isPrevious = false) {
119
+ this.elementsContainer.dispatchEvent(
120
+ new CustomEvent('moveFocusToOthers', {
121
+ bubbles: true,
122
+ composed: true,
123
+ detail: {
124
+ mediatype: this.menuOption,
125
+ moveTo: isPrevious ? 'prev' : 'next',
126
+ },
127
+ })
128
+ );
129
+ }
130
+ }
@@ -103,6 +103,7 @@ class MediaButton extends TrackedElement {
103
103
  @click=${this.followable ? this.trackClick : this.onClick}
104
104
  data-event-click-tracking="${this.analyticsEvent}"
105
105
  title="${this.tooltipPrefix} ${this.mediatype} menu"
106
+ tabindex="${this.openMenu === 'media' ? '' : '0'}"
106
107
  >
107
108
  ${this.menuItem}
108
109
  </a>
package/src/media-menu.js CHANGED
@@ -67,6 +67,7 @@ class MediaMenu extends LitElement {
67
67
  config: { type: Object },
68
68
  openMenu: { type: String },
69
69
  selectedMenuOption: { type: String },
70
+ currentTab: { type: Object },
70
71
  };
71
72
  }
72
73
 
@@ -75,6 +76,25 @@ class MediaMenu extends LitElement {
75
76
  this.config = {};
76
77
  this.openMenu = '';
77
78
  this.selectedMenuOption = '';
79
+ this.currentTab = {};
80
+ }
81
+
82
+ updated(props) {
83
+ if (props.has('currentTab')) {
84
+ const mediaButtons = Array.from(this.shadowRoot.querySelectorAll('media-button'));
85
+
86
+ mediaButtons.map((button, index) => {
87
+ const linkItem = button.shadowRoot.querySelector('a.menu-item');
88
+ if (linkItem) {
89
+ if (linkItem.classList.contains(`${this.selectedMenuOption}`)) {
90
+ linkItem.classList.remove('selected');
91
+ linkItem.blur();
92
+
93
+ mediaButtons[this.currentTab.moveTo === 'next' ? index + 1 : index - 1].shadowRoot.querySelector('a.menu-item').focus();
94
+ }
95
+ }
96
+ });
97
+ }
78
98
  }
79
99
 
80
100
  get mediaMenuOptionsTemplate() {
@@ -96,6 +116,7 @@ class MediaMenu extends LitElement {
96
116
  .mediatype=${menu}
97
117
  .openMenu=${this.openMenu}
98
118
  .selected=${selected}
119
+ .selectedMenuOption=${this.selectedMenuOption}
99
120
  data-mediatype="${menu}"
100
121
  ></media-button>
101
122
  `;
@@ -117,7 +138,6 @@ class MediaMenu extends LitElement {
117
138
  <div class="overflow-clip">
118
139
  <nav
119
140
  class="media-menu-inner"
120
- aria-hidden="${!this.menuOpened}"
121
141
  aria-expanded="${this.menuOpened}"
122
142
  >
123
143
  <div class="menu-group">
@@ -1,6 +1,7 @@
1
1
  import { LitElement, html } from 'https://offshoot.prod.archive.org/lit.js';
2
2
  import './media-subnav.js';
3
3
  import mediaSliderCSS from './styles/media-slider.js';
4
+ import KeyboardNavigation from './lib/keyboard-navigation.js';
4
5
 
5
6
  class MediaSlider extends LitElement {
6
7
  static get styles() {
@@ -26,6 +27,19 @@ class MediaSlider extends LitElement {
26
27
  this.selectedMenuOption = 'texts';
27
28
  }
28
29
 
30
+ updated(props) {
31
+ if (props.has('selectedMenuOption') && this.selectedMenuOption) {
32
+ const container = this.shadowRoot?.querySelector('.has-focused')?.shadowRoot;
33
+
34
+ if (container) {
35
+ const keyboardNavigation = new KeyboardNavigation(container, this.selectedMenuOption);
36
+ this.addEventListener('keydown', keyboardNavigation.handleKeyDown);
37
+ this.removeEventListener('keydown', this.previousKeydownListener);
38
+ this.previousKeydownListener = keyboardNavigation.handleKeyDown;
39
+ }
40
+ }
41
+ }
42
+
29
43
  shouldUpdate() {
30
44
  const scrollPane = this.shadowRoot ? this.shadowRoot.querySelector('.information-menu') : null;
31
45
 
@@ -47,49 +61,49 @@ class MediaSlider extends LitElement {
47
61
  <media-subnav
48
62
  .baseHost=${this.baseHost}
49
63
  .config=${this.config}
50
- class="${this.selectedMenuOption === 'audio' ? '' : 'hidden'}"
64
+ class="${this.selectedMenuOption === 'audio' ? 'has-focused' : 'hidden'}"
51
65
  menu="audio"
52
66
  .menuItems=${this.menus.audio}
53
67
  ></media-subnav>
54
68
  <media-subnav
55
69
  .baseHost=${this.baseHost}
56
70
  .config=${this.config}
57
- class="${this.selectedMenuOption === 'images' ? '' : 'hidden'}"
71
+ class="${this.selectedMenuOption === 'images' ? 'has-focused' : 'hidden'}"
58
72
  menu="images"
59
73
  .menuItems=${this.menus.images}
60
74
  ></media-subnav>
61
75
  <media-subnav
62
76
  .baseHost=${this.baseHost}
63
77
  .config=${this.config}
64
- class="${this.selectedMenuOption === 'software' ? '' : 'hidden'}"
78
+ class="${this.selectedMenuOption === 'software' ? 'has-focused' : 'hidden'}"
65
79
  menu="software"
66
80
  .menuItems=${this.menus.software}
67
81
  ></media-subnav>
68
82
  <media-subnav
69
83
  .baseHost=${this.baseHost}
70
84
  .config=${this.config}
71
- class="${this.selectedMenuOption === 'texts' ? '' : 'hidden'}"
85
+ class="${this.selectedMenuOption === 'texts' ? 'has-focused' : 'hidden'}"
72
86
  menu="texts"
73
87
  .menuItems=${this.menus.texts}
74
88
  ></media-subnav>
75
89
  <media-subnav
76
90
  .baseHost=${this.baseHost}
77
91
  .config=${this.config}
78
- class="${this.selectedMenuOption === 'video' ? '' : 'hidden'}"
92
+ class="${this.selectedMenuOption === 'video' ? 'has-focused' : 'hidden'}"
79
93
  menu="video"
80
94
  .menuItems=${this.menus.video}
81
95
  ></media-subnav>
82
96
  <media-subnav
83
97
  .baseHost=${this.baseHost}
84
98
  .config=${this.config}
85
- class="${this.selectedMenuOption === 'web' ? '' : 'hidden'}"
99
+ class="${this.selectedMenuOption === 'web' ? 'has-focused' : 'hidden'}"
86
100
  menu="web"
87
101
  .menuItems=${this.menus.web}
88
102
  ></media-subnav>
89
103
  <media-subnav
90
104
  .baseHost=${this.baseHost}
91
105
  .config=${this.config}
92
- class="${this.selectedMenuOption === 'more' ? '' : 'hidden'}"
106
+ class="${this.selectedMenuOption === 'more' ? 'has-focused' : 'hidden'}"
93
107
  menu="more"
94
108
  .menuItems=${this.menus.more}
95
109
  ></media-subnav>
package/src/nav-search.js CHANGED
@@ -98,8 +98,8 @@ class NavSearch extends TrackedElement {
98
98
  class="search-field"
99
99
  placeholder="Search"
100
100
  autocomplete="off"
101
- @focus=${this.toggleSearchMenu}
102
101
  value=${this.searchQuery || ''}
102
+ @focus=${this.toggleSearchMenu}
103
103
  />
104
104
  ${this.searchInsideInput}
105
105
  <button
@@ -31,6 +31,7 @@ class PrimaryNav extends TrackedElement {
31
31
  userMenuOpen: { type: Boolean },
32
32
  username: { type: String },
33
33
  userProfileImagePath: { type: String },
34
+ currentTab: { type: Object },
34
35
  };
35
36
  }
36
37
 
@@ -44,6 +45,7 @@ class PrimaryNav extends TrackedElement {
44
45
  this.userMenuOpen = false;
45
46
  this.mediaBaseHost = 'https://archive.org';
46
47
  this.secondIdentitySlotMode = '';
48
+ this.currentTab = {};
47
49
  }
48
50
 
49
51
  toggleMediaMenu(e) {
@@ -79,6 +81,27 @@ class PrimaryNav extends TrackedElement {
79
81
  );
80
82
  }
81
83
 
84
+ updated(props) {
85
+ const { currentTab } = this;
86
+ console.log(currentTab)
87
+ const isUserMenuTab = currentTab && currentTab.mediatype === 'usermenu';
88
+ if (props.has('currentTab')) {
89
+ if (isUserMenuTab) {
90
+ console.log('inside-user-menu');
91
+ const focusElement = currentTab.moveTo === 'next'
92
+ ? this.shadowRoot.querySelector('a.upload')
93
+ : this.shadowRoot.querySelector('.user-menu');
94
+
95
+ if (focusElement) {
96
+ focusElement.focus();
97
+ }
98
+ } else if (this.currentTab.moveTo === 'next') {
99
+ console.log('inside-media-menu');
100
+ this.shadowRoot.querySelector('.user-menu').focus();
101
+ }
102
+ }
103
+ }
104
+
82
105
  get userIcon() {
83
106
  const userMenuClass = this.openMenu === 'user' ? 'active' : '';
84
107
  const userMenuToolTip = this.openMenu === 'user' ? 'Close user menu' : 'Expand user menu';
@@ -107,7 +130,6 @@ class PrimaryNav extends TrackedElement {
107
130
  .dropdownOpen=${this.signedOutMenuOpen}
108
131
  .openMenu=${this.openMenu}
109
132
  @signedOutMenuToggled=${this.signedOutMenuToggled}
110
- tabindex="-1"
111
133
  ></login-button>
112
134
  `;
113
135
  }
@@ -156,12 +178,8 @@ class PrimaryNav extends TrackedElement {
156
178
  return html`
157
179
  <a href="${formatUrl('/create', this.baseHost)}"
158
180
  class="upload"
159
- tabindex="1"
160
- @focus=${(e) => {
161
- if (e.relatedTarget !== null) {
162
- this.toggleSearchMenu(e)
163
- }
164
- }}>
181
+ @focus=${this.toggleMediaMenu}
182
+ >
165
183
  ${icons.upload}
166
184
  <span>Upload</span>
167
185
  </a>`;
@@ -190,6 +208,15 @@ class PrimaryNav extends TrackedElement {
190
208
  const mediaMenuTabIndex = this.openMenu === 'media' ? '' : '-1';
191
209
  return html`
192
210
  <nav class=${this.hideSearch ? 'hide-search' : nothing}>
211
+ <button
212
+ class="hamburger"
213
+ @click="${this.toggleMediaMenu}"
214
+ data-event-click-tracking="${this.config.eventCategory}|NavHamburger"
215
+ title="Open main menu"
216
+ >
217
+ <icon-hamburger ?active=${this.openMenu === 'media'}></icon-hamburger>
218
+ </button>
219
+
193
220
  <div class=${`branding ${this.secondLogoClass}`}>
194
221
  <a
195
222
  href=${formatUrl('/', this.baseHost)}
@@ -202,30 +229,20 @@ class PrimaryNav extends TrackedElement {
202
229
  >
203
230
  ${this.secondLogoSlot}
204
231
  </div>
205
-
206
- <div class="right-side-section">
207
- ${this.mobileDonateHeart}
208
- ${this.searchMenu}
209
- ${this.uploadButtonTemplate}
210
- ${this.userStateTemplate}
211
- </div>
212
232
  <media-menu
213
233
  .baseHost=${this.baseHost}
214
234
  .config=${this.config}
215
235
  ?mediaMenuAnimate="${this.mediaMenuAnimate}"
216
- tabindex="${mediaMenuTabIndex}"
217
236
  .selectedMenuOption=${this.selectedMenuOption}
218
237
  .openMenu=${this.openMenu}
238
+ .currentTab=${this.currentTab}
219
239
  ></media-menu>
220
- <button
221
- class="hamburger"
222
- @click="${this.toggleMediaMenu}"
223
- tabindex="1"
224
- data-event-click-tracking="${this.config.eventCategory}|NavHamburger"
225
- title="Open main menu"
226
- >
227
- <icon-hamburger ?active=${this.openMenu === 'media'}></icon-hamburger>
228
- </button>
240
+ <div class="right-side-section">
241
+ ${this.mobileDonateHeart}
242
+ ${this.userStateTemplate}
243
+ ${this.uploadButtonTemplate}
244
+ ${this.searchMenu}
245
+ </div>
229
246
  </nav>
230
247
  `;
231
248
  }
@@ -30,26 +30,33 @@ class SearchMenu extends TrackedElement {
30
30
  }
31
31
 
32
32
  firstUpdated() {
33
- this.shadowRoot.addEventListener('keyup', (e) => {
34
- const searchTypes = this.shadowRoot.querySelectorAll('.search-menu-inner label input[type=radio]');
35
- const length = searchTypes.length - 1;
36
-
37
- // early return if searchTypes not found
38
- if (!length) return;
39
-
40
- const searchTypeHandler = (index) => {
41
- const searchType = searchTypes[index];
42
- searchType.checked = true;
43
- searchType.dispatchEvent(new Event('change'));
44
- searchType.focus();
45
- };
46
-
47
- if (e.key === 'Home') {
48
- searchTypeHandler(0);
49
- } else if (e.key === 'End') {
50
- searchTypeHandler(length);
51
- }
52
- });
33
+ this.shadowRoot.addEventListener('keydown', e => this.handleKeyDownEvent(e));
34
+ }
35
+
36
+ disconnectedCallback() {
37
+ // Clean up event listener when the element is removed
38
+ this.shadowRoot.removeEventListener('keydown', e => this.handleKeyDownEvent(e));
39
+ }
40
+
41
+ handleKeyDownEvent(e) {
42
+ const searchTypes = this.shadowRoot.querySelectorAll('.search-menu-inner label input[type=radio]');
43
+
44
+ const length = searchTypes.length - 1;
45
+ if (!length) return;
46
+
47
+ const searchTypeHandler = (index) => {
48
+ e.preventDefault();
49
+ const searchType = searchTypes[index];
50
+ searchType.checked = true;
51
+ searchType.dispatchEvent(new Event('change'));
52
+ searchType.focus();
53
+ };
54
+
55
+ if (e.key === 'Home') {
56
+ searchTypeHandler(0);
57
+ } else if (e.key === 'End') {
58
+ searchTypeHandler(length);
59
+ }
53
60
  }
54
61
 
55
62
  selectSearchType(e) {
@@ -93,7 +100,7 @@ class SearchMenu extends TrackedElement {
93
100
  }
94
101
  return html`
95
102
  <label @click="${this.selectSearchType}">
96
- <input tabindex="3" form="nav-search" type="radio" name="sin" value="${value}" ?checked=${isDefault} @change=${this.searchInChanged} />
103
+ <input form="nav-search" type="radio" name="sin" value="${value}" ?checked=${isDefault} @change=${this.searchInChanged} />
97
104
  Search ${label}
98
105
  </label>
99
106
  `;
@@ -127,7 +134,6 @@ class SearchMenu extends TrackedElement {
127
134
  href="${formatUrl('/advancedsearch.php', this.baseHost)}"
128
135
  @click=${this.trackClick}
129
136
  data-event-click-tracking="${this.config.eventCategory}|NavAdvancedSearch"
130
- tabindex="4"
131
137
  >Advanced Search</a
132
138
  >
133
139
  </div>
@@ -92,6 +92,7 @@ export default css`
92
92
 
93
93
  @media (min-width: 890px) {
94
94
  nav {
95
+ display: flex;
95
96
  overflow: visible;
96
97
  top: 0;
97
98
  left: auto;
@@ -146,6 +147,7 @@ export default css`
146
147
  a:focus {
147
148
  color: var(--linkHoverColor);
148
149
  background: var(--linkColor);
150
+ outline: none;
149
151
  }
150
152
 
151
153
  .initial,
@@ -23,10 +23,6 @@ export default css`
23
23
 
24
24
  /* Mobile view styles */
25
25
  @media (max-width: 889px) {
26
- .media-menu-container {
27
- position: relative;
28
- }
29
-
30
26
  .media-menu-inner {
31
27
  position: absolute;
32
28
  width: 100%;
@@ -39,7 +35,7 @@ export default css`
39
35
  .overflow-clip {
40
36
  position: absolute;
41
37
  z-index: -1; /** needs to be under the navigation, otherwise it intercepts clicks */
42
- top: 0;
38
+ top: 4rem;
43
39
  left: 0;
44
40
  height: 0;
45
41
  width: 100%;
@@ -9,8 +9,7 @@ export default css`
9
9
 
10
10
  nav {
11
11
  position: relative;
12
- display: -ms-grid;
13
- display: grid;
12
+ display: flex;
14
13
  height: 4rem;
15
14
  grid-template-areas: 'hamburger empty heart search user';
16
15
  -ms-grid-columns: 4rem minmax(1rem, 100%) 4rem 4rem 4rem;
@@ -29,6 +28,7 @@ export default css`
29
28
 
30
29
  .right-side-section {
31
30
  display: flex;
31
+ margin-left: auto;
32
32
  user-select: none;
33
33
  }
34
34
  button {
@@ -86,6 +86,9 @@ export default css`
86
86
  fill: var(--activeColor);
87
87
  }
88
88
 
89
+ .mobile-donate-link {
90
+ display: inline-block;
91
+ }
89
92
  .mobile-donate-link svg {
90
93
  height: 4rem;
91
94
  width: 4rem;
@@ -151,7 +154,8 @@ export default css`
151
154
  height: 100%;
152
155
  }
153
156
 
154
- .user-menu:hover {
157
+ .user-menu:hover,
158
+ .user-menu:focus {
155
159
  color: var(--linkHoverColor);
156
160
  }
157
161
 
@@ -187,6 +191,13 @@ export default css`
187
191
  slot[name='opt-sec-logo'] {
188
192
  display: none;
189
193
  }
194
+
195
+ .right-side-section {
196
+ display: initial;
197
+ }
198
+ .right-side-section .user-info {
199
+ float: right;
200
+ }
190
201
  }
191
202
 
192
203
  @media (min-width: 890px) {
@@ -195,12 +206,8 @@ export default css`
195
206
  --userIconHeight: 3.2rem;
196
207
  }
197
208
 
198
- .right-side-section {
199
- display: contents;
200
- }
201
-
202
209
  nav {
203
- display: block;
210
+ display: flex;
204
211
  z-index: 4;
205
212
  height: 5rem;
206
213
  padding-right: 1.5rem;
@@ -89,6 +89,8 @@ export default css`
89
89
 
90
90
  label {
91
91
  padding: 0;
92
+ font-weight: normal;
93
+ margin: 0;
92
94
  }
93
95
 
94
96
  label + label {
package/src/user-menu.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { html } from 'https://offshoot.prod.archive.org/lit.js';
2
2
  import DropdownMenu from './dropdown-menu.js';
3
3
  import userMenuCSS from './styles/user-menu.js';
4
+ import KeyboardNavigation from './lib/keyboard-navigation.js';
4
5
 
5
6
  class UserMenu extends DropdownMenu {
6
7
  static get styles() {
@@ -21,6 +22,19 @@ class UserMenu extends DropdownMenu {
21
22
  this.username = '';
22
23
  }
23
24
 
25
+ updated(props) {
26
+ if (props.has('open') && this.open) {
27
+ const container = this.shadowRoot?.querySelector('.nav-container');
28
+
29
+ if (container) {
30
+ const keyboardNavigation = new KeyboardNavigation(container, 'usermenu');
31
+ this.addEventListener('keydown', keyboardNavigation.handleKeyDown);
32
+ this.removeEventListener('keydown', this.previousKeydownListener);
33
+ this.previousKeydownListener = keyboardNavigation.handleKeyDown;
34
+ }
35
+ }
36
+ }
37
+
24
38
  render() {
25
39
  return html`
26
40
  <div class="nav-container">