Skip to content

Commit

Permalink
refactor(menu)!: events no longer subclass Event but rather use Custo…
Browse files Browse the repository at this point in the history
…mEvent

Fixes issues running in ES5

BREAKING CHANGE: subclassing events is not supported in ES5 so all menu-related events now use CustomEvent rather than subclassing Event

PiperOrigin-RevId: 553304128
  • Loading branch information
e111077 authored and Copybara-Service committed Aug 3, 2023
1 parent c148bf6 commit 043d548
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 81 deletions.
6 changes: 3 additions & 3 deletions menu/demo/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ function displayCloseEvent(outputRef: Ref<HTMLElement>) {
if (!outputRef.value) return;

outputRef.value.innerText = `Closed by item(s) with text: ${
JSON.stringify(event.itemPath.map(
item =>
item.headline))} For reason: ${JSON.stringify(event.reason)}`;
JSON.stringify(
event.detail.itemPath.map(item => item.headline))} For reason: ${
JSON.stringify(event.detail.reason)}`;
};
}

Expand Down
8 changes: 4 additions & 4 deletions menu/internal/menuitem/menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {property, state} from 'lit/decorators.js';

import type {MdFocusRing} from '../../../focus/md-focus-ring.js';
import {ListItemEl, ListItemRole} from '../../../list/internal/listitem/list-item.js';
import {CLOSE_REASON, DefaultCloseMenuEvent, isClosableKey, MenuItem} from '../shared.js';
import {CLOSE_REASON, createDefaultCloseMenuEvent, isClosableKey, MenuItem} from '../shared.js';

export {ListItemRole} from '../../../list/internal/listitem/list-item.js';

Expand Down Expand Up @@ -39,8 +39,8 @@ export class MenuItemEl extends ListItemEl implements MenuItem {
protected override onClick() {
if (this.keepOpen || this.keepOpenOnClick) return;

this.dispatchEvent(
new DefaultCloseMenuEvent(this, {kind: CLOSE_REASON.CLICK_SELECTION}));
this.dispatchEvent(createDefaultCloseMenuEvent(
this, {kind: CLOSE_REASON.CLICK_SELECTION}));
}

protected override getRenderClasses() {
Expand All @@ -61,7 +61,7 @@ export class MenuItemEl extends ListItemEl implements MenuItem {

if (!event.defaultPrevented && isClosableKey(keyCode)) {
event.preventDefault();
this.dispatchEvent(new DefaultCloseMenuEvent(
this.dispatchEvent(createDefaultCloseMenuEvent(
this, {kind: CLOSE_REASON.KEYDOWN, key: keyCode}));
}
}
Expand Down
8 changes: 4 additions & 4 deletions menu/internal/menuitemlink/menu-item-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import {property} from 'lit/decorators.js';

import {ListItemLink} from '../../../list/internal/listitemlink/list-item-link.js';
import {CLOSE_REASON, DefaultCloseMenuEvent, isClosableKey, MenuItem, SELECTION_KEY} from '../shared.js';
import {CLOSE_REASON, createDefaultCloseMenuEvent, isClosableKey, MenuItem, SELECTION_KEY} from '../shared.js';

/**
* @fires close-menu {CloseMenuEvent}
Expand All @@ -29,8 +29,8 @@ export class MenuItemLink extends ListItemLink implements MenuItem {
protected override onClick() {
if (this.keepOpen || this.keepOpenOnClick) return;

this.dispatchEvent(
new DefaultCloseMenuEvent(this, {kind: CLOSE_REASON.CLICK_SELECTION}));
this.dispatchEvent(createDefaultCloseMenuEvent(
this, {kind: CLOSE_REASON.CLICK_SELECTION}));
}

protected override onKeydown(event: KeyboardEvent) {
Expand All @@ -41,7 +41,7 @@ export class MenuItemLink extends ListItemLink implements MenuItem {
if (!event.defaultPrevented && isClosableKey(keyCode) &&
keyCode !== SELECTION_KEY.ENTER) {
event.preventDefault();
this.dispatchEvent(new DefaultCloseMenuEvent(
this.dispatchEvent(createDefaultCloseMenuEvent(
this, {kind: CLOSE_REASON.KEYDOWN, key: keyCode}));
}
}
Expand Down
99 changes: 60 additions & 39 deletions menu/internal/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,71 +71,92 @@ export interface KeydownReason extends Reason {
export type DefaultReasons = ClickReason|KeydownReason;

/**
* The event that closes any parent menus. It is recommended to subclass and
* dispatch this event rather than creating your own `close-menu` event.
*/
export class CloseMenuEvent<T extends Reason = DefaultReasons> extends Event {
readonly itemPath: MenuItem[];
constructor(public initiator: MenuItem, readonly reason: T) {
super('close-menu', {bubbles: true, composed: true});
this.itemPath = [initiator];
}
* Creates an event that closes any parent menus.
*/
export function createCloseMenuEvent<T extends Reason = DefaultReasons>(
initiator: MenuItem, reason: T) {
return new CustomEvent<
{initiator: MenuItem, itemPath: MenuItem[], reason: T}>('close-menu', {
bubbles: true,
composed: true,
detail: {initiator, reason, itemPath: [initiator]}
});
}

/**
* The event that signals to the menu that it should stay open on the focusout
* event.
* Creates an event that signals to the menu that it should stay open on the
* focusout event.
*/
export class StayOpenOnFocusoutEvent extends Event {
constructor() {
super('stay-open-on-focusout', {bubbles: true, composed: true});
}
export function createStayOpenOnFocusoutEvent() {
return new Event('stay-open-on-focusout', {bubbles: true, composed: true});
}

/**
* The event that signals to the menu that it should close open on the focusout
* event.
* Creates an event that signals to the menu that it should close open on the
* focusout event.
*/
export class CloseOnFocusoutEvent extends Event {
constructor() {
super('close-on-focusout', {bubbles: true, composed: true});
}
export function createCloseOnFocusoutEvent() {
return new Event('close-on-focusout', {bubbles: true, composed: true});
}

/**
* The default close menu event used by md-menu. To create your own `close-menu`
* event, you should subclass the `CloseMenuEvent` instead.
* Creates a default close menu event used by md-menu.
*/
export const createDefaultCloseMenuEvent = createCloseMenuEvent<DefaultReasons>;

/**
* The type of the default close menu event used by md-menu.
*/
// tslint:disable-next-line
export const DefaultCloseMenuEvent = CloseMenuEvent<DefaultReasons>;
export type CloseMenuEvent<T extends Reason = DefaultReasons> =
ReturnType<typeof createCloseMenuEvent<T>>;

/**
* The event that requests the parent md-menu to deactivate all other items.
* Creates an event that requests the parent md-menu to deactivate all other
* items.
*/
export class DeactivateItemsEvent extends Event {
constructor() {
super('deactivate-items', {bubbles: true, composed: true});
}
export function createDeactivateItemsEvent() {
return new Event('deactivate-items', {bubbles: true, composed: true});
}

/**
* Requests the typeahead functionality of containing menu be deactivated.
* The type of the event that requests the parent md-menu to deactivate all
* other items.
*/
export type DeactivateItemsEvent =
ReturnType<typeof createDeactivateItemsEvent>;


/**
* Creates an event that requests the typeahead functionality of containing menu
* be deactivated.
*/
export class DeactivateTypeaheadEvent extends Event {
constructor() {
super('deactivate-typeahead', {bubbles: true, composed: true});
}
export function createDeactivateTypeaheadEvent() {
return new Event('deactivate-typeahead', {bubbles: true, composed: true});
}

/**
* Requests the typeahead functionality of containing menu be activated.
* The type of the event that requests the typeahead functionality of containing
* menu be deactivated.
*/
export class ActivateTypeaheadEvent extends Event {
constructor() {
super('activate-typeahead', {bubbles: true, composed: true});
}
export type DeactivateTypeaheadEvent =
ReturnType<typeof createDeactivateTypeaheadEvent>;

/**
* Creates an event that requests the typeahead functionality of containing menu
* be activated.
*/
export function createActivateTypeaheadEvent() {
return new Event('activate-typeahead', {bubbles: true, composed: true});
}

/**
* The type of the event that requests the typeahead functionality of containing
* menu be activated.
*/
export type ActivateTypeaheadEvent =
ReturnType<typeof createActivateTypeaheadEvent>;

/**
* Keys that are used to navigate menus.
*/
Expand Down
46 changes: 23 additions & 23 deletions menu/internal/submenuitem/sub-menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,24 @@ import {property, queryAssignedElements, state} from 'lit/decorators.js';
import {List} from '../../../list/internal/list.js';
import {Corner, Menu} from '../menu.js';
import {MenuItemEl} from '../menuitem/menu-item.js';
import {ActivateTypeaheadEvent, CLOSE_REASON, CloseMenuEvent, CloseOnFocusoutEvent, DeactivateItemsEvent, DeactivateTypeaheadEvent, KEYDOWN_CLOSE_KEYS, NAVIGABLE_KEY, SELECTION_KEY, StayOpenOnFocusoutEvent} from '../shared.js';
import {CLOSE_REASON, CloseMenuEvent, createActivateTypeaheadEvent, createCloseOnFocusoutEvent, createDeactivateItemsEvent, createDeactivateTypeaheadEvent, createStayOpenOnFocusoutEvent, KEYDOWN_CLOSE_KEYS, NAVIGABLE_KEY, SELECTION_KEY} from '../shared.js';

function stopPropagation(event: Event) {
event.stopPropagation();
}

/**
* @fires deactivate-items {DeactivateItemsEvent} Requests the parent menu to
* deselect other items when a submenu opens
* @fires deactivate-typeahead {DeactivateItemsEvent} Requests the parent menu
* to deactivate the typeahead functionality when a submenu opens
* @fires activate-typeahead {DeactivateItemsEvent} Requests the parent menu to
* activate the typeahead functionality when a submenu closes
* @fires stay-open-on-focusout {StayOpenOnFocusoutEvent} Requests the parent
* menu to stay open when focusout event is fired or has a `null`
* `relatedTarget` when submenu is opened.
* @fires close-on-focusout {CloseOnFocusoutEvent} Requests the parent
* menu to close when focusout event is fired or has a `null`
* `relatedTarget` When submenu is closed.
* @fires deactivate-items Requests the parent menu to deselect other items when
* a submenu opens
* @fires deactivate-typeahead Requests the parent menu to deactivate the
* typeahead functionality when a submenu opens
* @fires activate-typeahead Requests the parent menu to activate the typeahead
* functionality when a submenu closes
* @fires stay-open-on-focusout Requests the parent menu to stay open when
* focusout event is fired or has a `null` `relatedTarget` when submenu is
* opened.
* @fires close-on-focusout Requests the parent menu to close when focusout
* event is fired or has a `null` `relatedTarget` When submenu is closed.
*/
export class SubMenuItem extends MenuItemEl {
/**
Expand Down Expand Up @@ -165,14 +164,15 @@ export class SubMenuItem extends MenuItemEl {
}

private onCloseSubmenu(event: CloseMenuEvent) {
event.itemPath.push(this);
const {itemPath, reason} = event.detail;
itemPath.push(this);
// Restore focusout behavior
this.dispatchEvent(new CloseOnFocusoutEvent());
this.dispatchEvent(new ActivateTypeaheadEvent());
this.dispatchEvent(createCloseOnFocusoutEvent());
this.dispatchEvent(createActivateTypeaheadEvent());
// Escape should only close one menu not all of the menus unlike space or
// click selection which should close all menus.
if (event.reason.kind === CLOSE_REASON.KEYDOWN &&
event.reason.key === KEYDOWN_CLOSE_KEYS.ESCAPE) {
if (reason.kind === CLOSE_REASON.KEYDOWN &&
reason.key === KEYDOWN_CLOSE_KEYS.ESCAPE) {
event.stopPropagation();
this.active = true;
this.selected = false;
Expand Down Expand Up @@ -231,13 +231,13 @@ export class SubMenuItem extends MenuItemEl {
// has a submenuitem hovered which opens a third submenut. Then if you hover
// on yet another middle menu-item (not submenuitem) then focusout Event's
// relatedTarget will be `null` thus, causing all the menus to close
this.dispatchEvent(new StayOpenOnFocusoutEvent());
this.dispatchEvent(createStayOpenOnFocusoutEvent());
menu.show();

// Deactivate other items. This can be the case if the user has tabbed
// around the menu and then mouses over an md-sub-menu.
this.dispatchEvent(new DeactivateItemsEvent());
this.dispatchEvent(new DeactivateTypeaheadEvent());
this.dispatchEvent(createDeactivateItemsEvent());
this.dispatchEvent(createDeactivateTypeaheadEvent());
this.selected = true;

// This is the case of mouse hovering when already opened via keyboard or
Expand All @@ -258,11 +258,11 @@ export class SubMenuItem extends MenuItemEl {
const menu = this.submenuEl;
if (!menu || !menu.open) return;

this.dispatchEvent(new ActivateTypeaheadEvent());
this.dispatchEvent(createActivateTypeaheadEvent());
menu.quick = true;
menu.close();
// Restore focusout behavior.
this.dispatchEvent(new CloseOnFocusoutEvent());
this.dispatchEvent(createCloseOnFocusoutEvent());
this.active = false;
this.selected = false;
menu.addEventListener('closed', onClosed, {once: true});
Expand Down
2 changes: 1 addition & 1 deletion menu/menu-item-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {styles} from './internal/menuitem/menu-item-styles.css.js';
import {MenuItemLink} from './internal/menuitemlink/menu-item-link.js';

export {ListItem} from '../list/internal/listitem/list-item.js';
export {CloseMenuEvent, DeactivateItemsEvent, MenuItem} from './internal/shared.js';
export {CloseMenuEvent, MenuItem} from './internal/shared.js';


declare global {
Expand Down
2 changes: 1 addition & 1 deletion menu/menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {MenuItemEl} from './internal/menuitem/menu-item.js';
import {styles} from './internal/menuitem/menu-item-styles.css.js';

export {ListItem} from '../list/internal/listitem/list-item.js';
export {CloseMenuEvent, DeactivateItemsEvent, MenuItem} from './internal/shared.js';
export {CloseMenuEvent, MenuItem} from './internal/shared.js';

declare global {
interface HTMLElementTagNameMap {
Expand Down
2 changes: 1 addition & 1 deletion menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {styles} from './internal/menu-styles.css.js';

export {ListItem} from '../list/internal/listitem/list-item.js';
export {Corner, DefaultFocusState} from './internal/menu.js';
export {CloseMenuEvent, DeactivateItemsEvent, MenuItem} from './internal/shared.js';
export {CloseMenuEvent, MenuItem} from './internal/shared.js';

declare global {
interface HTMLElementTagNameMap {
Expand Down
2 changes: 1 addition & 1 deletion menu/sub-menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {styles} from './internal/menuitem/menu-item-styles.css.js';
import {SubMenuItem} from './internal/submenuitem/sub-menu-item.js';

export {ListItem} from '../list/internal/listitem/list-item.js';
export {CloseMenuEvent, DeactivateItemsEvent, MenuItem} from './internal/shared.js';
export {CloseMenuEvent, MenuItem} from './internal/shared.js';

declare global {
interface HTMLElementTagNameMap {
Expand Down
8 changes: 4 additions & 4 deletions select/internal/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {html as staticHtml, StaticValue} from 'lit/static-html.js';
import {Field} from '../../field/internal/field.js';
import {List} from '../../list/internal/list.js';
import {DEFAULT_TYPEAHEAD_BUFFER_TIME, Menu} from '../../menu/internal/menu.js';
import {DefaultCloseMenuEvent, isElementInSubtree, isSelectableKey} from '../../menu/internal/shared.js';
import {CloseMenuEvent, isElementInSubtree, isSelectableKey} from '../../menu/internal/shared.js';
import {TYPEAHEAD_RECORD} from '../../menu/internal/typeaheadController.js';

import {getSelectedItems, RequestDeselectionEvent, RequestSelectionEvent, SelectOption, SelectOptionRecord} from './shared.js';
Expand Down Expand Up @@ -445,9 +445,9 @@ export abstract class Select extends LitElement {
/**
* Determines the reason for closing, and updates the UI accordingly.
*/
private handleCloseMenu(event: InstanceType<typeof DefaultCloseMenuEvent>) {
const reason = event.reason;
const item = event.itemPath[0] as SelectOption;
private handleCloseMenu(event: CloseMenuEvent) {
const reason = event.detail.reason;
const item = event.detail.itemPath[0] as SelectOption;
this.open = false;
let hasChanged = false;

Expand Down

0 comments on commit 043d548

Please sign in to comment.