@momentum-design/components 0.134.21 → 0.135.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 (50) hide show
  1. package/dist/browser/index.js +582 -477
  2. package/dist/browser/index.js.map +4 -4
  3. package/dist/components/datepicker/datepicker.component.d.ts +5 -0
  4. package/dist/components/datepicker/datepicker.component.js +96 -26
  5. package/dist/components/formfieldwrapper/formfieldwrapper.component.js +7 -3
  6. package/dist/components/formfieldwrapper/formfieldwrapper.constants.js +2 -7
  7. package/dist/components/formfieldwrapper/formfieldwrapper.styles.js +10 -0
  8. package/dist/components/formfieldwrapper/formfieldwrapper.types.d.ts +2 -2
  9. package/dist/components/formfieldwrapper/formfieldwrapper.utils.js +2 -11
  10. package/dist/components/formfieldwrapper/index.d.ts +1 -0
  11. package/dist/components/formfieldwrapper/index.js +1 -0
  12. package/dist/components/menubar/menubar.component.js +2 -1
  13. package/dist/components/menupopover/menupopover.component.d.ts +1 -1
  14. package/dist/components/menupopover/menupopover.component.js +4 -4
  15. package/dist/components/navmenuitem/navmenuitem.component.d.ts +32 -2
  16. package/dist/components/navmenuitem/navmenuitem.component.js +146 -9
  17. package/dist/components/navmenuitem/navmenuitem.constants.d.ts +1 -0
  18. package/dist/components/navmenuitem/navmenuitem.constants.js +1 -0
  19. package/dist/components/navmenuitem/navmenuitem.styles.js +16 -5
  20. package/dist/components/sidenavigation/sidenavigation.component.d.ts +43 -3
  21. package/dist/components/sidenavigation/sidenavigation.component.js +240 -8
  22. package/dist/components/sidenavigation/sidenavigation.constants.d.ts +6 -1
  23. package/dist/components/sidenavigation/sidenavigation.constants.js +6 -1
  24. package/dist/components/sidenavigation/sidenavigation.context.d.ts +6 -1
  25. package/dist/components/sidenavigation/sidenavigation.context.js +36 -11
  26. package/dist/components/sidenavigation/sidenavigation.types.d.ts +3 -2
  27. package/dist/components/sidenavigation/sidenavigationbase.d.ts +8 -0
  28. package/dist/components/sidenavigation/sidenavigationbase.js +7 -0
  29. package/dist/components/statusmessage/index.d.ts +9 -0
  30. package/dist/components/statusmessage/index.js +8 -0
  31. package/dist/components/statusmessage/statusmessage.component.d.ts +54 -0
  32. package/dist/components/statusmessage/statusmessage.component.js +80 -0
  33. package/dist/components/statusmessage/statusmessage.constants.d.ts +28 -0
  34. package/dist/components/statusmessage/statusmessage.constants.js +31 -0
  35. package/dist/components/statusmessage/statusmessage.styles.d.ts +2 -0
  36. package/dist/components/statusmessage/statusmessage.styles.js +66 -0
  37. package/dist/components/statusmessage/statusmessage.types.d.ts +5 -0
  38. package/dist/components/statusmessage/statusmessage.types.js +2 -0
  39. package/dist/custom-elements.json +283 -10
  40. package/dist/index.d.ts +4 -2
  41. package/dist/index.js +2 -1
  42. package/dist/models/provider/provider.component.d.ts +2 -2
  43. package/dist/models/provider/provider.component.js +13 -0
  44. package/dist/react/index.d.ts +1 -0
  45. package/dist/react/index.js +1 -0
  46. package/dist/react/navmenuitem/index.d.ts +4 -2
  47. package/dist/react/navmenuitem/index.js +3 -1
  48. package/dist/react/statusmessage/index.d.ts +37 -0
  49. package/dist/react/statusmessage/index.js +32 -0
  50. package/package.json +1 -1
@@ -10,6 +10,7 @@ const ALLOWED_BADGE_TYPES = {
10
10
  };
11
11
  const ICON_NAME = {
12
12
  RIGHT_ARROW: 'arrow-right-bold',
13
+ DOWN_ARROW: 'arrow-down-bold',
13
14
  };
