From 043d5482702620fe36f87255c74b17bb76d10554 Mon Sep 17 00:00:00 2001 From: Elliott Marquez Date: Wed, 2 Aug 2023 17:23:04 -0700 Subject: [PATCH] refactor(menu)!: events no longer subclass Event but rather use CustomEvent 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 --- menu/demo/stories.ts | 6 +- menu/internal/menuitem/menu-item.ts | 8 +- menu/internal/menuitemlink/menu-item-link.ts | 8 +- menu/internal/shared.ts | 99 ++++++++++++-------- menu/internal/submenuitem/sub-menu-item.ts | 46 ++++----- menu/menu-item-link.ts | 2 +- menu/menu-item.ts | 2 +- menu/menu.ts | 2 +- menu/sub-menu-item.ts | 2 +- select/internal/select.ts | 8 +- 10 files changed, 102 insertions(+), 81 deletions(-) diff --git a/menu/demo/stories.ts b/menu/demo/stories.ts index 3145b133ef..2b74077bda 100644 --- a/menu/demo/stories.ts +++ b/menu/demo/stories.ts @@ -244,9 +244,9 @@ function displayCloseEvent(outputRef: Ref) { 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)}`; }; } diff --git a/menu/internal/menuitem/menu-item.ts b/menu/internal/menuitem/menu-item.ts index 162209e768..b77a9a2d75 100644 --- a/menu/internal/menuitem/menu-item.ts +++ b/menu/internal/menuitem/menu-item.ts @@ -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'; @@ -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() { @@ -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})); } } diff --git a/menu/internal/menuitemlink/menu-item-link.ts b/menu/internal/menuitemlink/menu-item-link.ts index 9b1187c177..8d084c2cf5 100644 --- a/menu/internal/menuitemlink/menu-item-link.ts +++ b/menu/internal/menuitemlink/menu-item-link.ts @@ -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} @@ -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) { @@ -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})); } } diff --git a/menu/internal/shared.ts b/menu/internal/shared.ts index 90e4387484..9e5ae4f9b9 100644 --- a/menu/internal/shared.ts +++ b/menu/internal/shared.ts @@ -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 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( + 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; + +/** + * The type of the default close menu event used by md-menu. */ // tslint:disable-next-line -export const DefaultCloseMenuEvent = CloseMenuEvent; +export type CloseMenuEvent = + ReturnType>; /** - * 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; + + +/** + * 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; + +/** + * 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; + /** * Keys that are used to navigate menus. */ diff --git a/menu/internal/submenuitem/sub-menu-item.ts b/menu/internal/submenuitem/sub-menu-item.ts index dda6c13c12..9b2f982958 100644 --- a/menu/internal/submenuitem/sub-menu-item.ts +++ b/menu/internal/submenuitem/sub-menu-item.ts @@ -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 { /** @@ -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; @@ -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 @@ -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}); diff --git a/menu/menu-item-link.ts b/menu/menu-item-link.ts index 3dc2d822d7..3e2f0e677a 100644 --- a/menu/menu-item-link.ts +++ b/menu/menu-item-link.ts @@ -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 { diff --git a/menu/menu-item.ts b/menu/menu-item.ts index 3886db87cf..3b93ff9c4f 100644 --- a/menu/menu-item.ts +++ b/menu/menu-item.ts @@ -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 { diff --git a/menu/menu.ts b/menu/menu.ts index f254a55d72..0e510fdc28 100644 --- a/menu/menu.ts +++ b/menu/menu.ts @@ -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 { diff --git a/menu/sub-menu-item.ts b/menu/sub-menu-item.ts index ac4e006154..d36e83d6d2 100644 --- a/menu/sub-menu-item.ts +++ b/menu/sub-menu-item.ts @@ -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 { diff --git a/select/internal/select.ts b/select/internal/select.ts index 1f0e8ef19d..e15a4840ce 100644 --- a/select/internal/select.ts +++ b/select/internal/select.ts @@ -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'; @@ -445,9 +445,9 @@ export abstract class Select extends LitElement { /** * Determines the reason for closing, and updates the UI accordingly. */ - private handleCloseMenu(event: InstanceType) { - 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;