Skip to content

Commit

Permalink
feat(menu): submenu navigation keydown
Browse files Browse the repository at this point in the history
  • Loading branch information
sangeethababu9223 committed Jun 3, 2024
1 parent 0412698 commit 4762618
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import { property } from 'lit/decorators.js';
import { prefix } from '../../globals/settings';
import styles from './menu-item.scss?lit';
import { carbonElement as customElement } from '../../globals/decorators/carbon-element';
import { classMap } from 'lit/directives/class-map.js';

/**
* Menu Item.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import { property } from 'lit/decorators.js';
import { prefix } from '../../globals/settings';
import styles from './menu-item.scss?lit';
import { carbonElement as customElement } from '../../globals/decorators/carbon-element';
import { classMap } from 'lit/directives/class-map.js';

/**
* Menu Item.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { property } from 'lit/decorators.js';
import { prefix } from '../../globals/settings';
import styles from './menu-item.scss?lit';
import { carbonElement as customElement } from '../../globals/decorators/carbon-element';
import { classMap } from 'lit/directives/class-map.js';
import { EventHandler } from 'react';
import Checkmark16 from '@carbon/icons/lib/checkmark/16';
import { ChangeEventHandler, EventHandler } from 'react';

/**
* Menu Item.
Expand Down Expand Up @@ -42,11 +42,13 @@ class CDSmenuItemRadioGroup extends LitElement {
/**
* List of items in the radio group.
*/
@property()
itemToString?: (item: Array<String | number>) => String;
/**
* Provide an optional function to be called when the selection state changes.
*/
onChange?;
@property()
onChange?: ChangeEventHandler;