14
15
  const DEFAULTS = {
15
16
  MAX_COUNTER: 99,
@@ -12,6 +12,7 @@ const styles = [
12
12
  --mdc-navmenuitem-in-sidenav-collapsed-margin-right: var(--mdc-sidenavigation-collapsed-right-padding, 1rem);
13
13
 
14
14
  --mdc-navmenuitem-color: var(--mds-color-theme-text-primary-normal);
15
+ --mdc-navmenuitem-notch-color: var(--mds-color-theme-text-primary-normal);
15
16
  --mdc-navmenuitem-disabled-color: var(--mds-color-theme-text-primary-disabled);
16
17
 
17
18
  /* Background color when in default (normal) or active state */
@@ -25,6 +26,7 @@ const styles = [
25
26
 
26
27
  position: relative;
27
28
  flex-shrink: 0;
29
+ height: 2.5rem;
28
30
  display: flex;
29
31
  align-items: center;
30
32
  gap: 0.5rem;
@@ -54,8 +56,10 @@ const styles = [
54
56
  display: none;
55
57
  }
56
58
 
57
- :host([in-menupopover]) {
59
+ :host([in-menupopover]),
60
+ :host([in-dropdown-container]) {
58
61
  width: 100%;
62
+ padding: 1rem;
59
63
  }
60
64
 
61
65
  :host(:not([in-menupopover])) {
@@ -112,11 +116,12 @@ const styles = [
112
116
  background-color: var(--mdc-navmenuitem-pressed-active-background-color);
113
117
  }
114
118
 
115
- :host(:not([in-menupopover]))::before {
119
+ :host(:not([in-menupopover]))::before,
120
+ :host([in-dropdown-container])::before {
116
121
  content: '';
117
122
  display: block;
118
123
  position: absolute;
119
- background-color: var(--mdc-navmenuitem-color);
124
+ background-color: var(--mdc-navmenuitem-notch-color);
120
125
  width: 0.25rem;
121
126
  height: 1rem;
122
127
  pointer-events: none;
@@ -175,14 +180,20 @@ const styles = [
175
180
  flex-shrink: 0;
176
181
 
177
182
  position: absolute;
178
- inset-inline-end: -0.25rem;
183
+ inset-inline-end: -0.125rem;
179
184
  top: 0.875rem;
180
185
  }
181
186
 
182
- :host([show-label])::part(trailing-arrow) {
187
+ :host([show-label])::part(trailing-arrow),
188
+ :host([show-label])::part(trailing-arrow-dropdown) {
183
189
  flex-shrink: 0;
184
190
  }
185
191
 
192
+ /* Arrow rotation when dropdown is open */
193
+ .arrow-rotated {
194
+ transform: rotate(180deg);
195
+ }
196
+
186
197
  :host mdc-badge {
187
198
  --mdc-badge-dot-width: 1rem;
188
199
  --mdc-badge-dot-height: 1rem;
@@ -1,7 +1,8 @@
1
1
  import { CSSResult, PropertyValues } from 'lit';
2
- import { Provider } from '../../models';
3
- import type { SideNavigationVariant } from './sidenavigation.types';
2
+ import type { SideNavigationSubmenuType, SideNavigationVariant } from './sidenavigation.types';
4
3
  import SideNavigationContext from './sidenavigation.context';
4
+ import SideNavigationBase from './sidenavigationbase';
5
+ declare const SideNavigation_base: import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/KeyToActionMixin").KeyToActionInterface> & typeof SideNavigationBase;
5
6
  /**
6
7
  * The side navigation is a vertically stacked navigation landmark, typically used as a persistent or collapsible sidebar. It supports five layout variants, toggleable expand/collapse behaviour, an optional brand logo, an optional footer text, and acts as a context provider for nested `mdc-menubar` and `mdc-navmenuitem` elements.
7
8
  *
@@ -56,7 +57,7 @@ import SideNavigationContext from './sidenavigation.context';
56
57
  * @cssproperty --mdc-sidenavigation-bottom-padding - padding for the bottom of the scrollable section
57
58
  * @cssproperty --mdc-sidenavigation-vertical-divider-button-z-index - z-index of the vertical divider button
58
59
  */
59
- declare class SideNavigation extends Provider<SideNavigationContext> {
60
+ declare class SideNavigation extends SideNavigation_base {
60
61
  /**
61
62
  * Five variants of the sideNavigation
62
63
  * - **fixed-collapsed**: Shows icons without labels and has fixed width, 4.5rem.
@@ -95,6 +96,17 @@ declare class SideNavigation extends Provider<SideNavigationContext> {
95
96
  * @default false
96
97
  */
97
98
  hideFixedSectionDivider: boolean;
99
+ /**
100
+ * Determines how sibling submenus are displayed. There are 2 types of submenus supported:
101
+ *
102
+ * - `flyout`: Submenus are shown with `mdc-menupopover`.
103
+ * - `dropdown`: A parent `mdc-navmenuitem` can render its submenu inside a sibling
104
+ * `div[data-trigger="<navmenuitem-id>"]` while the sidenavigation is expanded. In collapsed mode,
105
+ * dropdown submenus are converted to flyout menus.
106
+ *
107
+ * @default flyout
108
+ */
109
+ submenuType: SideNavigationSubmenuType;
98
110
  constructor();
99
111
  connectedCallback(): void;
100
112
  disconnectedCallback(): void;
@@ -154,6 +166,34 @@ declare class SideNavigation extends Provider<SideNavigationContext> {
154
166
  * @internal
155
167
  */
156
168
  private get navMenuItems();
169
+ /**
170
+ * Closes all open dropdown containers and resets their parent navmenuitems.
171
+ * @internal
172
+ */
173
+ private closeAllDropdowns;
174
+ /**
175
+ * Hides all div[data-trigger] elements within the sidenavigation.
176
+ * @internal
177
+ */
178
+ private hideAllDropdownContainers;
179
+ /**
180
+ * When collapsed with dropdown submenus, converts div[data-trigger] children into dynamically
181
+ * created menupopover elements for flyout behavior.
182
+ * @internal
183
+ */
184
+ private convertDropdownsToFlyouts;
185
+ /**
186
+ * When expanding with dropdown submenus, converts dynamically created menupopover elements
187
+ * back to div[data-trigger] containers.
188
+ * @internal
189
+ */
190
+ private convertFlyoutsToDropdowns;
191
+ /**
192
+ * Handles Escape key inside dropdown containers to close the dropdown
193
+ * and return focus to the parent trigger navmenuitem.
194
+ * @internal
195
+ */
196
+ private handleDropdownKeyDown;
157
197
  /**
158
198
  * Sets the variant attribute for the sideNavigation component.
159
199
  * If the provided variant is not included in the variant,
@@ -9,15 +9,17 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  };
10
10
  import { html, nothing } from 'lit';
11
11
  import { property } from 'lit/decorators.js';
12
- import { Component, Provider } from '../../models';
12
+ import { Component } from '../../models';
13
13
  import { TYPE, VALID_TEXT_TAGS } from '../text/text.constants';
14
14
  import { TAG_NAME as NAVMENUITEM_TAGNAME } from '../navmenuitem/navmenuitem.constants';
15
+ import { TAG_NAME as MENUPOPOVER_TAGNAME } from '../menupopover/menupopover.constants';
15
16
  import { DIRECTIONS, DIVIDER_VARIANT, DIVIDER_ORIENTATION } from '../divider/divider.constants';
16
17
  import { ROLE } from '../../utils/roles';
17
- import { KEYS } from '../../utils/keys';
18
- import { DEFAULTS, VARIANTS } from './sidenavigation.constants';
18
+ import { ACTIONS, KeyToActionMixin } from '../../utils/mixins/KeyToActionMixin';
19
+ import { DEFAULTS, SUBMENU_TYPES, VARIANTS } from './sidenavigation.constants';
19
20
  import SideNavigationContext from './sidenavigation.context';
20
21
  import styles from './sidenavigation.styles';
22
+ import SideNavigationBase from './sidenavigationbase';
21
23
  /**
22
24
  * @tagname mdc-sidenavigation
23
25
  *
@@ -58,11 +60,11 @@ import styles from './sidenavigation.styles';
58
60
  * @cssproperty --mdc-sidenavigation-bottom-padding - padding for the bottom of the scrollable section
59
61
  * @cssproperty --mdc-sidenavigation-vertical-divider-button-z-index - z-index of the vertical divider button
60
62
  */
61
- class SideNavigation extends Provider {
63
+ class SideNavigation extends KeyToActionMixin(SideNavigationBase) {
62
64
  constructor() {
63
65
  super({
64
66
  context: SideNavigationContext.context,
65
- initialValue: new SideNavigationContext(DEFAULTS.VARIANT, true),
67
+ initialValue: new SideNavigationContext(DEFAULTS.VARIANT, true, DEFAULTS.SUBMENU_TYPE),
66
68
  });
67
69
  /**
68
70
  * Five variants of the sideNavigation
@@ -86,6 +88,17 @@ class SideNavigation extends Provider {
86
88
  * @default false
87
89
  */
88
90
  this.hideFixedSectionDivider = false;
91
+ /**
92
+ * Determines how sibling submenus are displayed. There are 2 types of submenus supported:
93
+ *
94
+ * - `flyout`: Submenus are shown with `mdc-menupopover`.
95
+ * - `dropdown`: A parent `mdc-navmenuitem` can render its submenu inside a sibling
96
+ * `div[data-trigger="<navmenuitem-id>"]` while the sidenavigation is expanded. In collapsed mode,
97
+ * dropdown submenus are converted to flyout menus.
98
+ *
99
+ * @default flyout
100
+ */
101
+ this.submenuType = DEFAULTS.SUBMENU_TYPE;
89
102
  this.isHovered = false;
90
103
  this.isFocused = false;
91
104
  /** @internal */
@@ -167,6 +180,7 @@ class SideNavigation extends Provider {
167
180
  */
168
181
  this.findNav = (navs, navId) => navs.find(nav => nav.navId === navId);
169
182
  this.addEventListener('activechange', this.handleNestedNavMenuItemActiveChange.bind(this));
183
+ this.addEventListener('keydown', this.handleDropdownKeyDown.bind(this));
170
184
  }
171
185
  connectedCallback() {
172
186
  super.connectedCallback();
@@ -206,9 +220,28 @@ class SideNavigation extends Provider {
206
220
  default:
207
221
  }
208
222
  }
209
- if (changedProperties.has('variant') || changedProperties.has('expanded')) {
223
+ if (changedProperties.has('variant') || changedProperties.has('expanded') || changedProperties.has('submenuType')) {
210
224
  this.updateContext();
211
225
  }
226
+ // When collapsing or when dropdown submenus are turned off, close all dropdown containers
227
+ if (changedProperties.has('expanded') || changedProperties.has('submenuType')) {
228
+ if (!this.expanded || this.submenuType !== SUBMENU_TYPES.DROPDOWN) {
229
+ this.closeAllDropdowns();
230
+ }
231
+ // Handle dropdown-to-flyout conversion
232
+ if (this.submenuType === SUBMENU_TYPES.DROPDOWN) {
233
+ if (!this.expanded) {
234
+ this.convertDropdownsToFlyouts();
235
+ }
236
+ else {
237
+ this.convertFlyoutsToDropdowns();
238
+ }
239
+ }
240
+ else {
241
+ // If dropdown submenus are turned off, clean up any dynamic menupopover elements
242
+ this.convertFlyoutsToDropdowns();
243
+ }
244
+ }
212
245
  }
213
246
  firstUpdated(changedProperties) {
214
247
  super.firstUpdated(changedProperties);
@@ -217,6 +250,12 @@ class SideNavigation extends Provider {
217
250
  this.expanded = true;
218
251
  this.updateContext();
219
252
  }
253
+ // Hide all dropdown containers by default
254
+ this.hideAllDropdownContainers();
255
+ // If initially collapsed with dropdown submenus, convert to flyouts
256
+ if (this.submenuType === SUBMENU_TYPES.DROPDOWN && !this.expanded) {
257
+ this.convertDropdownsToFlyouts();
258
+ }
220
259
  }
221
260
  /**
222
261
  * Update all observing components of this
@@ -225,9 +264,12 @@ class SideNavigation extends Provider {
225
264
  * Is called on every re-render, see Provider class
226
265
  */
227
266
  updateContext() {
228
- if (this.context.value.variant !== this.variant || this.context.value.expanded !== this.expanded) {
267
+ if (this.context.value.variant !== this.variant ||
268
+ this.context.value.expanded !== this.expanded ||
269
+ this.context.value.submenuType !== this.submenuType) {
229
270
  this.context.value.variant = this.variant;
230
271
  this.context.value.expanded = this.expanded;
272
+ this.context.value.submenuType = this.submenuType;
231
273
  this.context.updateObservers();
232
274
  }
233
275
  }
@@ -238,6 +280,192 @@ class SideNavigation extends Provider {
238
280
  get navMenuItems() {
239
281
  return Array.from(this.querySelectorAll(`${NAVMENUITEM_TAGNAME}:not([disabled])`));
240
282
  }
283
+ /**
284
+ * Closes all open dropdown containers and resets their parent navmenuitems.
285
+ * @internal
286
+ */
287
+ closeAllDropdowns() {
288
+ this.navMenuItems.filter(item => this.context.value.isDropDownParent(item)).forEach(item => item.closeDropdown());
289
+ // Also hide all dropdown containers
290
+ this.hideAllDropdownContainers();
291
+ }
292
+ /**
293
+ * Hides all div[data-trigger] elements within the sidenavigation.
294
+ * @internal
295
+ */
296
+ hideAllDropdownContainers() {
297
+ const containers = this.querySelectorAll('div[data-trigger]');
298
+ containers.forEach(container => {
299
+ Object.assign(container.style, {
300
+ display: 'none',
301
+ flexDirection: 'column',
302
+ gap: '0.25rem',
303
+ });
304
+ });
305
+ }
306
+ /**
307
+ * When collapsed with dropdown submenus, converts div[data-trigger] children into dynamically
308
+ * created menupopover elements for flyout behavior.
309
+ * @internal
310
+ */
311
+ convertDropdownsToFlyouts() {
312
+ const containers = this.querySelectorAll('div[data-trigger]');
313
+ containers.forEach(container => {
314
+ var _a;
315
+ const triggerId = container.getAttribute('data-trigger');
316
+ if (!triggerId)
317
+ return;
318
+ // Check if a dynamic menupopover already exists for this trigger
319
+ const existingPopover = (_a = container.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector(`${MENUPOPOVER_TAGNAME}[triggerid="${CSS.escape(triggerId)}"][data-dynamic-popover]`);
320
+ if (existingPopover)
321
+ return;
322
+ // Create menupopover
323
+ const popover = document.createElement(MENUPOPOVER_TAGNAME);
324
+ popover.setAttribute('triggerid', triggerId);
325
+ popover.setAttribute('data-dynamic-popover', '');
326
+ popover.setAttribute('placement', 'right-start');
327
+ // Move children from div to menupopover
328
+ while (container.firstChild) {
329
+ popover.appendChild(container.firstChild);
330
+ }
331
+ // Insert menupopover after the div
332
+ container.after(popover);
333
+ // Hide the empty div
334
+ Object.assign(container.style, { display: 'none' });
335
+ });
336
+ }
337
+ /**
338
+ * When expanding with dropdown submenus, converts dynamically created menupopover elements
339
+ * back to div[data-trigger] containers.
340
+ * @internal
341
+ */
342
+ convertFlyoutsToDropdowns() {
343
+ const dynamicPopovers = this.querySelectorAll(`${MENUPOPOVER_TAGNAME}[data-dynamic-popover]`);
344
+ dynamicPopovers.forEach(popover => {
345
+ const triggerId = popover.getAttribute('triggerid');
346
+ if (!triggerId)
347
+ return;
348
+ // Find the corresponding div[data-trigger]
349
+ const container = this.querySelector(`div[data-trigger="${CSS.escape(triggerId)}"]`);
350
+ if (!container)
351
+ return;
352
+ // Move children back from menupopover to div
353
+ while (popover.firstChild) {
354
+ container.appendChild(popover.firstChild);
355
+ }
356
+ // Remove the dynamic menupopover
357
+ popover.remove();
358
+ });
359
+ }
360
+ /**
361
+ * Handles Escape key inside dropdown containers to close the dropdown
362
+ * and return focus to the parent trigger navmenuitem.
363
+ * @internal
364
+ */
365
+ handleDropdownKeyDown(event) {
366
+ var _a, _b;
367
+ if (this.submenuType !== SUBMENU_TYPES.DROPDOWN || !this.expanded)
368
+ return;
369
+ const target = event.target;
370
+ const isNavMenuItem = target.tagName.toLowerCase() === NAVMENUITEM_TAGNAME;
371
+ if (!isNavMenuItem)
372
+ return;
373
+ const targetItem = target;
374
+ const action = this.getActionForKeyEvent(event);
375
+ if (action === ACTIONS.ESCAPE) {
376
+ const dropdownContainer = target.closest('div[data-trigger]');
377
+ if (!dropdownContainer)
378
+ return;
379
+ const triggerId = dropdownContainer.getAttribute('data-trigger');
380
+ if (!triggerId)
381
+ return;
382
+ const triggerItem = this.querySelector(`${NAVMENUITEM_TAGNAME}#${CSS.escape(triggerId)}`);
383
+ if (triggerItem) {
384
+ triggerItem.closeDropdown();
385
+ triggerItem.focus();
386
+ event.stopPropagation();
387
+ }
388
+ return;
389
+ }
390
+ const context = this.context.value;
391
+ const isInsideDropdown = targetItem.hasAttribute('in-dropdown-container');
392
+ const isOpen = targetItem.getAttribute('aria-expanded') === 'true';
393
+ const getDropdownContainer = (item) => { var _a, _b; return (_b = (_a = item.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector(`div[data-trigger="${item.id}"]`)) !== null && _b !== void 0 ? _b : null; };
394
+ const getFirstDropdownItem = (container) => container === null || container === void 0 ? void 0 : container.querySelector(`${NAVMENUITEM_TAGNAME}:not([disabled])`);
395
+ if (isInsideDropdown) {
396
+ const dropdownContainer = target.closest('div[data-trigger]');
397
+ if (!dropdownContainer)
398
+ return;
399
+ const children = Array.from(dropdownContainer.querySelectorAll(`${NAVMENUITEM_TAGNAME}:not([disabled])`));
400
+ const currentIndex = children.indexOf(targetItem);
401
+ if (currentIndex === -1)
402
+ return;
403
+ switch (action) {
404
+ case ACTIONS.DOWN: {
405
+ // Arrow Down: move focus to the next child navmenuitem in the dropdown container, if exists. If on the last child, move focus back to the first child.
406
+ const idx = currentIndex + 1 < children.length ? currentIndex + 1 : 0;
407
+ children[idx].focus();
408
+ event.preventDefault();
409
+ break;
410
+ }
411
+ case ACTIONS.UP: {
412
+ // Arrow Up: move focus to the previous child navmenuitem in the dropdown container, if exists. If on the first child, move focus to the last child.
413
+ const idx = currentIndex > 0 ? currentIndex - 1 : children.length - 1;
414
+ children[idx].focus();
415
+ event.preventDefault();
416
+ break;
417
+ }
418
+ case ACTIONS.RIGHT: {
419
+ // Arrow Right: move focus to the next parent-level navmenuitem in the main list (if exists). If this parent-level navmenuitem has a dropdown, then open the dropdown and move focus to the first child navmenuitem in the dropdown container.
420
+ const outerItems = this.navMenuItems.filter(item => !item.hasAttribute('in-dropdown-container'));
421
+ const triggerId = dropdownContainer.getAttribute('data-trigger');
422
+ const triggerIndex = outerItems.findIndex(item => item.id === triggerId);
423
+ if (triggerIndex !== -1 && triggerIndex + 1 < outerItems.length) {
424
+ const nextItem = outerItems[triggerIndex + 1];
425
+ nextItem.focus();
426
+ if (context.isDropDownParent(nextItem)) {
427
+ nextItem.openDropdown();
428
+ const nextDropdownContainer = getDropdownContainer(nextItem);
429
+ (_a = getFirstDropdownItem(nextDropdownContainer)) === null || _a === void 0 ? void 0 : _a.focus();
430
+ }
431
+ }
432
+ break;
433
+ }
434
+ case ACTIONS.LEFT: {
435
+ // Arrow Left: move focus to the previous parent-level navmenuitem in the main list (if exists). If this parent-level navmenuitem has a dropdown, then open the dropdown and move focus to the first child navmenuitem in the dropdown container.
436
+ const outerItems = this.navMenuItems.filter(item => !item.hasAttribute('in-dropdown-container'));
437
+ const triggerId = dropdownContainer.getAttribute('data-trigger');
438
+ const triggerIndex = outerItems.findIndex(item => item.id === triggerId);
439
+ if (triggerIndex > 0) {
440
+ const previousItem = outerItems[triggerIndex - 1];
441
+ previousItem.focus();
442
+ if (context.isDropDownParent(previousItem)) {
443
+ previousItem.openDropdown();
444
+ const previousDropdownContainer = getDropdownContainer(previousItem);
445
+ (_b = getFirstDropdownItem(previousDropdownContainer)) === null || _b === void 0 ? void 0 : _b.focus();
446
+ }
447
+ }
448
+ break;
449
+ }
450
+ default:
451
+ event.preventDefault();
452
+ }
453
+ }
454
+ // if the dropdown is closed and ArrowRight is pressed on the parent navmenuitem, open the dropdown and move focus to the first child navmenuitem in the dropdown container
455
+ if (action === ACTIONS.RIGHT) {
456
+ const dropdownContainer = getDropdownContainer(targetItem);
457
+ if (dropdownContainer) {
458
+ if (!isOpen) {
459
+ targetItem.openDropdown();
460
+ }
461
+ const firstChild = getFirstDropdownItem(dropdownContainer);
462
+ if (firstChild) {
463
+ firstChild.focus();
464
+ event.preventDefault();
465
+ }
466
+ }
467
+ }
468
+ }
241
469
  /**
242
470
  * Sets the variant attribute for the sideNavigation component.
243
471
  * If the provided variant is not included in the variant,
@@ -267,7 +495,7 @@ class SideNavigation extends Provider {
267
495
  }
268
496
  preventScrollOnSpace(event) {
269
497
  // Prevent default space key behavior to avoid scrolling the page
270
- if (event.key === KEYS.SPACE) {
498
+ if (this.getActionForKeyEvent(event) === ACTIONS.SPACE) {
271
499
  event.preventDefault();
272
500
  }
273
501
  }
@@ -352,4 +580,8 @@ __decorate([
352
580
  property({ type: Boolean, reflect: true, attribute: 'hide-fixed-section-divider' }),
353
581
  __metadata("design:type", Boolean)
354
582
  ], SideNavigation.prototype, "hideFixedSectionDivider", void 0);
583
+ __decorate([
584
+ property({ type: String, reflect: true, attribute: 'submenu-type' }),
585
+ __metadata("design:type", String)
586
+ ], SideNavigation.prototype, "submenuType", void 0);
355
587
  export default SideNavigation;
@@ -6,7 +6,12 @@ declare const VARIANTS: {
6
6
  FLEXIBLE_ON_HOVER: string;
7
7
  HIDDEN: string;
8
8
  };
9
+ declare const SUBMENU_TYPES: {
10
+ FLYOUT: string;
11
+ DROPDOWN: string;
12
+ };
9
13
  declare const DEFAULTS: {
10
14
  readonly VARIANT: string;
15
+ readonly SUBMENU_TYPE: string;
11
16
  };
12
- export { DEFAULTS, VARIANTS, TAG_NAME };
17
+ export { DEFAULTS, VARIANTS, SUBMENU_TYPES, TAG_NAME };
@@ -7,7 +7,12 @@ const VARIANTS = {
7
7
  FLEXIBLE_ON_HOVER: 'flexible-on-hover',
8
8
  HIDDEN: 'hidden',
9
9
  };
10
+ const SUBMENU_TYPES = {
11
+ FLYOUT: 'flyout',
12
+ DROPDOWN: 'dropdown',
13
+ };
10
14
  const DEFAULTS = {
11
15
  VARIANT: VARIANTS.FLEXIBLE,
16
+ SUBMENU_TYPE: SUBMENU_TYPES.FLYOUT,
12
17
  };
13
- export { DEFAULTS, VARIANTS, TAG_NAME };
18
+ export { DEFAULTS, VARIANTS, SUBMENU_TYPES, TAG_NAME };
@@ -1,14 +1,19 @@
1
1
  import type NavMenuItem from '../navmenuitem/navmenuitem.component';
2
+ import type { SideNavigationSubmenuType } from './sidenavigation.types';
2
3
  declare class SideNavigationContext {
3
4
  variant?: string;
4
5
  expanded?: boolean;
6
+ submenuType?: SideNavigationSubmenuType;
5
7
  private currentActiveNavMenuItem?;
6
8
  static context: {
7
9
  __context__: SideNavigationContext;
8
10
  };
9
- constructor(defaultVariant?: string, defaultExpanded?: boolean);
11
+ constructor(defaultVariant?: string, defaultExpanded?: boolean, defaultSubmenuType?: SideNavigationSubmenuType);
12
+ get isDropdownSubmenuType(): boolean;
10
13
  hasSiblingWithTriggerId(navMenuItem: NavMenuItem | undefined): boolean;
14
+ isDropDownParent(navMenuItem: NavMenuItem | undefined): boolean;
11
15
  private getParentNavMenuItems;
16
+ private getTriggerElement;
12
17
  setCurrentActiveNavMenuItem(navMenuItem: NavMenuItem | undefined): void;
13
18
  }
14
19
  export default SideNavigationContext;
@@ -2,11 +2,15 @@
2
2
  import { createContext } from '@lit/context';
3
3
  import { TAG_NAME as MENUPOPOVER_TAGNAME } from '../menupopover/menupopover.constants';
4
4
  import { TAG_NAME as NAVMENUITEM_TAGNAME } from '../navmenuitem/navmenuitem.constants';
5
- import { TAG_NAME } from './sidenavigation.constants';
5
+ import { SUBMENU_TYPES, TAG_NAME } from './sidenavigation.constants';
6
6
  class SideNavigationContext {
7
- constructor(defaultVariant, defaultExpanded) {
7
+ constructor(defaultVariant, defaultExpanded, defaultSubmenuType) {
8
8
  this.variant = defaultVariant;
9
9
  this.expanded = defaultExpanded;
10
+ this.submenuType = defaultSubmenuType;
11
+ }
12
+ get isDropdownSubmenuType() {
13
+ return this.submenuType === SUBMENU_TYPES.DROPDOWN;
10
14
  }
11
15
  hasSiblingWithTriggerId(navMenuItem) {
12
16
  var _a, _b;
@@ -18,21 +22,33 @@ class SideNavigationContext {
18
22
  sibling.tagName.toLowerCase() === MENUPOPOVER_TAGNAME &&
19
23
  sibling.getAttribute('triggerid') === id);
20
24
  }
25
+ isDropDownParent(navMenuItem) {
26
+ var _a, _b;
27
+ const id = navMenuItem === null || navMenuItem === void 0 ? void 0 : navMenuItem.getAttribute('id');
28
+ if (!id)
29
+ return false;
30
+ const siblings = Array.from((_b = (_a = navMenuItem === null || navMenuItem === void 0 ? void 0 : navMenuItem.parentElement) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : []);
31
+ return siblings.some(sibling => sibling !== navMenuItem && sibling.matches(`div[data-trigger="${id}"]`));
32
+ }
21
33
  getParentNavMenuItems(navMenuItem) {
34
+ var _a;
22
35
  if (!navMenuItem)
23
36
  return [];
24
37
  const parents = [];
25
38
  let current = navMenuItem;
26
39
  while (current) {
27
- // Walk up to find the menupopover
40
+ let triggeringNavMenuItem = null;
41
+ // A navmenuitem is either inside a menupopover (flyout) or a div[data-trigger] (dropdown), never both.
28
42
  const popover = current === null || current === void 0 ? void 0 : current.closest(MENUPOPOVER_TAGNAME);
29
- if (!popover)
30
- break;
31
- const triggerId = popover.getAttribute('triggerid');
32
- if (!triggerId)
33
- break;
34
- // Find the NavMenuItem that triggered this menupopover
35
- const triggeringNavMenuItem = document.getElementById(triggerId);
43
+ if (popover) {
44
+ triggeringNavMenuItem = this.getTriggerElement(popover, 'triggerid');
45
+ }
46
+ else {
47
+ const dropdownContainer = (_a = current === null || current === void 0 ? void 0 : current.parentElement) === null || _a === void 0 ? void 0 : _a.closest('div[data-trigger]');
48
+ if (dropdownContainer) {
49
+ triggeringNavMenuItem = this.getTriggerElement(dropdownContainer, 'data-trigger');
50
+ }
51
+ }
36
52
  if (triggeringNavMenuItem && triggeringNavMenuItem.tagName.toLowerCase() === NAVMENUITEM_TAGNAME) {
37
53
  parents.push(triggeringNavMenuItem);
38
54
  current = triggeringNavMenuItem;
@@ -43,10 +59,19 @@ class SideNavigationContext {
43
59
  }
44
60
  return parents;
45
61
  }
62
+ getTriggerElement(container, attribute) {
63
+ const triggerId = container.getAttribute(attribute);
64
+ if (!triggerId)
65
+ return null;
66
+ return document.getElementById(triggerId);
67
+ }
46
68
  setCurrentActiveNavMenuItem(navMenuItem) {
47
69
  var _a;
48
70
  const isSameItem = ((_a = this.currentActiveNavMenuItem) === null || _a === void 0 ? void 0 : _a.navId) === (navMenuItem === null || navMenuItem === void 0 ? void 0 : navMenuItem.navId);
49
- const shouldSkip = (navMenuItem === null || navMenuItem === void 0 ? void 0 : navMenuItem.cannotActivate) || this.hasSiblingWithTriggerId(navMenuItem) || (navMenuItem === null || navMenuItem === void 0 ? void 0 : navMenuItem.softDisabled);
71
+ const shouldSkip = (navMenuItem === null || navMenuItem === void 0 ? void 0 : navMenuItem.cannotActivate) ||
72
+ this.hasSiblingWithTriggerId(navMenuItem) ||
73
+ (this.isDropdownSubmenuType && this.expanded && this.isDropDownParent(navMenuItem)) ||
74
+ (navMenuItem === null || navMenuItem === void 0 ? void 0 : navMenuItem.softDisabled);
50
75
  if (isSameItem || shouldSkip)
51
76
  return;
52
77
  // Clean up previous active item
@@ -1,7 +1,8 @@
1
1
  import type { TypedCustomEvent, ValueOf } from '../../utils/types';
2
2
  import type SideNavigation from './sidenavigation.component';
3
- import { VARIANTS } from './sidenavigation.constants';
3
+ import { SUBMENU_TYPES, VARIANTS } from './sidenavigation.constants';
4
4
  type SideNavigationVariant = ValueOf<typeof VARIANTS>;
5
+ type SideNavigationSubmenuType = ValueOf<typeof SUBMENU_TYPES>;
5
6
  type SideNavigationActiveChangeEvent = TypedCustomEvent<SideNavigation, {
6
7
  navId: string;
7
8
  active: boolean;
@@ -13,4 +14,4 @@ interface Events {
13
14
  onActiveChangeEvent: SideNavigationActiveChangeEvent;
14
15
  onToggleEvent: SideNavigationToggleEvent;
15
16
  }
16
- export type { SideNavigationVariant, Events };
17
+ export type { SideNavigationVariant, SideNavigationSubmenuType, Events };
@@ -0,0 +1,8 @@
1
+ import { Provider } from '../../models';
2
+ import SideNavigationContext from './sidenavigation.context';
3
+ /**
4
+ * The `SideNavigationBase` class serves as a foundational component for the side navigation feature. It extends the `Provider` class, which allows it to manage and provide the `SideNavigationContext` to its child components. Since `KeyToActionMixin` cannot be directly applied to a `Provider` with context, this base class is created as an intermediate layer. The `SideNavigation` component then extends this class and wraps it with `KeyToActionMixin` to gain mixin functionality while maintaining context management.
5
+ */
6
+ declare class SideNavigationBase extends Provider<SideNavigationContext> {
7
+ }
8
+ export default SideNavigationBase;
@@ -0,0 +1,7 @@
1
+ import { Provider } from '../../models';
2
+ /**
3
+ * The `SideNavigationBase` class serves as a foundational component for the side navigation feature. It extends the `Provider` class, which allows it to manage and provide the `SideNavigationContext` to its child components. Since `KeyToActionMixin` cannot be directly applied to a `Provider` with context, this base class is created as an intermediate layer. The `SideNavigation` component then extends this class and wraps it with `KeyToActionMixin` to gain mixin functionality while maintaining context management.
4
+ */
5
+ class SideNavigationBase extends Provider {
6
+ }
7
+ export default SideNavigationBase;
@@ -0,0 +1,9 @@
1
+ import '../icon';
2
+ import '../text';
3
+ import StatusMessage from './statusmessage.component';
4
+ declare global {
5
+ interface HTMLElementTagNameMap {
6
+ ['mdc-statusmessage']: StatusMessage;
7
+ }
8
+ }
9
+ export default StatusMessage;
@@ -0,0 +1,8 @@
1
+ // AI-Assisted
2
+ import '../icon';
3
+ import '../text';
4
+ import StatusMessage from './statusmessage.component';
5
+ import { TAG_NAME } from './statusmessage.constants';
6
+ StatusMessage.register(TAG_NAME);
7
+ export default StatusMessage;
8
+ // End AI-Assisted