UNPKG

@material/web

Version:
130 lines 4.33 kB
/** * @license * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * Re-dispatches an event from the provided element. * * This function is useful for forwarding non-composed events, such as `change` * events. * * @example * class MyInput extends LitElement { * render() { * return html`<input @change=${this.redispatchEvent}>`; * } * * protected redispatchEvent(event: Event) { * redispatchEvent(this, event); * } * } * * @param element The element to dispatch the event from. * @param event The event to re-dispatch. * @return Whether or not the event was dispatched (if cancelable). */ export function redispatchEvent(element, event) { // For bubbling events in SSR light DOM (or composed), stop their propagation // and dispatch the copy. if (event.bubbles && (!element.shadowRoot || event.composed)) { event.stopPropagation(); } const copy = Reflect.construct(event.constructor, [event.type, event]); const dispatched = element.dispatchEvent(copy); if (!dispatched) { event.preventDefault(); } return dispatched; } /** * Dispatches a click event to the given element that triggers a native action, * but is not composed and therefore is not seen outside the element. * * This is useful for responding to an external click event on the host element * that should trigger an internal action like a button click. * * Note, a helper is provided because setting this up correctly is a bit tricky. * In particular, calling `click` on an element creates a composed event, which * is not desirable, and a manually dispatched event must specifically be a * `MouseEvent` to trigger a native action. * * @example * hostClickListener = (event: MouseEvent) { * if (isActivationClick(event)) { * this.dispatchActivationClick(this.buttonElement); * } * } * */ export function dispatchActivationClick(element) { const event = new MouseEvent('click', { bubbles: true }); element.dispatchEvent(event); return event; } /** * Returns true if the click event should trigger an activation behavior. The * behavior is defined by the element and is whatever it should do when * clicked. * * Typically when an element needs to handle a click, the click is generated * from within the element and an event listener within the element implements * the needed behavior; however, it's possible to fire a click directly * at the element that the element should handle. This method helps * distinguish these "external" clicks. * * An "external" click can be triggered in a number of ways: via a click * on an associated label for a form associated element, calling * `element.click()`, or calling * `element.dispatchEvent(new MouseEvent('click', ...))`. * * Also works around Firefox issue * https://bugzilla.mozilla.org/show_bug.cgi?id=1804576 by squelching * events for a microtask after called. * * @example * hostClickListener = (event: MouseEvent) { * if (isActivationClick(event)) { * this.dispatchActivationClick(this.buttonElement); * } * } * */ export function isActivationClick(event) { // Event must start at the event target. if (event.currentTarget !== event.target) { return false; } // Event must not be retargeted from shadowRoot. if (event.composedPath()[0] !== event.target) { return false; } // Target must not be disabled; this should only occur for a synthetically // dispatched click. if (event.target.disabled) { return false; } // This is an activation if the event should not be squelched. return !squelchEvent(event); } // TODO(https://bugzilla.mozilla.org/show_bug.cgi?id=1804576) // Remove when Firefox bug is addressed. function squelchEvent(event) { const squelched = isSquelchingEvents; if (squelched) { event.preventDefault(); event.stopImmediatePropagation(); } squelchEventsForMicrotask(); return squelched; } // Ignore events for one microtask only. let isSquelchingEvents = false; async function squelchEventsForMicrotask() { isSquelchingEvents = true; // Need to pause for just one microtask. // tslint:disable-next-line await null; isSquelchingEvents = false; } //# sourceMappingURL=events.js.map