@momentum-design/components 0.83.4 → 0.83.6
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/dist/browser/index.js +207 -207
- package/dist/browser/index.js.map +4 -4
- package/dist/components/listitem/listitem.component.d.ts +4 -3
- package/dist/components/listitem/listitem.component.js +2 -1
- package/dist/components/menuitem/menuitem.component.d.ts +16 -0
- package/dist/components/menuitem/menuitem.component.js +30 -0
- package/dist/components/menuitemcheckbox/menuitemcheckbox.component.d.ts +26 -0
- package/dist/components/menuitemcheckbox/menuitemcheckbox.component.js +32 -1
- package/dist/components/menuitemradio/menuitemradio.component.d.ts +11 -0
- package/dist/components/menuitemradio/menuitemradio.component.js +33 -2
- package/dist/components/menupopover/menupopover.component.d.ts +66 -2
- package/dist/components/menupopover/menupopover.component.js +202 -10
- package/dist/components/menupopover/menupopover.utils.d.ts +9 -0
- package/dist/components/menupopover/menupopover.utils.js +21 -0
- package/dist/components/menusection/menusection.component.d.ts +0 -27
- package/dist/components/menusection/menusection.component.js +0 -66
- package/dist/components/popover/popover.component.d.ts +3 -3
- package/dist/custom-elements.json +647 -1016
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +1 -1
- package/dist/utils/mixins/MenuMixin.js +4 -1
- package/package.json +1 -1
@@ -98,14 +98,15 @@ declare class ListItem extends ListItem_base {
|
|
98
98
|
constructor();
|
99
99
|
connectedCallback(): void;
|
100
100
|
/**
|
101
|
-
* Fires the click event when the enter or space key is pressed.
|
101
|
+
* Fires the click event when the enter or space key is pressed down.
|
102
|
+
* This behavior is similar to a button click and key interaction.
|
102
103
|
* @param event - The keyboard event triggered when a key is pressed down.
|
103
104
|
*/
|
104
|
-
|
105
|
+
protected handleKeyDown(event: KeyboardEvent): void;
|
105
106
|
/**
|
106
107
|
* Triggers a click event on the list item.
|
107
108
|
*/
|
108
|
-
|
109
|
+
protected triggerClickEvent(): void;
|
109
110
|
/**
|
110
111
|
* Handles the click event on the list item.
|
111
112
|
* If the tooltip is open, it has to be closed first.
|
@@ -90,7 +90,8 @@ class ListItem extends DisabledMixin(TabIndexMixin(Component)) {
|
|
90
90
|
this.role = this.role || ROLE.LISTITEM;
|
91
91
|
}
|
92
92
|
/**
|
93
|
-
* Fires the click event when the enter or space key is pressed.
|
93
|
+
* Fires the click event when the enter or space key is pressed down.
|
94
|
+
* This behavior is similar to a button click and key interaction.
|
94
95
|
* @param event - The keyboard event triggered when a key is pressed down.
|
95
96
|
*/
|
96
97
|
handleKeyDown(event) {
|
@@ -36,6 +36,22 @@ declare class MenuItem extends ListItem {
|
|
36
36
|
* - `'negative'`: Arrow points toward the leading side.
|
37
37
|
*/
|
38
38
|
arrowDirection?: ArrowDirections;
|
39
|
+
constructor();
|
40
|
+
/**
|
41
|
+
* Handles the keydown event for the menu item.
|
42
|
+
* If the Enter key is pressed, it triggers a click event on the menu item.
|
43
|
+
* This allows keyboard users to activate the menu item using the Enter key.
|
44
|
+
* @param event - The keyboard event that triggered the action.
|
45
|
+
*/
|
46
|
+
handleKeyDown(event: KeyboardEvent): void;
|
47
|
+
/**
|
48
|
+
* Handles the keyup event for the menu item.
|
49
|
+
* If the Space key is released, it triggers a click event on the menu item.
|
50
|
+
* This allows keyboard users to activate the menu item using the Space key.
|
51
|
+
* It also prevents the default action of the Space key to avoid scrolling the page.
|
52
|
+
* @param event - The keyboard event that triggered the action.
|
53
|
+
*/
|
54
|
+
private handleKeyUp;
|
39
55
|
connectedCallback(): void;
|
40
56
|
/**
|
41
57
|
* Renders the trailing controls slot and optionally the trailing arrow icon,
|
@@ -14,6 +14,7 @@ import ListItem from '../listitem/listitem.component';
|
|
14
14
|
import { LISTITEM_VARIANTS } from '../listitem/listitem.constants';
|
15
15
|
import { ARROW_ICONS, ARROW_DIRECTIONS, ARROW_POSITIONS } from './menuitem.constants';
|
16
16
|
import styles from './menuitem.styles';
|
17
|
+
import { KEYS } from '../../utils/keys';
|
17
18
|
/**
|
18
19
|
* menuitem component is inherited by listitem component with the role set `menuitem`.<br/>
|
19
20
|
* A menu item can contain an icon on the leading or trailing side.
|
@@ -35,6 +36,35 @@ import styles from './menuitem.styles';
|
|
35
36
|
* @event focus - (React: onFocus) This event is dispatched when the menuitem receives focus.
|
36
37
|
*/
|
37
38
|
class MenuItem extends ListItem {
|
39
|
+
constructor() {
|
40
|
+
super();
|
41
|
+
this.addEventListener('keyup', this.handleKeyUp);
|
42
|
+
}
|
43
|
+
/**
|
44
|
+
* Handles the keydown event for the menu item.
|
45
|
+
* If the Enter key is pressed, it triggers a click event on the menu item.
|
46
|
+
* This allows keyboard users to activate the menu item using the Enter key.
|
47
|
+
* @param event - The keyboard event that triggered the action.
|
48
|
+
*/
|
49
|
+
handleKeyDown(event) {
|
50
|
+
if (event.key === KEYS.ENTER) {
|
51
|
+
this.triggerClickEvent();
|
52
|
+
event.preventDefault();
|
53
|
+
}
|
54
|
+
}
|
55
|
+
/**
|
56
|
+
* Handles the keyup event for the menu item.
|
57
|
+
* If the Space key is released, it triggers a click event on the menu item.
|
58
|
+
* This allows keyboard users to activate the menu item using the Space key.
|
59
|
+
* It also prevents the default action of the Space key to avoid scrolling the page.
|
60
|
+
* @param event - The keyboard event that triggered the action.
|
61
|
+
*/
|
62
|
+
handleKeyUp(event) {
|
63
|
+
if (event.key === KEYS.SPACE) {
|
64
|
+
this.triggerClickEvent();
|
65
|
+
event.preventDefault();
|
66
|
+
}
|
67
|
+
}
|
38
68
|
connectedCallback() {
|
39
69
|
super.connectedCallback();
|
40
70
|
this.role = ROLE.MENUITEM;
|
@@ -45,9 +45,35 @@ declare class MenuItemCheckbox extends MenuItem {
|
|
45
45
|
* @default 'checkbox'
|
46
46
|
*/
|
47
47
|
indicator: Indicator;
|
48
|
+
constructor();
|
48
49
|
connectedCallback(): void;
|
50
|
+
/**
|
51
|
+
* Handles click events to toggle checked state
|
52
|
+
* If the menuitemcheckbox is disabled, it does nothing.
|
53
|
+
* If the menuitemcheckbox is not disabled, it toggles the `aria-checked` state between `true` and `false`.
|
54
|
+
*/
|
55
|
+
private menuitemcheckboxHandleClick;
|
56
|
+
/**
|
57
|
+
* Returns a static checkbox element if the indicator is set to checkbox.
|
58
|
+
* If the indicator is not set to checkbox, it returns nothing.
|
59
|
+
* @returns TemplateResult | typeof nothing
|
60
|
+
*/
|
49
61
|
private staticCheckbox;
|
62
|
+
/**
|
63
|
+
* Returns a static toggle element if the indicator is set to toggle.
|
64
|
+
* If the indicator is not set to toggle, it returns nothing.
|
65
|
+
*
|
66
|
+
* The toggle will always be positioned on the trailing side of the menuitem label.
|
67
|
+
* @returns TemplateResult | typeof nothing
|
68
|
+
*/
|
50
69
|
private staticToggle;
|
70
|
+
/**
|
71
|
+
* Returns a checkmark icon if the indicator is set to checkmark and the aria-checked state is true.
|
72
|
+
* If the indicator is not set to checkmark or the aria-checked state is false, it returns nothing.
|
73
|
+
*
|
74
|
+
* The checkmark icon will always be positioned on the trailing side of the menuitem label.
|
75
|
+
* @returns TemplateResult | typeof nothing
|
76
|
+
*/
|
51
77
|
private getCheckmarkIcon;
|
52
78
|
render(): TemplateResult<1>;
|
53
79
|
static styles: Array<CSSResult>;
|
@@ -50,7 +50,7 @@ import styles from './menuitemcheckbox.styles';
|
|
50
50
|
*/
|
51
51
|
class MenuItemCheckbox extends MenuItem {
|
52
52
|
constructor() {
|
53
|
-
super(
|
53
|
+
super();
|
54
54
|
/**
|
55
55
|
* The aria-checked attribute is used to indicate that the menuitemcheckbox is checked or not.
|
56
56
|
* @default 'false'
|
@@ -61,11 +61,28 @@ class MenuItemCheckbox extends MenuItem {
|
|
61
61
|
* @default 'checkbox'
|
62
62
|
*/
|
63
63
|
this.indicator = DEFAULTS.INDICATOR;
|
64
|
+
/**
|
65
|
+
* Handles click events to toggle checked state
|
66
|
+
* If the menuitemcheckbox is disabled, it does nothing.
|
67
|
+
* If the menuitemcheckbox is not disabled, it toggles the `aria-checked` state between `true` and `false`.
|
68
|
+
*/
|
69
|
+
this.menuitemcheckboxHandleClick = () => {
|
70
|
+
if (this.disabled)
|
71
|
+
return;
|
72
|
+
const prevChecked = this.ariaChecked === ARIA_CHECKED_STATES.TRUE;
|
73
|
+
this.ariaChecked = prevChecked ? ARIA_CHECKED_STATES.FALSE : ARIA_CHECKED_STATES.TRUE;
|
74
|
+
};
|
75
|
+
this.addEventListener('click', this.menuitemcheckboxHandleClick);
|
64
76
|
}
|
65
77
|
connectedCallback() {
|
66
78
|
super.connectedCallback();
|
67
79
|
this.role = ROLE.MENUITEMCHECKBOX;
|
68
80
|
}
|
81
|
+
/**
|
82
|
+
* Returns a static checkbox element if the indicator is set to checkbox.
|
83
|
+
* If the indicator is not set to checkbox, it returns nothing.
|
84
|
+
* @returns TemplateResult | typeof nothing
|
85
|
+
*/
|
69
86
|
staticCheckbox() {
|
70
87
|
if (this.indicator !== INDICATOR.CHECKBOX) {
|
71
88
|
return nothing;
|
@@ -78,6 +95,13 @@ class MenuItemCheckbox extends MenuItem {
|
|
78
95
|
></mdc-staticcheckbox>
|
79
96
|
`;
|
80
97
|
}
|
98
|
+
/**
|
99
|
+
* Returns a static toggle element if the indicator is set to toggle.
|
100
|
+
* If the indicator is not set to toggle, it returns nothing.
|
101
|
+
*
|
102
|
+
* The toggle will always be positioned on the trailing side of the menuitem label.
|
103
|
+
* @returns TemplateResult | typeof nothing
|
104
|
+
*/
|
81
105
|
staticToggle() {
|
82
106
|
if (this.indicator !== INDICATOR.TOGGLE) {
|
83
107
|
return nothing;
|
@@ -91,6 +115,13 @@ class MenuItemCheckbox extends MenuItem {
|
|
91
115
|
></mdc-statictoggle>
|
92
116
|
`;
|
93
117
|
}
|
118
|
+
/**
|
119
|
+
* Returns a checkmark icon if the indicator is set to checkmark and the aria-checked state is true.
|
120
|
+
* If the indicator is not set to checkmark or the aria-checked state is false, it returns nothing.
|
121
|
+
*
|
122
|
+
* The checkmark icon will always be positioned on the trailing side of the menuitem label.
|
123
|
+
* @returns TemplateResult | typeof nothing
|
124
|
+
*/
|
94
125
|
getCheckmarkIcon() {
|
95
126
|
if (this.indicator !== INDICATOR.CHECKMARK || this.ariaChecked === ARIA_CHECKED_STATES.FALSE) {
|
96
127
|
return nothing;
|
@@ -27,7 +27,18 @@ declare class MenuItemRadio extends MenuItem {
|
|
27
27
|
* @default 'false'
|
28
28
|
*/
|
29
29
|
ariaChecked: AriaCheckedStates;
|
30
|
+
/**
|
31
|
+
* The name attribute is used to group radio items within the same menu container.
|
32
|
+
*/
|
33
|
+
name: string;
|
34
|
+
constructor();
|
30
35
|
connectedCallback(): void;
|
36
|
+
/**
|
37
|
+
* Handles click events to set checked state and uncheck siblings in the same group and container.
|
38
|
+
* If the menuitemradio is not checked, it sets its aria-checked state to `true`
|
39
|
+
* and sets all other menuitemradio elements of the same group with aria-checked state to `false`.
|
40
|
+
*/
|
41
|
+
private menuitemradioHandleClick;
|
31
42
|
render(): import("lit-html").TemplateResult<1>;
|
32
43
|
static styles: Array<CSSResult>;
|
33
44
|
}
|
@@ -12,7 +12,8 @@ import { property } from 'lit/decorators.js';
|
|
12
12
|
import { ROLE } from '../../utils/roles';
|
13
13
|
import MenuItem from '../menuitem/menuitem.component';
|
14
14
|
import { TYPE } from '../text/text.constants';
|
15
|
-
import { ARIA_CHECKED_STATES } from '../menusection/menusection.constants';
|
15
|
+
import { ARIA_CHECKED_STATES, TAG_NAME as MENUSECTION_TAGNAME } from '../menusection/menusection.constants';
|
16
|
+
import { TAG_NAME as MENUPOPOVER_TAGNAME } from '../menupopover/menupopover.constants';
|
16
17
|
/**
|
17
18
|
* A menuitemradio component is a checkable menuitem that is used in a menu.
|
18
19
|
* A menuitemradio should be checked only one at a time. <br/>
|
@@ -35,12 +36,38 @@ import { ARIA_CHECKED_STATES } from '../menusection/menusection.constants';
|
|
35
36
|
*/
|
36
37
|
class MenuItemRadio extends MenuItem {
|
37
38
|
constructor() {
|
38
|
-
super(
|
39
|
+
super();
|
39
40
|
/**
|
40
41
|
* The aria-checked attribute is used to indicate that the menuitemradio is checked or not.
|
41
42
|
* @default 'false'
|
42
43
|
*/
|
43
44
|
this.ariaChecked = ARIA_CHECKED_STATES.FALSE;
|
45
|
+
/**
|
46
|
+
* The name attribute is used to group radio items within the same menu container.
|
47
|
+
*/
|
48
|
+
this.name = '';
|
49
|
+
/**
|
50
|
+
* Handles click events to set checked state and uncheck siblings in the same group and container.
|
51
|
+
* If the menuitemradio is not checked, it sets its aria-checked state to `true`
|
52
|
+
* and sets all other menuitemradio elements of the same group with aria-checked state to `false`.
|
53
|
+
*/
|
54
|
+
this.menuitemradioHandleClick = () => {
|
55
|
+
if (this.disabled || this.ariaChecked === ARIA_CHECKED_STATES.TRUE)
|
56
|
+
return;
|
57
|
+
// Find the closest menu container (menupopover or menusection)
|
58
|
+
const container = this.closest(`${MENUSECTION_TAGNAME}, ${MENUPOPOVER_TAGNAME}`);
|
59
|
+
if (container) {
|
60
|
+
const radios = Array.from(container.querySelectorAll(this.tagName));
|
61
|
+
radios.forEach((item) => {
|
62
|
+
const radio = item;
|
63
|
+
if (radio.name === this.name) {
|
64
|
+
radio.ariaChecked = ARIA_CHECKED_STATES.FALSE;
|
65
|
+
}
|
66
|
+
});
|
67
|
+
}
|
68
|
+
this.ariaChecked = ARIA_CHECKED_STATES.TRUE;
|
69
|
+
};
|
70
|
+
this.addEventListener('click', this.menuitemradioHandleClick);
|
44
71
|
}
|
45
72
|
connectedCallback() {
|
46
73
|
super.connectedCallback();
|
@@ -67,4 +94,8 @@ __decorate([
|
|
67
94
|
property({ type: String, reflect: true, attribute: 'aria-checked' }),
|
68
95
|
__metadata("design:type", String)
|
69
96
|
], MenuItemRadio.prototype, "ariaChecked", void 0);
|
97
|
+
__decorate([
|
98
|
+
property({ type: String, reflect: true }),
|
99
|
+
__metadata("design:type", Object)
|
100
|
+
], MenuItemRadio.prototype, "name", void 0);
|
70
101
|
export default MenuItemRadio;
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { CSSResult, PropertyValues } from 'lit';
|
2
2
|
import Popover from '../popover/popover.component';
|
3
|
-
declare const MenuPopover_base: import("../../utils/mixins/index.types").Constructor<import("../../utils/mixins/MenuMixin").MenuMixinInterface> & typeof Popover;
|
4
3
|
/**
|
5
4
|
* A popover menu component that displays a list of menu items in a floating container.
|
6
5
|
* It's designed to work in conjunction with `mdc-menubar` and `mdc-menuitem` to create
|
@@ -21,10 +20,75 @@ declare const MenuPopover_base: import("../../utils/mixins/index.types").Constru
|
|
21
20
|
* @tagname mdc-menupopover
|
22
21
|
* @slot default - Contains the menu items to be displayed in the popover
|
23
22
|
*/
|
24
|
-
declare class MenuPopover extends
|
23
|
+
declare class MenuPopover extends Popover {
|
25
24
|
constructor();
|
25
|
+
/** @internal */
|
26
|
+
get menuItems(): Array<HTMLElement>;
|
27
|
+
hidePopover: () => void;
|
26
28
|
connectedCallback(): void;
|
27
29
|
firstUpdated(changedProperties: PropertyValues): Promise<void>;
|
30
|
+
/**
|
31
|
+
* Retrieves the current index of the menu item that triggered the event.
|
32
|
+
* @param target - The target element that triggered the event.
|
33
|
+
* @returns - The index of the current menu item in the `menuItems` array.
|
34
|
+
*/
|
35
|
+
private getCurrentIndex;
|
36
|
+
/**
|
37
|
+
* Resets the tabindex of the currently focused menu item and sets focus to a new menu item.
|
38
|
+
* @param newIndex - The index of the new menu item to focus.
|
39
|
+
* @param oldIndex - The index of the currently focused menu item.
|
40
|
+
* @returns - This method does not return anything.
|
41
|
+
*/
|
42
|
+
private resetTabIndexAndSetFocus;
|
43
|
+
/**
|
44
|
+
* Closes all menu popovers in the stack.
|
45
|
+
* This method is used to ensure that when a menu item is clicked,
|
46
|
+
* all other open popovers are closed, maintaining a clean user interface.
|
47
|
+
* It iterates through the `popoverStack` and hides each popover until the stack is empty.
|
48
|
+
*/
|
49
|
+
private closeAllMenuPopovers;
|
50
|
+
/**
|
51
|
+
* Handles outside click events to close the popover.
|
52
|
+
* This method checks if the click occurred outside the popover and its trigger element.
|
53
|
+
* If so, it closes the popover by calling `closeAllMenuPopovers`.
|
54
|
+
* It also checks if the click was on the backdrop element (if present) to close the popover.
|
55
|
+
* @param event - The mouse event that triggered the outside click.
|
56
|
+
*/
|
57
|
+
onOutsidePopoverClick: (event: MouseEvent) => void;
|
58
|
+
/**
|
59
|
+
* Checks if the menu popover has a submenu with the specified trigger ID.
|
60
|
+
* This method is used to determine if a menu item has a submenu associated with it,
|
61
|
+
* which is indicated by the presence of a `triggerid` attribute in the submenu popover.
|
62
|
+
* It queries the parent element for any popover with the specified trigger ID.
|
63
|
+
* @param id - The ID of the menu item to check for a submenu.
|
64
|
+
* @returns - A boolean indicating whether a submenu with the specified trigger ID exists.
|
65
|
+
*/
|
66
|
+
private hasSubmenuWithTriggerId;
|
67
|
+
/**
|
68
|
+
* Handles mouse click events on the menu items.
|
69
|
+
* This method checks if the clicked element is a valid menu item and not a submenu trigger.
|
70
|
+
* If it is, it closes all other menu popovers to ensure only one menu is open at a time.
|
71
|
+
* @param event - The mouse event that triggered the click.
|
72
|
+
*/
|
73
|
+
private handleMouseClick;
|
74
|
+
/**
|
75
|
+
* Resolves the key pressed by the user based on the direction of the layout.
|
76
|
+
* This method is used to handle keyboard navigation in a right-to-left (RTL) layout.
|
77
|
+
* It checks if the layout is RTL and adjusts the arrow keys accordingly.
|
78
|
+
* For example, in RTL, the left arrow key behaves like the right arrow key and vice versa.
|
79
|
+
* @param key - The key pressed by the user.
|
80
|
+
* @param isRtl - A boolean indicating if the layout is right-to-left (RTL).
|
81
|
+
* @returns - The resolved key based on the direction.
|
82
|
+
*/
|
83
|
+
private resolveDirectionKey;
|
84
|
+
/**
|
85
|
+
* Handles keydown events for keyboard navigation within the menu popover.
|
86
|
+
* This method allows users to navigate through the menu items using the keyboard.
|
87
|
+
* It supports Home, End, Arrow Up, Arrow Down, Arrow Left, Arrow Right, and Escape keys.
|
88
|
+
* @param event - The keyboard event that triggered the keydown action.
|
89
|
+
* @returns - This method does not return anything.
|
90
|
+
*/
|
91
|
+
private handleKeyDown;
|
28
92
|
static styles: Array<CSSResult>;
|
29
93
|
}
|
30
94
|
export default MenuPopover;
|
@@ -1,10 +1,13 @@
|
|
1
|
-
import {
|
1
|
+
import { KEYS } from '../../utils/keys';
|
2
2
|
import { ROLE } from '../../utils/roles';
|
3
|
+
import { TAG_NAME as MENUSECTION_TAGNAME } from '../menusection/menusection.constants';
|
3
4
|
import { ORIENTATION } from '../menubar/menubar.constants';
|
4
5
|
import Popover from '../popover/popover.component';
|
5
|
-
import {
|
6
|
+
import { COLOR } from '../popover/popover.constants';
|
6
7
|
import { TAG_NAME as MENU_POPOVER } from './menupopover.constants';
|
7
8
|
import styles from './menupopover.styles';
|
9
|
+
import { isActiveMenuItem, isValidMenuItem, isValidPopover } from './menupopover.utils';
|
10
|
+
import { popoverStack } from '../popover/popover.stack';
|
8
11
|
/**
|
9
12
|
* A popover menu component that displays a list of menu items in a floating container.
|
10
13
|
* It's designed to work in conjunction with `mdc-menubar` and `mdc-menuitem` to create
|
@@ -25,32 +28,221 @@ import styles from './menupopover.styles';
|
|
25
28
|
* @tagname mdc-menupopover
|
26
29
|
* @slot default - Contains the menu items to be displayed in the popover
|
27
30
|
*/
|
28
|
-
class MenuPopover extends
|
31
|
+
class MenuPopover extends Popover {
|
29
32
|
constructor() {
|
30
33
|
super();
|
34
|
+
this.hidePopover = () => {
|
35
|
+
setTimeout(() => {
|
36
|
+
this.visible = false;
|
37
|
+
this.isTriggerClicked = false;
|
38
|
+
}, this.closeDelay);
|
39
|
+
};
|
40
|
+
/**
|
41
|
+
* Handles outside click events to close the popover.
|
42
|
+
* This method checks if the click occurred outside the popover and its trigger element.
|
43
|
+
* If so, it closes the popover by calling `closeAllMenuPopovers`.
|
44
|
+
* It also checks if the click was on the backdrop element (if present) to close the popover.
|
45
|
+
* @param event - The mouse event that triggered the outside click.
|
46
|
+
*/
|
47
|
+
this.onOutsidePopoverClick = (event) => {
|
48
|
+
if (popoverStack.peek() !== this)
|
49
|
+
return;
|
50
|
+
let insidePopoverClick = false;
|
51
|
+
const path = event.composedPath();
|
52
|
+
insidePopoverClick = this.contains(event.target) || path.includes(this.triggerElement);
|
53
|
+
const clickedOnBackdrop = this.backdropElement ? path.includes(this.backdropElement) : false;
|
54
|
+
if (!insidePopoverClick || clickedOnBackdrop) {
|
55
|
+
this.closeAllMenuPopovers();
|
56
|
+
}
|
57
|
+
};
|
31
58
|
this.addEventListener('keydown', this.handleKeyDown);
|
32
59
|
this.addEventListener('click', this.handleMouseClick);
|
33
60
|
}
|
61
|
+
/** @internal */
|
62
|
+
get menuItems() {
|
63
|
+
var _a;
|
64
|
+
const slot = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot');
|
65
|
+
const allAssignedElements = ((slot === null || slot === void 0 ? void 0 : slot.assignedElements({ flatten: true })) || []);
|
66
|
+
return allAssignedElements.map((node) => {
|
67
|
+
if (node.tagName.toLowerCase() === MENUSECTION_TAGNAME) {
|
68
|
+
return Array.from(node.children)
|
69
|
+
.filter((child) => isValidMenuItem(child));
|
70
|
+
}
|
71
|
+
return isValidMenuItem(node) ? node : [];
|
72
|
+
})
|
73
|
+
.flat()
|
74
|
+
.filter((node) => !!node && !node.hasAttribute('disabled'));
|
75
|
+
}
|
34
76
|
connectedCallback() {
|
35
77
|
super.connectedCallback();
|
36
78
|
this.role = ROLE.MENU;
|
79
|
+
this.ariaOrientation = ORIENTATION.VERTICAL;
|
80
|
+
this.backdrop = false;
|
81
|
+
this.color = COLOR.TONAL;
|
82
|
+
this.disableAriaExpanded = false;
|
37
83
|
this.focusBackToTrigger = true;
|
38
84
|
this.focusTrap = true;
|
39
|
-
this.focusBackToTrigger = true;
|
40
85
|
this.hideOnEscape = true;
|
41
86
|
this.hideOnOutsideClick = true;
|
42
87
|
this.interactive = true;
|
43
|
-
this.placement = POPOVER_PLACEMENT.BOTTOM_START;
|
44
88
|
this.showArrow = false;
|
45
|
-
this.
|
89
|
+
this.closeButton = false;
|
90
|
+
this.closeButtonAriaLabel = null;
|
46
91
|
}
|
47
92
|
async firstUpdated(changedProperties) {
|
48
|
-
var _a
|
93
|
+
var _a;
|
49
94
|
await super.firstUpdated(changedProperties);
|
95
|
+
// Reset all tabindex to -1 and set the tabindex of the first menu item to 0
|
96
|
+
if (this.menuItems.length > 0) {
|
97
|
+
this.menuItems.forEach((menuitem) => menuitem.setAttribute('tabindex', '-1'));
|
98
|
+
this.menuItems[0].setAttribute('tabindex', '0');
|
99
|
+
}
|
50
100
|
(_a = this.triggerElement) === null || _a === void 0 ? void 0 : _a.setAttribute('aria-haspopup', ROLE.MENU);
|
51
|
-
|
52
|
-
|
53
|
-
|
101
|
+
}
|
102
|
+
/**
|
103
|
+
* Retrieves the current index of the menu item that triggered the event.
|
104
|
+
* @param target - The target element that triggered the event.
|
105
|
+
* @returns - The index of the current menu item in the `menuItems` array.
|
106
|
+
*/
|
107
|
+
getCurrentIndex(target) {
|
108
|
+
return this.menuItems.findIndex((node) => node === target);
|
109
|
+
}
|
110
|
+
/**
|
111
|
+
* Resets the tabindex of the currently focused menu item and sets focus to a new menu item.
|
112
|
+
* @param newIndex - The index of the new menu item to focus.
|
113
|
+
* @param oldIndex - The index of the currently focused menu item.
|
114
|
+
* @returns - This method does not return anything.
|
115
|
+
*/
|
116
|
+
resetTabIndexAndSetFocus(newIndex, oldIndex) {
|
117
|
+
if (newIndex === oldIndex)
|
118
|
+
return;
|
119
|
+
this.menuItems[oldIndex].setAttribute('tabindex', '-1');
|
120
|
+
this.menuItems[newIndex].setAttribute('tabindex', '0');
|
121
|
+
this.menuItems[newIndex].focus();
|
122
|
+
}
|
123
|
+
/**
|
124
|
+
* Closes all menu popovers in the stack.
|
125
|
+
* This method is used to ensure that when a menu item is clicked,
|
126
|
+
* all other open popovers are closed, maintaining a clean user interface.
|
127
|
+
* It iterates through the `popoverStack` and hides each popover until the stack is empty.
|
128
|
+
*/
|
129
|
+
closeAllMenuPopovers() {
|
130
|
+
while (popoverStack.peek()) {
|
131
|
+
const popover = popoverStack.pop();
|
132
|
+
if (popover) {
|
133
|
+
popover.hidePopover();
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
/**
|
138
|
+
* Checks if the menu popover has a submenu with the specified trigger ID.
|
139
|
+
* This method is used to determine if a menu item has a submenu associated with it,
|
140
|
+
* which is indicated by the presence of a `triggerid` attribute in the submenu popover.
|
141
|
+
* It queries the parent element for any popover with the specified trigger ID.
|
142
|
+
* @param id - The ID of the menu item to check for a submenu.
|
143
|
+
* @returns - A boolean indicating whether a submenu with the specified trigger ID exists.
|
144
|
+
*/
|
145
|
+
hasSubmenuWithTriggerId(id) {
|
146
|
+
var _a;
|
147
|
+
return ((_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector(`${MENU_POPOVER}[triggerid="${id}"]`)) !== null;
|
148
|
+
}
|
149
|
+
/**
|
150
|
+
* Handles mouse click events on the menu items.
|
151
|
+
* This method checks if the clicked element is a valid menu item and not a submenu trigger.
|
152
|
+
* If it is, it closes all other menu popovers to ensure only one menu is open at a time.
|
153
|
+
* @param event - The mouse event that triggered the click.
|
154
|
+
*/
|
155
|
+
handleMouseClick(event) {
|
156
|
+
const target = event.target;
|
157
|
+
const triggerId = target.getAttribute('id');
|
158
|
+
if (isActiveMenuItem(target) // menuitemcheckbox and menuitemradio are not supposed to close the popover
|
159
|
+
&& !this.hasSubmenuWithTriggerId(triggerId)) {
|
160
|
+
this.closeAllMenuPopovers();
|
161
|
+
}
|
162
|
+
}
|
163
|
+
/**
|
164
|
+
* Resolves the key pressed by the user based on the direction of the layout.
|
165
|
+
* This method is used to handle keyboard navigation in a right-to-left (RTL) layout.
|
166
|
+
* It checks if the layout is RTL and adjusts the arrow keys accordingly.
|
167
|
+
* For example, in RTL, the left arrow key behaves like the right arrow key and vice versa.
|
168
|
+
* @param key - The key pressed by the user.
|
169
|
+
* @param isRtl - A boolean indicating if the layout is right-to-left (RTL).
|
170
|
+
* @returns - The resolved key based on the direction.
|
171
|
+
*/
|
172
|
+
resolveDirectionKey(key, isRtl) {
|
173
|
+
if (!isRtl)
|
174
|
+
return key;
|
175
|
+
switch (key) {
|
176
|
+
case KEYS.ARROW_LEFT:
|
177
|
+
return KEYS.ARROW_RIGHT;
|
178
|
+
case KEYS.ARROW_RIGHT:
|
179
|
+
return KEYS.ARROW_LEFT;
|
180
|
+
default:
|
181
|
+
return key;
|
182
|
+
}
|
183
|
+
}
|
184
|
+
/**
|
185
|
+
* Handles keydown events for keyboard navigation within the menu popover.
|
186
|
+
* This method allows users to navigate through the menu items using the keyboard.
|
187
|
+
* It supports Home, End, Arrow Up, Arrow Down, Arrow Left, Arrow Right, and Escape keys.
|
188
|
+
* @param event - The keyboard event that triggered the keydown action.
|
189
|
+
* @returns - This method does not return anything.
|
190
|
+
*/
|
191
|
+
handleKeyDown(event) {
|
192
|
+
var _a, _b;
|
193
|
+
const currentIndex = this.getCurrentIndex(event.target);
|
194
|
+
if (currentIndex === -1)
|
195
|
+
return;
|
196
|
+
const isRtl = window.getComputedStyle(this).direction === 'rtl';
|
197
|
+
const targetKey = this.resolveDirectionKey(event.key, isRtl);
|
198
|
+
switch (targetKey) {
|
199
|
+
case KEYS.HOME: {
|
200
|
+
// Move focus to the first menu item
|
201
|
+
this.resetTabIndexAndSetFocus(0, currentIndex);
|
202
|
+
break;
|
203
|
+
}
|
204
|
+
case KEYS.END: {
|
205
|
+
// Move focus to the last menu item
|
206
|
+
this.resetTabIndexAndSetFocus(this.menuItems.length - 1, currentIndex);
|
207
|
+
break;
|
208
|
+
}
|
209
|
+
case KEYS.ARROW_DOWN: {
|
210
|
+
// Move focus to the next menu item
|
211
|
+
const newIndex = (currentIndex + 1) === this.menuItems.length ? 0 : (currentIndex + 1);
|
212
|
+
this.resetTabIndexAndSetFocus(newIndex, currentIndex);
|
213
|
+
break;
|
214
|
+
}
|
215
|
+
case KEYS.ARROW_UP: {
|
216
|
+
// Move focus to the prev menu item
|
217
|
+
const newIndex = (currentIndex - 1) === -1 ? (this.menuItems.length - 1) : (currentIndex - 1);
|
218
|
+
this.resetTabIndexAndSetFocus(newIndex, currentIndex);
|
219
|
+
break;
|
220
|
+
}
|
221
|
+
case KEYS.ARROW_RIGHT: {
|
222
|
+
// If there is a submenu, open it.
|
223
|
+
const triggerId = (_a = this.menuItems[currentIndex]) === null || _a === void 0 ? void 0 : _a.getAttribute('id');
|
224
|
+
if (this.hasSubmenuWithTriggerId(triggerId)) {
|
225
|
+
const submenu = (_b = this.parentElement) === null || _b === void 0 ? void 0 : _b.querySelector(`${MENU_POPOVER}[triggerid="${triggerId}"]`);
|
226
|
+
if (submenu) {
|
227
|
+
submenu.showPopover();
|
228
|
+
}
|
229
|
+
}
|
230
|
+
break;
|
231
|
+
}
|
232
|
+
case KEYS.ARROW_LEFT: {
|
233
|
+
// If the current popover is a submenu then close this popover.
|
234
|
+
if (isValidPopover(this.parentElement)) {
|
235
|
+
this.hidePopover();
|
236
|
+
this.resetTabIndexAndSetFocus(0, currentIndex);
|
237
|
+
}
|
238
|
+
break;
|
239
|
+
}
|
240
|
+
case KEYS.ESCAPE: {
|
241
|
+
this.resetTabIndexAndSetFocus(0, currentIndex);
|
242
|
+
break;
|
243
|
+
}
|
244
|
+
default:
|
245
|
+
break;
|
54
246
|
}
|
55
247
|
}
|
56
248
|
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
/**
|
2
|
+
* Checks if the given menu item is a valid menu item.
|
3
|
+
* @param menuItem - The menu item to check.
|
4
|
+
* @returns True if the menu item is a valid menu item, false otherwise.
|
5
|
+
*/
|
6
|
+
declare const isValidMenuItem: (menuItem: Element | null) => boolean;
|
7
|
+
declare const isValidPopover: (menuItem: Element | null) => boolean;
|
8
|
+
declare const isActiveMenuItem: (menuItem: Element | null) => boolean;
|
9
|
+
export { isValidMenuItem, isValidPopover, isActiveMenuItem, };
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { TAG_NAME as MENUITEM_TAGNAME } from '../menuitem/menuitem.constants';
|
2
|
+
import { TAG_NAME as MENUITEMCHECKBOX_TAGNAME } from '../menuitemcheckbox/menuitemcheckbox.constants';
|
3
|
+
import { TAG_NAME as MENUITEMRADIO_TAGNAME } from '../menuitemradio/menuitemradio.constants';
|
4
|
+
import { TAG_NAME as MENUPOPOVER_TAGNAME } from './menupopover.constants';
|
5
|
+
/**
|
6
|
+
* Checks if the given menu item is a valid menu item.
|
7
|
+
* @param menuItem - The menu item to check.
|
8
|
+
* @returns True if the menu item is a valid menu item, false otherwise.
|
9
|
+
*/
|
10
|
+
const isValidMenuItem = (menuItem) => {
|
11
|
+
var _a, _b;
|
12
|
+
return [MENUITEM_TAGNAME, MENUITEMCHECKBOX_TAGNAME, MENUITEMRADIO_TAGNAME]
|
13
|
+
.includes((_b = (_a = menuItem === null || menuItem === void 0 ? void 0 : menuItem.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase) === null || _b === void 0 ? void 0 : _b.call(_a));
|
14
|
+
};
|
15
|
+
const isValidPopover = (menuItem) => { var _a; return ((_a = menuItem === null || menuItem === void 0 ? void 0 : menuItem.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === MENUPOPOVER_TAGNAME; };
|
16
|
+
const isActiveMenuItem = (menuItem) => {
|
17
|
+
var _a, _b;
|
18
|
+
return ((_b = (_a = menuItem === null || menuItem === void 0 ? void 0 : menuItem.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase) === null || _b === void 0 ? void 0 : _b.call(_a)) === MENUITEM_TAGNAME
|
19
|
+
&& !menuItem.hasAttribute('disabled');
|
20
|
+
};
|
21
|
+
export { isValidMenuItem, isValidPopover, isActiveMenuItem, };
|