diff --git a/eslint.config.mjs b/eslint.config.mjs index e006d362..87beee2a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -110,4 +110,33 @@ export default [ ], }, }, + { + // Library source must go through the free-function helpers in + // `Utils.ts` instead of calling the raw DOM event APIs directly — + // helper names mangle to single chars after minification, native + // property names like `addEventListener` are preserved verbatim. + files: ["src/**/*.ts"], + ignores: ["src/Utils.ts"], + rules: { + "no-restricted-syntax": [ + "error", + { + selector: + "CallExpression[callee.property.name='addEventListener']", + message: + "Use `addListener` from './Utils.js' (saves bytes after minification).", + }, + { + selector: + "CallExpression[callee.property.name='removeEventListener']", + message: "Use `removeListener` from './Utils.js'.", + }, + { + selector: + "CallExpression[callee.property.name='dispatchEvent']", + message: "Use `dispatchEvent` from './Utils.js'.", + }, + ], + }, + }, ]; diff --git a/src/CrossOrigin.ts b/src/CrossOrigin.ts index 469f0b3e..278ab672 100644 --- a/src/CrossOrigin.ts +++ b/src/CrossOrigin.ts @@ -14,11 +14,13 @@ import { Subscribable } from "./State/Subscribable.js"; import type * as Types from "./Types.js"; import { ObservedElementAccessibilities } from "./Consts.js"; import { + addListener, getElementUId, getInstanceContext, getUId, getWindowUId, type HTMLElementWithUID, + removeListener, } from "./Utils.js"; import { dom } from "./DOMAPI.js"; @@ -992,7 +994,7 @@ class CrossOriginTransactions { this.setSendUp(sendUp); - this._owner().addEventListener("pagehide", this._onPageHide); + addListener(this._owner(), "pagehide", this._onPageHide); this._ping(); } @@ -1029,11 +1031,11 @@ class CrossOriginTransactions { }; } - owner.addEventListener("message", this._onBrowserMessage); + addListener(owner, "message", this._onBrowserMessage); } } } else if (this._isDefaultSendUp) { - owner.removeEventListener("message", this._onBrowserMessage); + removeListener(owner, "message", this._onBrowserMessage); this._isDefaultSendUp = false; } @@ -1048,8 +1050,8 @@ class CrossOriginTransactions { this._pingTimer = undefined; } - owner.removeEventListener("message", this._onBrowserMessage); - owner.removeEventListener("pagehide", this._onPageHide); + removeListener(owner, "message", this._onBrowserMessage); + removeListener(owner, "pagehide", this._onPageHide); await this._dead(); diff --git a/src/Deloser.ts b/src/Deloser.ts index dda58a04..d1f6ad9a 100644 --- a/src/Deloser.ts +++ b/src/Deloser.ts @@ -14,9 +14,12 @@ import { TabsterMoveFocusEvent, } from "./Events.js"; import { + addListener, + dispatchEvent, documentContains, getElementUId, isDisplayNone, + removeListener, TabsterPart, WeakHTMLElement, } from "./Utils.js"; @@ -55,7 +58,8 @@ export class DeloserItem extends DeloserItemBase { if (available && deloserElement) { if ( - !deloserElement.dispatchEvent( + !dispatchEvent( + deloserElement, new TabsterMoveFocusEvent({ by: "deloser", owner: deloserElement, @@ -611,7 +615,8 @@ export class Deloser }; customFocusLostHandler(element: HTMLElement): boolean { - return element.dispatchEvent( + return dispatchEvent( + element, new DeloserFocusLostEvent(this.getActions()) ); } @@ -723,7 +728,8 @@ export class DeloserAPI implements Types.DeloserAPI { this._tabster.focusedElement.subscribe(this._onFocus); const doc = this._win().document; - doc.addEventListener( + addListener( + doc, DeloserRestoreFocusEventName, this._onRestoreFocus ); @@ -758,7 +764,8 @@ export class DeloserAPI implements Types.DeloserAPI { this._tabster.focusedElement.unsubscribe(this._onFocus); - win.document.removeEventListener( + removeListener( + win.document, DeloserRestoreFocusEventName, this._onRestoreFocus ); @@ -927,18 +934,23 @@ export class DeloserAPI implements Types.DeloserAPI { const curDeloserElement = curDeloser.getElement(); const el = curDeloser.findAvailable(); - if ( - el && - (!curDeloserElement?.dispatchEvent( - new TabsterMoveFocusEvent({ - by: "deloser", - owner: curDeloserElement, - next: el, - }) - ) || - this._tabster.focusedElement.focus(el)) - ) { - return; + if (el) { + if (!curDeloserElement) { + return; + } + if ( + !dispatchEvent( + curDeloserElement, + new TabsterMoveFocusEvent({ + by: "deloser", + owner: curDeloserElement, + next: el, + }) + ) || + this._tabster.focusedElement.focus(el) + ) { + return; + } } } } diff --git a/src/Deprecated.ts b/src/Deprecated.ts index 45f85b10..18086457 100644 --- a/src/Deprecated.ts +++ b/src/Deprecated.ts @@ -9,13 +9,14 @@ import { MoverMoveFocusEvent, MoverMemorizedElementEvent, } from "./Events.js"; +import { dispatchEvent } from "./Utils.js"; /** @deprecated This function is obsolete, use native element.dispatchEvent(new GroupperMoveFocusEvent(...)). */ export function dispatchGroupperMoveFocusEvent( target: HTMLElement, action: GroupperMoveFocusAction ) { - return target.dispatchEvent(new GroupperMoveFocusEvent({ action })); + return dispatchEvent(target, new GroupperMoveFocusEvent({ action })); } /** @deprecated This function is obsolete, use native element.dispatchEvent(new MoverMoveFocusEvent(...)). */ @@ -23,7 +24,7 @@ export function dispatchMoverMoveFocusEvent( target: HTMLElement, key: MoverKey ) { - return target.dispatchEvent(new MoverMoveFocusEvent({ key })); + return dispatchEvent(target, new MoverMoveFocusEvent({ key })); } /** @deprecated This function is obsolete, use native element.dispatchEvent(new MoverMemorizedElementEvent(...)). */ @@ -31,7 +32,8 @@ export function dispatchMoverMemorizedElementEvent( target: HTMLElement, memorizedElement: HTMLElement | undefined ) { - return target.dispatchEvent( + return dispatchEvent( + target, new MoverMemorizedElementEvent({ memorizedElement }) ); } diff --git a/src/DummyInput.ts b/src/DummyInput.ts index ea1b2a36..4f1f9d82 100644 --- a/src/DummyInput.ts +++ b/src/DummyInput.ts @@ -17,7 +17,14 @@ import { } from "./Consts.js"; import { TabsterMoveFocusEvent } from "./Events.js"; import { dom } from "./DOMAPI.js"; -import { hasSubFocusable, makeFocusIgnored, WeakHTMLElement } from "./Utils.js"; +import { + addListener, + dispatchEvent, + hasSubFocusable, + makeFocusIgnored, + removeListener, + WeakHTMLElement, +} from "./Utils.js"; const _updateDummyInputsTimeout = 100; @@ -91,8 +98,8 @@ export class DummyInput { this._isPhantom = props.isPhantom ?? false; this._fixedTarget = fixedTarget; - input.addEventListener("focusin", this._focusIn); - input.addEventListener("focusout", this._focusOut); + addListener(input, "focusin", this._focusIn); + addListener(input, "focusout", this._focusOut); (input as HTMLElementWithDummyContainer).__tabsterDummyContainer = element; @@ -130,8 +137,8 @@ export class DummyInput { delete this.onFocusOut; delete this.input; - input.removeEventListener("focusin", this._focusIn); - input.removeEventListener("focusout", this._focusOut); + removeListener(input, "focusin", this._focusIn); + removeListener(input, "focusout", this._focusOut); delete (input as HTMLElementWithDummyContainer).__tabsterDummyContainer; @@ -404,7 +411,9 @@ export class DummyInputManager { } if ( - parent?.dispatchEvent( + parent && + dispatchEvent( + parent, new TabsterMoveFocusEvent({ by: "root", owner: parent, @@ -786,7 +795,7 @@ class DummyInputManagerCore { .__tabsterDummy; for (const el of this._transformElements) { - el.removeEventListener("scroll", this._addTransformOffsets); + removeListener(el, "scroll", this._addTransformOffsets); } this._transformElements.clear(); @@ -922,7 +931,8 @@ class DummyInputManagerCore { if ( toFocus && - element.dispatchEvent( + dispatchEvent( + element, new TabsterMoveFocusEvent({ by: "root", owner: element, @@ -1104,10 +1114,7 @@ class DummyInputManagerCore { newTransformElements.add(element); if (!transformElements.has(element)) { - element.addEventListener( - "scroll", - this._addTransformOffsets - ); + addListener(element, "scroll", this._addTransformOffsets); } scrollTop += scrollTopLeft.scrollTop; @@ -1117,7 +1124,7 @@ class DummyInputManagerCore { for (const el of transformElements) { if (!newTransformElements.has(el)) { - el.removeEventListener("scroll", this._addTransformOffsets); + removeListener(el, "scroll", this._addTransformOffsets); } } diff --git a/src/Groupper.ts b/src/Groupper.ts index 893e1098..e8d07f0f 100644 --- a/src/Groupper.ts +++ b/src/Groupper.ts @@ -26,7 +26,14 @@ import { DummyInputManagerPriorities, getDummyInputContainer, } from "./DummyInput.js"; -import { getAdjacentElement, TabsterPart, WeakHTMLElement } from "./Utils.js"; +import { + addListener, + dispatchEvent, + getAdjacentElement, + removeListener, + TabsterPart, + WeakHTMLElement, +} from "./Utils.js"; import { dom } from "./DOMAPI.js"; class GroupperDummyManager extends DummyInputManager { @@ -451,9 +458,9 @@ export class GroupperAPI implements Types.GroupperAPI { this._onFocus(activeElement as HTMLElement); } - doc.addEventListener("mousedown", this._onMouseDown, true); - win.addEventListener("keydown", this._onKeyDown, true); - win.addEventListener(GroupperMoveFocusEventName, this._onMoveFocus); + addListener(doc, "mousedown", this._onMouseDown, true); + addListener(win, "keydown", this._onKeyDown, true); + addListener(win, GroupperMoveFocusEventName, this._onMoveFocus); }; dispose(): void { @@ -472,9 +479,9 @@ export class GroupperAPI implements Types.GroupperAPI { this._tabster.focusedElement.unsubscribe(this._onFocus); - win.document.removeEventListener("mousedown", this._onMouseDown, true); - win.removeEventListener("keydown", this._onKeyDown, true); - win.removeEventListener(GroupperMoveFocusEventName, this._onMoveFocus); + removeListener(win.document, "mousedown", this._onMouseDown, true); + removeListener(win, "keydown", this._onKeyDown, true); + removeListener(win, GroupperMoveFocusEventName, this._onMoveFocus); Object.keys(this._grouppers).forEach((groupperId) => { if (this._grouppers[groupperId]) { @@ -656,7 +663,8 @@ export class GroupperAPI implements Types.GroupperAPI { next && (!relatedEvent || (relatedEvent && - groupperElement.dispatchEvent( + dispatchEvent( + groupperElement, new TabsterMoveFocusEvent({ by: "groupper", owner: groupperElement, @@ -716,7 +724,8 @@ export class GroupperAPI implements Types.GroupperAPI { next && (!relatedEvent || (relatedEvent && - groupperElement.dispatchEvent( + dispatchEvent( + groupperElement, new TabsterMoveFocusEvent({ by: "groupper", owner: groupperElement, diff --git a/src/Modalizer.ts b/src/Modalizer.ts index 0402dece..66072a69 100644 --- a/src/Modalizer.ts +++ b/src/Modalizer.ts @@ -17,7 +17,14 @@ import { DummyInputManagerPriorities, getDummyInputContainer, } from "./DummyInput.js"; -import { augmentAttribute, TabsterPart, WeakHTMLElement } from "./Utils.js"; +import { + addListener, + augmentAttribute, + dispatchEvent, + removeListener, + TabsterPart, + WeakHTMLElement, +} from "./Utils.js"; import { dom } from "./DOMAPI.js"; let _wasFocusedCounter = 0; @@ -304,7 +311,7 @@ export class Modalizer ? new ModalizerActiveEvent(eventDetail) : new ModalizerInactiveEvent(eventDetail); - el.dispatchEvent(event); + dispatchEvent(el, event); if (event.defaultPrevented) { defaultPrevented = true; @@ -367,7 +374,7 @@ export class ModalizerAPI implements Types.ModalizerAPI { } const win = this._win(); - win.addEventListener("keydown", this._onKeyDown, true); + addListener(win, "keydown", this._onKeyDown, true); tabster.queueInit(() => { this._tabster.focusedElement.subscribe(this._onFocus); @@ -377,7 +384,7 @@ export class ModalizerAPI implements Types.ModalizerAPI { dispose(): void { const win = this._win(); - win.removeEventListener("keydown", this._onKeyDown, true); + removeListener(win, "keydown", this._onKeyDown, true); // Dispose all modalizers managed by the API Object.keys(this._modalizers).forEach((modalizerId) => { diff --git a/src/Mover.ts b/src/Mover.ts index 0bca09a5..c899f11a 100644 --- a/src/Mover.ts +++ b/src/Mover.ts @@ -25,10 +25,13 @@ import { getDummyInputContainer, } from "./DummyInput.js"; import { + addListener, createElementTreeWalker, + dispatchEvent, getElementUId, isElementVerticallyVisibleInContainer, matchesSelector, + removeListener, scrollIntoView, TabsterPart, WeakHTMLElement, @@ -227,7 +230,7 @@ export class Mover const state = this.getState(el); if (state) { - el.dispatchEvent(new MoverStateEvent(state)); + dispatchEvent(el, new MoverStateEvent(state)); } } } @@ -399,7 +402,7 @@ export class Mover const state = this.getState(el); if (state) { - el.dispatchEvent(new MoverStateEvent(state)); + dispatchEvent(el, new MoverStateEvent(state)); } } } @@ -701,9 +704,10 @@ export class MoverAPI implements Types.MoverAPI { private _init = (): void => { const win = this._win(); - win.addEventListener("keydown", this._onKeyDown, true); - win.addEventListener(MoverMoveFocusEventName, this._onMoveFocus); - win.addEventListener( + addListener(win, "keydown", this._onKeyDown, true); + addListener(win, MoverMoveFocusEventName, this._onMoveFocus); + addListener( + win, MoverMemorizedElementEventName, this._onMemorizedElement ); @@ -723,9 +727,10 @@ export class MoverAPI implements Types.MoverAPI { delete this._ignoredInputTimer; } - win.removeEventListener("keydown", this._onKeyDown, true); - win.removeEventListener(MoverMoveFocusEventName, this._onMoveFocus); - win.removeEventListener( + removeListener(win, "keydown", this._onKeyDown, true); + removeListener(win, MoverMoveFocusEventName, this._onMoveFocus); + removeListener( + win, MoverMemorizedElementEventName, this._onMemorizedElement ); @@ -1197,7 +1202,8 @@ export class MoverAPI implements Types.MoverAPI { next && (!relatedEvent || (relatedEvent && - container.dispatchEvent( + dispatchEvent( + container, new TabsterMoveFocusEvent({ by: "mover", owner: container, diff --git a/src/Outline.ts b/src/Outline.ts index 77bd671c..79edacb5 100644 --- a/src/Outline.ts +++ b/src/Outline.ts @@ -5,7 +5,7 @@ import { getTabsterOnElement } from "./Instance.js"; import type * as Types from "./Types.js"; -import { getBoundingRect } from "./Utils.js"; +import { addListener, getBoundingRect, removeListener } from "./Utils.js"; interface WindowWithOutlineStyle extends Window { __tabsterOutline?: { @@ -96,10 +96,11 @@ export class OutlineAPI implements Types.OutlineAPI { const win = this._win(); - win.addEventListener("scroll", this._onScroll, true); // Capture! + addListener(win, "scroll", this._onScroll, true); // Capture! if (this._fullScreenEventName) { - win.document.addEventListener( + addListener( + win.document, this._fullScreenEventName, this._onFullScreenChanged ); @@ -139,10 +140,11 @@ export class OutlineAPI implements Types.OutlineAPI { ); this._tabster.focusedElement.unsubscribe(this._onFocus); - win.removeEventListener("scroll", this._onScroll, true); + removeListener(win, "scroll", this._onScroll, true); if (this._fullScreenEventName) { - win.document.removeEventListener( + removeListener( + win.document, this._fullScreenEventName, this._onFullScreenChanged ); diff --git a/src/Restorer.ts b/src/Restorer.ts index ae7eb367..0b054d5d 100644 --- a/src/Restorer.ts +++ b/src/Restorer.ts @@ -18,7 +18,13 @@ import { RestorerRestoreFocusEvent, RestorerRestoreFocusEventName, } from "./Events.js"; -import { TabsterPart, WeakHTMLElement } from "./Utils.js"; +import { + addListener, + dispatchEvent, + removeListener, + TabsterPart, + WeakHTMLElement, +} from "./Utils.js"; import { dom } from "./DOMAPI.js"; class Restorer extends TabsterPart implements RestorerInterface { @@ -33,8 +39,8 @@ class Restorer extends TabsterPart implements RestorerInterface { if (this._props.type === RestorerTypes.Source) { const element = this._element?.get(); - element?.addEventListener("focusout", this._onFocusOut); - element?.addEventListener("focusin", this._onFocusIn); + addListener(element, "focusout", this._onFocusOut); + addListener(element, "focusin", this._onFocusIn); // set hasFocus when the instance is created, in case focus has already moved within it this._hasFocus = dom.nodeContains( @@ -47,12 +53,12 @@ class Restorer extends TabsterPart implements RestorerInterface { dispose(): void { if (this._props.type === RestorerTypes.Source) { const element = this._element?.get(); - element?.removeEventListener("focusout", this._onFocusOut); - element?.removeEventListener("focusin", this._onFocusIn); + removeListener(element, "focusout", this._onFocusOut); + removeListener(element, "focusin", this._onFocusIn); if (this._hasFocus) { const doc = this._tabster.getWindow().document; - doc.body.dispatchEvent(new RestorerRestoreFocusEvent()); + dispatchEvent(doc.body, new RestorerRestoreFocusEvent()); } } } @@ -60,7 +66,7 @@ class Restorer extends TabsterPart implements RestorerInterface { private _onFocusOut = (e: FocusEvent) => { const element = this._element?.get(); if (element && e.relatedTarget === null) { - element.dispatchEvent(new RestorerRestoreFocusEvent()); + dispatchEvent(element, new RestorerRestoreFocusEvent()); } if ( element && @@ -140,7 +146,8 @@ export class RestorerAPI implements RestorerAPIType { constructor(tabster: TabsterCore) { this._tabster = tabster; this._getWindow = tabster.getWindow; - this._getWindow().addEventListener( + addListener( + this._getWindow(), RestorerRestoreFocusEventName, this._onRestoreFocus ); @@ -158,7 +165,8 @@ export class RestorerAPI implements RestorerAPIType { this._focusedElementState.cancelAsyncFocus(AsyncFocusSources.Restorer); - win.removeEventListener( + removeListener( + win, RestorerRestoreFocusEventName, this._onRestoreFocus ); diff --git a/src/Root.ts b/src/Root.ts index 31b84124..d4a90228 100644 --- a/src/Root.ts +++ b/src/Root.ts @@ -12,7 +12,14 @@ import { DummyInputManager, DummyInputManagerPriorities, } from "./DummyInput.js"; -import { getElementUId, TabsterPart, type WeakHTMLElement } from "./Utils.js"; +import { + addListener, + dispatchEvent, + getElementUId, + removeListener, + TabsterPart, + type WeakHTMLElement, +} from "./Utils.js"; import { setTabsterAttribute } from "./AttributeHelpers.js"; export interface WindowWithTabsterInstance extends Window { @@ -130,8 +137,8 @@ export class Root const w = win(); const doc = w.document; - doc.addEventListener(KEYBORG_FOCUSIN, this._onFocusIn); - doc.addEventListener(KEYBORG_FOCUSOUT, this._onFocusOut); + addListener(doc, KEYBORG_FOCUSIN, this._onFocusIn); + addListener(doc, KEYBORG_FOCUSOUT, this._onFocusOut); this._add(); } @@ -153,8 +160,8 @@ export class Root const win = this._tabster.getWindow(); const doc = win.document; - doc.removeEventListener(KEYBORG_FOCUSIN, this._onFocusIn); - doc.removeEventListener(KEYBORG_FOCUSOUT, this._onFocusOut); + removeListener(doc, KEYBORG_FOCUSIN, this._onFocusIn); + removeListener(doc, KEYBORG_FOCUSOUT, this._onFocusOut); if (this._setFocusedTimer) { win.clearTimeout(this._setFocusedTimer); @@ -201,7 +208,7 @@ export class Root if (hasFocused) { this._isFocused = true; this._dummyManager?.setTabbable(false); - element.dispatchEvent(new RootFocusEvent({ element })); + dispatchEvent(element, new RootFocusEvent({ element })); } else { this._setFocusedTimer = this._tabster .getWindow() @@ -210,7 +217,7 @@ export class Root this._isFocused = false; this._dummyManager?.setTabbable(true); - element.dispatchEvent(new RootBlurEvent({ element })); + dispatchEvent(element, new RootBlurEvent({ element })); }, 0); } } @@ -291,14 +298,14 @@ export class RootAPI implements Types.RootAPI { } } else if (!this._autoRootWaiting) { this._autoRootWaiting = true; - doc.addEventListener("readystatechange", this._autoRootCreate); + addListener(doc, "readystatechange", this._autoRootCreate); } return undefined; }; private _autoRootUnwait(doc: Document): void { - doc.removeEventListener("readystatechange", this._autoRootCreate); + removeListener(doc, "readystatechange", this._autoRootCreate); this._autoRootWaiting = false; } diff --git a/src/State/FocusedElement.ts b/src/State/FocusedElement.ts index e92e04d9..498ec2da 100644 --- a/src/State/FocusedElement.ts +++ b/src/State/FocusedElement.ts @@ -22,8 +22,11 @@ import { } from "../Events.js"; import { DummyInputManager } from "../DummyInput.js"; import { + addListener, + dispatchEvent, documentContains, getLastChild, + removeListener, shouldIgnoreFocus, WeakHTMLElement, } from "../Utils.js"; @@ -104,17 +107,19 @@ export class FocusedElementState const doc = win.document; // Add these event listeners as capture - we want Tabster to run before user event handlers - doc.addEventListener( + addListener( + doc, KEYBORG_FOCUSIN, this._onFocusIn as EventListener, true ); - doc.addEventListener( + addListener( + doc, KEYBORG_FOCUSOUT, this._onFocusOut as EventListener, true ); - win.addEventListener("keydown", this._onKeyDown, true); + addListener(win, "keydown", this._onKeyDown, true); const activeElement = dom.getActiveElement(doc); @@ -131,17 +136,19 @@ export class FocusedElementState const win = this._win(); const doc = win.document; - doc.removeEventListener( + removeListener( + doc, KEYBORG_FOCUSIN, this._onFocusIn as EventListener, true ); - doc.removeEventListener( + removeListener( + doc, KEYBORG_FOCUSOUT, this._onFocusOut as EventListener, true ); - win.removeEventListener("keydown", this._onKeyDown, true); + removeListener(win, "keydown", this._onKeyDown, true); this.unsubscribe(this._onChanged); @@ -657,7 +664,8 @@ export class FocusedElementState // For iframes and uncontrolled areas we always want to use default action to // move focus into. if ( - rootElement.dispatchEvent( + dispatchEvent( + rootElement, new TabsterMoveFocusEvent({ by: "root", owner: rootElement, @@ -680,7 +688,8 @@ export class FocusedElementState if (controlTab || next?.outOfDOMOrder) { if ( - rootElement.dispatchEvent( + dispatchEvent( + rootElement, new TabsterMoveFocusEvent({ by: "root", owner: rootElement, @@ -701,7 +710,8 @@ export class FocusedElementState } else { if ( !uncontrolledCompletelyContainer && - rootElement.dispatchEvent( + dispatchEvent( + rootElement, new TabsterMoveFocusEvent({ by: "root", owner: rootElement, @@ -720,7 +730,7 @@ export class FocusedElementState detail: Types.FocusedElementDetail ): void => { if (element) { - element.dispatchEvent(new TabsterFocusInEvent(detail)); + dispatchEvent(element, new TabsterFocusInEvent(detail)); } else { const last = this._lastVal?.get(); @@ -733,7 +743,7 @@ export class FocusedElementState d.modalizerId = modalizerId; } - last.dispatchEvent(new TabsterFocusOutEvent(d)); + dispatchEvent(last, new TabsterFocusOutEvent(d)); } } }; diff --git a/src/Utils.ts b/src/Utils.ts index d4dc5845..4d8e1183 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -601,6 +601,45 @@ export function getRadioButtonGroup( }; } +/** + * Thin wrappers around `addEventListener` / `removeEventListener`. Their + * names get mangled to single chars by the minifier, while the inlined + * property accesses on `target` would not — so each call site shrinks by + * the difference between the helper's mangled name and the property name. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyEventHandler = (event: any) => void; + +export function addListener( + target: EventTarget | null | undefined, + type: string, + handler: AnyEventHandler, + options?: boolean | AddEventListenerOptions +): void { + target?.addEventListener(type, handler, options); +} + +export function removeListener( + target: EventTarget | null | undefined, + type: string, + handler: AnyEventHandler, + options?: boolean | EventListenerOptions +): void { + target?.removeEventListener(type, handler, options); +} + +/** + * Thin wrapper around `target.dispatchEvent`. Returns `false` if the dispatch + * was canceled (preventDefault) OR if `target` is nullish — both shapes the + * existing call sites already handle the same way. + */ +export function dispatchEvent( + target: EventTarget | null | undefined, + event: Event +): boolean { + return !!target && target.dispatchEvent(event); +} + /** * If the passed element is Tabster dummy input, returns the container element this dummy input belongs to. * @param element Element to check for being dummy input.