Skip to content

Commit

Permalink
refactor(core): Refactor EarlyEventContract to prepare for using it…
Browse files Browse the repository at this point in the history
… as a container.

This is the first step towards combining `EarlyEventContract` and `EventContract`. It contains a few refactors, such as making names more consistent.

The goal of this refactor is to remove the `EarlyEventContract` class altogether, as well as `EventContract`.

To install the early event contract with the default events in early script tag, users will call:

`bootstrapGlobalEarlyEventContract()`

And for boostraping:

`registerGlobalDispatcher(dispatcher)`
  • Loading branch information
tbondwilkinson committed Jul 8, 2024
1 parent a1f6ff3 commit 4c60dd3
Show file tree
Hide file tree
Showing 18 changed files with 170 additions and 217 deletions.
31 changes: 12 additions & 19 deletions goldens/public-api/core/primitives/event-dispatch/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ export const Attribute: {
JSACTION: "jsaction";
};

// @public
export function bootstrapEarlyEventContract(field: string, container: HTMLElement, appId: string, eventTypes?: string[], captureEventTypes?: string[], earlyJsactionTracker?: EventContractTracker<EarlyJsactionDataContainer>): void;

// @public (undocumented)
export interface EarlyJsactionDataContainer {
// (undocumented)
_ejsa?: EarlyJsactionData;
// (undocumented)
_ejsas?: {
[appId: string]: EarlyJsactionData | undefined;
};
}

// @public
Expand All @@ -32,7 +33,7 @@ export class EventContract implements UnrenamedEventContract {
// (undocumented)
static MOUSE_SPECIAL_SUPPORT: boolean;
registerDispatcher(dispatcher: Dispatcher, restriction: Restriction): void;
replayEarlyEvents(earlyJsactionContainer?: EarlyJsactionDataContainer): void;
replayEarlyEvents(earlyJsactionData?: EarlyJsactionData | undefined): void;
}

// @public
Expand All @@ -44,13 +45,6 @@ export class EventContractContainer implements EventContractContainerManager {
readonly element: Element;
}

// @public (undocumented)
export type EventContractTracker<T> = {
[key: string]: {
[appId: string]: T;
};
};

