Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
feat(menu): add setSelectedIndex to set selected item in menu selecti…
Browse files Browse the repository at this point in the history
…on group (#4620)

BREAKING CHANGE: Replaced adapter methods getParentElement, getSelectedElementIndex with getSelectedSiblingOfItemAtIndex, isSelectableItemAtIndex.
  • Loading branch information
Matt Goo authored and abhiomkar committed Jun 11, 2019
1 parent 1150926 commit b1e0888
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 171 deletions.
6 changes: 4 additions & 2 deletions packages/mdc-menu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ Method Signature | Description
`setAnchorMargin(Partial<MDCMenuDistance>) => void` | Proxies to the menu surface's `setAnchorMargin(Partial<MDCMenuDistance>)` method.
`setAbsolutePosition(x: number, y: number) => void` | Proxies to the menu surface's `setAbsolutePosition(x: number, y: number)` method.
`setFixedPosition(isFixed: boolean) => void` | Proxies to the menu surface's `setFixedPosition(isFixed: boolean)` method.
`setSelectedIndex(index: number) => void | Sets the list item to the selected state at the specified index.
`hoistMenuToBody() => void` | Proxies to the menu surface's `hoistMenuToBody()` method.
`setIsHoisted(isHoisted: boolean) => void` | Proxies to the menu surface's `setIsHoisted(isHoisted: boolean)` method.
`setAnchorElement(element: Element) => void` | Proxies to the menu surface's `setAnchorElement(element)` method.
Expand All @@ -250,12 +251,12 @@ Method Signature | Description
`elementContainsClass(element: Element, className: string) => boolean` | Returns true if the `element` contains the `className` class.
`closeSurface() => void` | Closes the menu surface.
`getElementIndex(element: Element) => number` | Returns the `index` value of the `element`.
`getParentElement(element: Element) => Element \| null` | Returns the `.parentElement` element of the `element` provided.
`getSelectedElementIndex(element: Element) => number` | Returns the `index` value of the element within the selection group provided, `element` that contains the `mdc-menu-item--selected` class.
`notifySelected(index: number) => void` | Emits a `MDCMenu:selected` event for the element at the `index` specified.
`getMenuItemCount() => number` | Returns the menu item count.
`focusItemAtIndex(index: number)` | Focuses the menu item at given index.
`focusListRoot() => void` | Focuses the list root element.
`getSelectedSiblingOfItemAtIndex(index: number) => number` | Returns selected list item index within the same selection group which is a sibling of item at given `index`.
`isSelectableItemAtIndex(index: number) => boolean` | Returns true if menu item at specified index is contained within an `.mdc-menu__selection-group` element.

### `MDCMenuFoundation`

Expand All @@ -265,6 +266,7 @@ Method Signature | Description
`handleItemAction(listItem: Element) => void` | Event handler for list's action event.
`handleMenuSurfaceOpened() => void` | Event handler for menu surface's opened event.
`setDefaultFocusState(focusState: DefaultFocusState) => void` | Sets default focus state where the menu should focus every time when menu is opened. Focuses the list root (`DefaultFocusState.LIST_ROOT`) element by default.
`setSelectedIndex(index: number) => void` | Selects the list item at given `index`.

### Events

Expand Down
23 changes: 13 additions & 10 deletions packages/mdc-menu/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,6 @@ export interface MDCMenuAdapter {
*/
getElementIndex(element: Element): number;

/**
* @return The parentElement of the provided element.
*/
getParentElement(element: Element): Element | null;

/**
* @return The element within the selectionGroup containing the selected element class.
*/
getSelectedElementIndex(selectionGroup: Element): number;

/**
* Emit an event when a menu item is selected.
*/
Expand All @@ -91,4 +81,17 @@ export interface MDCMenuAdapter {

/** Focuses the list root element. */
focusListRoot(): void;

/**
* @return Returns selected list item index within the same selection group which is
* a sibling of item at given `index`.
* @param index Index of the menu item with possible selected sibling.
*/
getSelectedSiblingOfItemAtIndex(index: number): number;

/**
* @return Returns true if item at specified index is contained within an `.mdc-menu__selection-group` element.
* @param index Index of the selectable menu item.
*/
isSelectableItemAtIndex(index: number): boolean;
}
20 changes: 15 additions & 5 deletions packages/mdc-menu/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import {MDCComponent} from '@material/base/component';
import {CustomEventListener, SpecificEventListener} from '@material/base/types';
import {closest} from '@material/dom/ponyfill';
import {MDCList, MDCListFactory} from '@material/list/component';
import {MDCListFoundation} from '@material/list/foundation';
import {MDCListActionEvent} from '@material/list/types';
Expand Down Expand Up @@ -143,6 +144,14 @@ export class MDCMenu extends MDCComponent<MDCMenuFoundation> {
this.menuSurface_.setAnchorMargin(margin);
}

/**
* Sets the list item as the selected row at the specified index.
* @param index Index of list item within menu.
*/
setSelectedIndex(index: number) {
this.foundation_.setSelectedIndex(index);
}

/**
* @return The item within the menu at the index specified.
*/
Expand Down Expand Up @@ -203,18 +212,19 @@ export class MDCMenu extends MDCComponent<MDCMenuFoundation> {
elementContainsClass: (element, className) => element.classList.contains(className),
closeSurface: () => this.open = false,
getElementIndex: (element) => this.items.indexOf(element),
getParentElement: (element) => element.parentElement,
getSelectedElementIndex: (selectionGroup) => {
const selectedListItem = selectionGroup.querySelector(`.${cssClasses.MENU_SELECTED_LIST_ITEM}`);
return selectedListItem ? this.items.indexOf(selectedListItem) : -1;
},
notifySelected: (evtData) => this.emit<MDCMenuItemComponentEventDetail>(strings.SELECTED_EVENT, {
index: evtData.index,
item: this.items[evtData.index],
}),
getMenuItemCount: () => this.items.length,
focusItemAtIndex: (index) => (this.items[index] as HTMLElement).focus(),
focusListRoot: () => (this.root_.querySelector(strings.LIST_SELECTOR) as HTMLElement).focus(),
isSelectableItemAtIndex: (index) => !!closest(this.items[index], `.${cssClasses.MENU_SELECTION_GROUP}`),
getSelectedSiblingOfItemAtIndex: (index) => {
const selectionGroupEl = closest(this.items[index], `.${cssClasses.MENU_SELECTION_GROUP}`) as HTMLElement;
const selectedItemEl = selectionGroupEl.querySelector(`.${cssClasses.MENU_SELECTED_LIST_ITEM}`);
return selectedItemEl ? this.items.indexOf(selectedItemEl) : -1;
},
};
// tslint:enable:object-literal-sort-keys
return new MDCMenuFoundation(adapter);
Expand Down
57 changes: 23 additions & 34 deletions packages/mdc-menu/foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
*/

import {MDCFoundation} from '@material/base/foundation';
import {MDCListFoundation} from '@material/list/foundation';
import {MDCMenuSurfaceFoundation} from '@material/menu-surface/foundation';
import {MDCMenuAdapter} from './adapter';
import {cssClasses, DefaultFocusState, numbers, strings} from './constants';
Expand Down Expand Up @@ -56,12 +55,12 @@ export class MDCMenuFoundation extends MDCFoundation<MDCMenuAdapter> {
elementContainsClass: () => false,
closeSurface: () => undefined,
getElementIndex: () => -1,
getParentElement: () => null,
getSelectedElementIndex: () => -1,
notifySelected: () => undefined,
getMenuItemCount: () => 0,
focusItemAtIndex: () => undefined,
focusListRoot: () => undefined,
getSelectedSiblingOfItemAtIndex: () => -1,
isSelectableItemAtIndex: () => false,
};
// tslint:enable:object-literal-sort-keys
}
Expand Down Expand Up @@ -98,9 +97,8 @@ export class MDCMenuFoundation extends MDCFoundation<MDCMenuAdapter> {

// Wait for the menu to close before adding/removing classes that affect styles.
this.closeAnimationEndTimerId_ = setTimeout(() => {
const selectionGroup = this.getSelectionGroup_(listItem);
if (selectionGroup) {
this.handleSelectionGroup_(selectionGroup, index);
if (this.adapter_.isSelectableItemAtIndex(index)) {
this.setSelectedIndex(index);
}
}, MDCMenuSurfaceFoundation.numbers.TRANSITION_CLOSE_DURATION);
}
Expand Down Expand Up @@ -132,41 +130,32 @@ export class MDCMenuFoundation extends MDCFoundation<MDCMenuAdapter> {
}

/**
* Handles toggling the selected classes in a selection group when a selection is made.
* Selects the list item at `index` within the menu.
* @param index Index of list item within the menu.
*/
private handleSelectionGroup_(selectionGroup: Element, index: number) {
// De-select the previous selection in this group.
const selectedIndex = this.adapter_.getSelectedElementIndex(selectionGroup);
if (selectedIndex >= 0) {
this.adapter_.removeAttributeFromElementAtIndex(selectedIndex, strings.ARIA_SELECTED_ATTR);
this.adapter_.removeClassFromElementAtIndex(selectedIndex, cssClasses.MENU_SELECTED_LIST_ITEM);
setSelectedIndex(index: number) {
this.validatedIndex_(index);

if (!this.adapter_.isSelectableItemAtIndex(index)) {
throw new Error('MDCMenuFoundation: No selection group at specified index.');
}
// Select the new list item in this group.
this.adapter_.addClassToElementAtIndex(index, cssClasses.MENU_SELECTED_LIST_ITEM);
this.adapter_.addAttributeToElementAtIndex(index, strings.ARIA_SELECTED_ATTR, 'true');
}

/**
* Returns the parent selection group of an element if one exists.
*/
private getSelectionGroup_(listItem: Element): Element | null {
let parent = this.adapter_.getParentElement(listItem);
if (!parent) {
return null;
const prevSelectedIndex = this.adapter_.getSelectedSiblingOfItemAtIndex(index);
if (prevSelectedIndex >= 0) {
this.adapter_.removeAttributeFromElementAtIndex(prevSelectedIndex, strings.ARIA_SELECTED_ATTR);
this.adapter_.removeClassFromElementAtIndex(prevSelectedIndex, cssClasses.MENU_SELECTED_LIST_ITEM);
}

let isGroup = this.adapter_.elementContainsClass(parent, cssClasses.MENU_SELECTION_GROUP);
this.adapter_.addClassToElementAtIndex(index, cssClasses.MENU_SELECTED_LIST_ITEM);
this.adapter_.addAttributeToElementAtIndex(index, strings.ARIA_SELECTED_ATTR, 'true');
}

// Iterate through ancestors until we find the group or get to the list.
while (!isGroup && parent && !this.adapter_.elementContainsClass(parent, MDCListFoundation.cssClasses.ROOT)) {
parent = this.adapter_.getParentElement(parent);
isGroup = parent ? this.adapter_.elementContainsClass(parent, cssClasses.MENU_SELECTION_GROUP) : false;
}
private validatedIndex_(index: number): void {
const menuSize = this.adapter_.getMenuItemCount();
const isIndexInRange = index >= 0 && index < menuSize;

if (isGroup) {
return parent;
} else {
return null;
if (!isIndexInRange) {
throw new Error('MDCMenuFoundation: No list item at specified index.');
}
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-menu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"@material/base": "^1.0.0",
"@material/dom": "^1.1.0",
"@material/feature-targeting": "^0.44.1",
"@material/list": "^2.3.0",
"@material/menu-surface": "^2.3.0",
Expand Down
8 changes: 8 additions & 0 deletions test/screenshot/golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,14 @@
"desktop_windows_ie@11": "https://storage.googleapis.com/mdc-web-screenshot-tests/travis/2019/03/15/18_35_08_782/spec/mdc-menu/classes/menu-selection-group.html.windows_ie_11.png"
}
},
"spec/mdc-menu/classes/multiple-menu-selection-group.html": {
"public_url": "https://storage.googleapis.com/mdc-web-screenshot-tests/travis/2019/04/23/22_44_56_133/spec/mdc-menu/classes/multiple-menu-selection-group.html?utm_source=golden_json",
"screenshots": {
"desktop_windows_chrome@latest": "https://storage.googleapis.com/mdc-web-screenshot-tests/travis/2019/04/23/22_44_56_133/spec/mdc-menu/classes/multiple-menu-selection-group.html.windows_chrome_73.png",
"desktop_windows_firefox@latest": "https://storage.googleapis.com/mdc-web-screenshot-tests/travis/2019/04/23/22_44_56_133/spec/mdc-menu/classes/multiple-menu-selection-group.html.windows_firefox_65.png",
"desktop_windows_ie@11": "https://storage.googleapis.com/mdc-web-screenshot-tests/travis/2019/04/23/22_44_56_133/spec/mdc-menu/classes/multiple-menu-selection-group.html.windows_ie_11.png"
}
},
"spec/mdc-menu/issues/4025.html": {
"public_url": "https://storage.googleapis.com/mdc-web-screenshot-tests/travis/2019/05/10/15_16_09_688/spec/mdc-menu/issues/4025.html?utm_source=golden_json",
"screenshots": {
Expand Down
Loading

0 comments on commit b1e0888

Please sign in to comment.