_handleClick = (item, e) => {
this.selectedItem = item;
Expand All @@ -65,18 +67,16 @@ class CDSmenuItemRadioGroup extends LitElement {
} = this;
return html`
<li class="${prefix}--menu-item-radio-group" role="none">
<ul role="group" label="${label}">
<ul role="group" aria-label="${label}">
${items.map(
(item) =>
html`
<cds-menu-item
label="${itemToString(item)}"
role="menuitemradio"
aria-checked="${item === selectedItem}"
renderIcon="${item === selectedItem
? 'checkMark'
: undefined}"
@click="${(e) => {
.renderIcon="${item === selectedItem ? Checkmark16 : ''}"
.onClick="${(e) => {
handleClick(item, e);
}}"></cds-menu-item>
`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { property } from 'lit/decorators.js';
import { prefix } from '../../globals/settings';
import styles from './menu-item.scss?lit';
import { carbonElement as customElement } from '../../globals/decorators/carbon-element';
import Checkmark16 from '@carbon/icons/lib/checkmark/16';
import { classMap } from 'lit/directives/class-map.js';
/**
* Menu Item.
Expand Down Expand Up @@ -48,9 +49,11 @@ class CDSmenuItemSelectable extends LitElement {
return html`
<cds-menu-item
label="${label}"
class="${prefix}--menu-item-selectable--selected"
role="menuitemcheckbox"
aria-checked="${selected}"
renderIcon="${selected ? 'checkMark' : undefined}"
@click="${handleClick}"></cds-menu-item>
.renderIcon="${selected ? Checkmark16 : undefined}"
.onClick="${handleClick}"></cds-menu-item>
`;
}
static styles = styles; // `styles` here is a `CSSResult` generated by custom Vite loader
Expand Down
92 changes: 62 additions & 30 deletions packages/carbon-web-components/src/components/menu/menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ class CDSmenuItem extends LitElement {
/**
* ICON type to be rendered for the menu item.
*/
@property({ type: String })
renderIcon;
@property()
renderIcon?: () => void;
/**
* Disabled property for the menu item.
*/
Expand All @@ -79,7 +79,12 @@ class CDSmenuItem extends LitElement {
* Whether the menu submen for an item is open or not.
*/
@property({ type: Array })
submenuList: HTMLLIElement[] = [];
submenuList;
/**
* Entrypoint.
*/
@property()
submenuEntry;
/**
* Document direction.
*/
Expand Down Expand Up @@ -114,6 +119,18 @@ class CDSmenuItem extends LitElement {
x: number | [number, number];
y: number | [number, number];
} = { x: -1, y: -1 };
/**
* Checks if document direction is rtl.
*/
@property()
onKeyDown?: (e: KeyboardEvent) => void;

/**
* Provide an optional function to be called when the MenuItem is clicked.
*/
@property()
onClick?: (event: KeyboardEvent | MouseEvent) => void;

firstUpdated() {
this.hasChildren = this.childNodes.length > 0;
this.isDisabled = this.disabled && !this.hasChildren;
Expand All @@ -130,7 +147,6 @@ class CDSmenuItem extends LitElement {
label,
shortcut,
renderIcon,
_iconRender: iconRender,
isDisabled,
hasChildren,
submenuOpen,
Expand All @@ -142,6 +158,7 @@ class CDSmenuItem extends LitElement {
isDanger,
boundaries,
childElements,
isRtl,
} = this;
const menuItemClasses = classMap({
[`${prefix}--menu-item`]: true,
Expand All @@ -161,17 +178,17 @@ class CDSmenuItem extends LitElement {
@mouseleave="${hasChildren ? handleMouseLeave : undefined}"
@keydown="${handleKeyDown}">
<div class="${prefix}--menu-item__icon">
${renderIcon ? iconRender() : undefined}
${renderIcon ? renderIcon() : html``}
</div>
<div class="${prefix}--menu-item__label">${label}</div>
${shortcut && !hasChildren && html `
<div class="${prefix}--menu-item__shortcut">${shortcut}</div>
`}
${shortcut &&
!hasChildren &&
html` <div class="${prefix}--menu-item__shortcut">${shortcut}</div> `}
${hasChildren
? html`
<div class="{${prefix}--menu-item__shortcut">
${this.isRtl ? CaretLeft16() : CaretRight16()}
</div>
<div class="{${prefix}--menu-item__shortcut">
${isRtl ? CaretLeft16() : CaretRight16()}
</div>
<cds-menu
.isChild="${hasChildren}"
label="${label}"
Expand All @@ -187,24 +204,13 @@ class CDSmenuItem extends LitElement {
`;
}

_iconRender = () => {
if (this.renderIcon) {
let iconRendered;
switch (this.renderIcon) {
case 'checkMark':
iconRendered = Checkmark16();
break;
}
return iconRendered;
}
};
_handleClick = (e: MouseEvent | KeyboardEvent): void => {
if (!this.isDisabled) {
if (this.hasChildren) {
this._openSubmenu();
} else {
if (this.onclick) {
this.onclick(e);
if (this.onClick) {
this.onClick(e);
}
}
}
Expand Down Expand Up @@ -235,14 +241,40 @@ class CDSmenuItem extends LitElement {
};
}
this.submenuOpen = true;
setTimeout(() => {
this.submenuEntry.focus();
});
};
_registerSubMenuItems = () => {
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
this.submenuList = this.childElements?.map((item) => {
return item.shadowRoot?.querySelector('li');
});
const item = this.childElements?.length && this.childElements[0];
if (item) {
switch (item.tagName) {
case 'CDS-MENU-ITEM-RADIO-GROUP':
this.submenuEntry = item.shadowRoot
?.querySelector('cds-menu-item')
?.shadowRoot?.querySelector('.cds--menu-item');
break;
case 'CDS-MENU-ITEM-GROUP': {
const slotElements = item.shadowRoot
?.querySelector('slot')
?.assignedElements();
const firstElement =
slotElements?.length &&
slotElements[0].shadowRoot?.querySelector('cds-menu-item');
this.submenuEntry =
firstElement &&
firstElement.shadowRoot?.querySelector('.cds--menu-item');
break;
}
case 'CDS-MENU-ITEM':
this.submenuEntry =
item.shadowRoot?.querySelector('.cds--menu-item');
break;
}
}
}
}
});
Expand All @@ -264,14 +296,14 @@ class CDSmenuItem extends LitElement {
_handleKeyDown = (e: KeyboardEvent) => {
if (this.hasChildren && e.key === 'ArrowRight') {
this._openSubmenu();
setTimeout(() => {
this.submenuList[0].focus();
});
e.stopPropagation();
}
if (e.key === 'Enter' || e.key === 'Space') {
this._handleClick(e);
}
if (this.onKeyDown) {
this.onKeyDown(e);
}
};

static styles = styles; // `styles` here is a `CSSResult` generated by custom Vite loader
Expand Down
18 changes: 12 additions & 6 deletions packages/carbon-web-components/src/components/menu/menu.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@ import './index';
export const Default = {
render: () => {
const itemlist = ['None', 'Overline', 'Line-through', 'Underline'];
const subitemlist = ['None', 'Product team', 'Organization', 'Company'];
return html`
<cds-menu>
<cds-menu-item label="Share with">
<cds-menu-item label="None"></cds-menu-item>
<cds-menu-item label="Product team"></cds-menu-item>
<cds-menu-item label="Organization"></cds-menu-item>
<cds-menu-item label="Company"></cds-menu-item>
<cds-menu-item-radio-group
label="Share with list"
.items="${subitemlist}"
selectedItem="None"></cds-menu-item-radio-group>
</cds-menu-item>
<cds-menu-item-divider></cds-menu-item-divider>
<cds-menu-item label="Cut" shortcut="⌘X"></cds-menu-item>
<cds-menu-item label="Copy" shortcut="⌘C"></cds-menu-item>
<cds-menu-item label="Paste" shortcut="⌘V" disabled></cds-menu-item>
<cds-menu-item-divider></cds-menu-item-divider>
<cds-menu-item-group>
<cds-menu-item-selectable label="Bold" selected></cds-menu-item-selectable>
<cds-menu-item-selectable
label="Bold"
selected></cds-menu-item-selectable>
<cds-menu-item-selectable label="Italic"></cds-menu-item-selectable>
</cds-menu-item-group>
<cds-menu-item-divider></cds-menu-item-divider>
Expand All @@ -36,7 +39,10 @@ export const Default = {
.items="${itemlist}"
selectedItem="None"></cds-menu-item-radio-group>
<cds-menu-item-divider></cds-menu-item-divider>
<cds-menu-item label="Delete" shortcut="" kind="danger"></cds-menu-item>
<cds-menu-item
label="Delete"
shortcut=""
kind="danger"></cds-menu-item>
</cds-menu>
`;
},
Expand Down
11 changes: 10 additions & 1 deletion packages/carbon-web-components/src/components/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,16 @@ class CDSMenu extends HostListenerMixin(LitElement) {
activeItem.item.shadowRoot?.querySelector('.cds--menu-item')
);
} else {
return activeItem.parent.contains(document.activeElement);
let shadowRootActiveEl =
this._findActiveElementInShadowRoot(document);
if (activeItem.parent.tagName === 'CDS-MENU-ITEM-SELECTABLE') {
return (
shadowRootActiveEl ===
activeItem.item.shadowRoot?.querySelector('.cds--menu-item')
);
} else {
return activeItem.parent.contains(document.activeElement);
}
}
});
} else {
Expand Down

0 comments on commit 4762618

Please sign in to comment.