// @public
export class EventDispatcher {
constructor(dispatchDelegate: (event: Event, actionName: string) => void);
Expand Down Expand Up @@ -107,19 +101,18 @@ export const EventPhase: {
};

// @public
export const isCaptureEvent: (eventType: string) => boolean;
export function getActionCache(element: Element): {
[key: string]: string | undefined;
};

// @public
export const isSupportedEvent: (eventType: string) => boolean;
export const isCaptureEventType: (eventType: string) => boolean;

// @public
export function registerDispatcher(eventContract: UnrenamedEventContract, dispatcher: EventDispatcher): void;

// @public (undocumented)
export function registerEventType(element: Element, eventType: string, action: string): void;
export const isEarlyEventType: (eventType: string) => boolean;

// @public (undocumented)
export function unregisterEventType(element: Element, eventType: string): void;
// @public
export function registerDispatcher(eventContract: UnrenamedEventContract, dispatcher: EventDispatcher): void;

// (No @packageDocumentation comment for this package)

Expand Down
4 changes: 2 additions & 2 deletions packages/core/primitives/event-dispatch/contract_binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/

import {bootstrapEarlyEventContract} from './src/register_events';
import {bootstrapAppScopedEarlyEventContract} from './src/bootstrap_app_scoped';

(window as any)['__jsaction_bootstrap'] = bootstrapEarlyEventContract;
(window as any)['__jsaction_bootstrap'] = bootstrapAppScopedEarlyEventContract;
15 changes: 6 additions & 9 deletions packages/core/primitives/event-dispatch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/

export {EventDispatcher, EventPhase, registerDispatcher} from './src/event_dispatcher';
export {EventContractContainer} from './src/event_contract_container';
export {Attribute} from './src/attribute';
export {getDefaulted as getActionCache} from './src/cache';
export type {EarlyJsactionDataContainer} from './src/earlyeventcontract';
export {EventContract} from './src/eventcontract';
export {bootstrapEarlyEventContract} from './src/register_events';

export type {EventContractTracker} from './src/register_events';
export {EventContractContainer} from './src/event_contract_container';
export {EventDispatcher, EventPhase, registerDispatcher} from './src/event_dispatcher';
export {EventInfoWrapper} from './src/event_info';
export {isSupportedEvent, isCaptureEvent} from './src/event_type';
export {Attribute} from './src/attribute';
export {registerEventType, unregisterEventType} from './src/cache';
export {isEarlyEventType, isCaptureEventType} from './src/event_type';
export {EventContract} from './src/eventcontract';
4 changes: 2 additions & 2 deletions packages/core/primitives/event-dispatch/src/a11y_click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function preventDefaultForA11yClick(eventInfo: eventInfoLib.EventInfo) {
export function populateClickOnlyAction(
actionElement: Element,
eventInfo: eventInfoLib.EventInfo,
actionMap: {[key: string]: string},
actionMap: {[key: string]: string | undefined},
) {
if (
// If there's already an action, don't attempt to set a CLICKONLY
Expand All @@ -60,5 +60,5 @@ export function populateClickOnlyAction(
return;
}
eventInfoLib.setEventType(eventInfo, EventType.CLICKONLY);
eventInfoLib.setAction(eventInfo, actionMap[EventType.CLICKONLY], actionElement);
eventInfoLib.setAction(eventInfo, actionMap[EventType.CLICKONLY]!, actionElement);
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class ActionResolver {
private populateClickOnlyAction?: (
actionElement: Element,
eventInfo: eventInfoLib.EventInfo,
actionMap: {[key: string]: string},
actionMap: {[key: string]: string | undefined},
) => void = undefined;

constructor({
Expand Down Expand Up @@ -243,8 +243,8 @@ export class ActionResolver {
* @param actionElement The DOM node to retrieve the jsaction map from.
* @return Map from event to qualified name of the jsaction bound to it.
*/
private parseActions(actionElement: Element): {[key: string]: string} {
let actionMap: {[key: string]: string} | undefined = cache.get(actionElement);
private parseActions(actionElement: Element): {[key: string]: string | undefined} {
let actionMap: {[key: string]: string | undefined} | undefined = cache.get(actionElement);
if (!actionMap) {
const jsactionAttribute = actionElement.getAttribute(Attribute.JSACTION);
if (!jsactionAttribute) {
Expand Down
42 changes: 15 additions & 27 deletions packages/core/primitives/event-dispatch/src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,29 @@ import {Property} from './property';
/**
* Map from jsaction annotation to a parsed map from event name to action name.
*/
const parseCache: {[key: string]: {[key: string]: string}} = {};

export function registerEventType(element: Element, eventType: string, action: string) {
const cache = get(element) || {};
cache[eventType] = action;
set(element, cache);
}

export function unregisterEventType(element: Element, eventType: string) {
const cache = get(element);
if (cache) {
cache[eventType] = undefined as unknown as string;
}
}
const parseCache: {[key: string]: {[key: string]: string | undefined}} = {};

/**
* Reads the jsaction parser cache from the given DOM Element.
*
* @param element .
* @return Map from event to qualified name of the jsaction bound to it.
*/
export function get(element: Element): {[key: string]: string} {
// @ts-ignore
export function get(element: Element): {[key: string]: string | undefined} | undefined {
return element[Property.JSACTION];
}

/**
* Reads the jsaction parser cache for the given DOM element. If no cache is yet present,
* creates an empty one.
*/
export function getDefaulted(element: Element): {[key: string]: string | undefined} {
const cache = get(element) ?? {};
set(element, cache);
return cache;
}

/**
* Writes the jsaction parser cache to the given DOM Element.
*
* @param element .
* @param actionMap Map from event to qualified name of the jsaction bound to
* it.
*/
export function set(element: Element, actionMap: {[key: string]: string}) {
// @ts-ignore
export function set(element: Element, actionMap: {[key: string]: string | undefined}) {
element[Property.JSACTION] = actionMap;
}

Expand All @@ -55,7 +43,7 @@ export function set(element: Element, actionMap: {[key: string]: string}) {
* @param text Unparsed jsaction attribute value.
* @return Parsed jsaction attribute value, if already present in the cache.
*/
export function getParsed(text: string): {[key: string]: string} | undefined {
export function getParsed(text: string): {[key: string]: string | undefined} | undefined {
return parseCache[text];
}

Expand All @@ -65,7 +53,7 @@ export function getParsed(text: string): {[key: string]: string} | undefined {
* @param text Unparsed jsaction attribute value.
* @param parsed Attribute value parsed into the action map.
*/
export function setParsed(text: string, parsed: {[key: string]: string}) {
export function setParsed(text: string, parsed: {[key: string]: string | undefined}) {
parseCache[text] = parsed;
}

Expand Down
91 changes: 61 additions & 30 deletions packages/core/primitives/event-dispatch/src/earlyeventcontract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import {createEventInfoFromParameters, EventInfo} from './event_info';

export declare interface EarlyJsactionDataContainer {
_ejsa?: EarlyJsactionData;
_ejsas?: {[appId: string]: EarlyJsactionData | undefined};
}

declare global {
interface Window {
_ejsa?: EarlyJsactionData;
_ejsas?: {[appId: string]: EarlyJsactionData | undefined};
}
}

/**
Expand All @@ -19,15 +27,18 @@ export declare interface EarlyJsactionData {
/** List used to keep track of the early JSAction event types. */
et: string[];

/** List used to keep track of capture event types. */
/** List used to keep track of the early JSAction capture event types. */
etc: string[];

/** List used to keep track of the JSAction events if using earlyeventcontract. */
q: EventInfo[];

/** Early Jsaction handler. */
/** Early JSAction handler for all events. */
h: (event: Event) => void;

/** Dispatcher handler. Initializes to populating `q`. */
d: (eventInfo: EventInfo) => void;

/** List used to push `EventInfo` objects if the dispatcher is not registered. */
q: EventInfo[];

/** Container for listening to events. */
c: HTMLElement;
}
Expand All @@ -39,37 +50,57 @@ export declare interface EarlyJsactionData {
*/
export class EarlyEventContract {
constructor(
private readonly replaySink: EarlyJsactionDataContainer = window as EarlyJsactionDataContainer,
private readonly container = window.document.documentElement,
private readonly dataContainer: EarlyJsactionDataContainer = window,
container = window.document.documentElement,
) {
replaySink._ejsa = {
c: container,
q: [],
et: [],
etc: [],
h: (event: Event) => {
const eventInfo = createEventInfoFromParameters(
event.type,
event,
event.target as Element,
container,
Date.now(),
);
replaySink._ejsa!.q.push(eventInfo);
},
};
dataContainer._ejsa = createEarlyJsactionData(container);
}

/**
* Installs a list of event types for container .
*/
addEvents(types: string[], capture?: boolean) {
const replaySink = this.replaySink._ejsa!;
for (let idx = 0; idx < types.length; idx++) {
const eventType = types[idx];
const eventTypes = capture ? replaySink.etc : replaySink.et;
eventTypes.push(eventType);
this.container.addEventListener(eventType, replaySink.h, capture);
}
addEvents(this.dataContainer._ejsa!, types, capture);
}
}

/** Creates an `EarlyJsactionData` object. */
export function createEarlyJsactionData(container: HTMLElement) {
const q: EventInfo[] = [];
const d = (eventInfo: EventInfo) => {
q.push(eventInfo);
};
const h = (event: Event) => {
d(
createEventInfoFromParameters(
event.type,
event,
event.target as Element,
container,
Date.now(),
),
);
};
return {
c: container,
q,
et: [],
etc: [],
d,
h,
};
}

/** Add all the events to the container stored in the `EarlyJsactionData`. */
export function addEvents(
earlyJsactionData: EarlyJsactionData,
types: string[],
capture?: boolean,
) {
for (let i = 0; i < types.length; i++) {
const eventType = types[i];
const eventTypes = capture ? earlyJsactionData.etc : earlyJsactionData.et;
eventTypes.push(eventType);
earlyJsactionData.c.addEventListener(eventType, earlyJsactionData.h, capture);
}
}
4 changes: 2 additions & 2 deletions packages/core/primitives/event-dispatch/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import {EventHandlerInfo} from './event_handler';
import {isCaptureEvent, EventType} from './event_type';
import {isCaptureEventType, EventType} from './event_type';
import {KeyCode} from './key_code';

/**
Expand Down Expand Up @@ -61,7 +61,7 @@ export function addEventListener(
// handled in the capture phase.
let capture = false;

if (isCaptureEvent(eventType)) {
if (isCaptureEventType(eventType)) {
capture = true;
}
element.addEventListener(eventType, handler, capture);
Expand Down
Loading

0 comments on commit 4c60dd3

Please sign in to comment.