Skip to content

Commit

Permalink
feat(core): Add event delegation library to queue up events and repla…
Browse files Browse the repository at this point in the history
…y them when the application is ready

This adds the JSAction library from the Wiz framework to core/primitives.
  • Loading branch information
iteriani committed Mar 29, 2024
1 parent 1b8bf1d commit e0a12d7
Show file tree
Hide file tree
Showing 31 changed files with 8,190 additions and 0 deletions.
29 changes: 29 additions & 0 deletions packages/core/primitives/event-dispatch/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
load("//tools:defaults.bzl", "ts_library", "tsec_test")

package(default_visibility = ["//visibility:public"])


ts_library(
name = "event-dispatch",
srcs = glob(
[
"**/*.ts",
],
),
module_name = "@angular/core/primitives/event-dispatch"
)

tsec_test(
name = "tsec_test",
target = "event-dispatch",
tsconfig = "//packages:tsec_config",
)

filegroup(
name = "files_for_docgen",
srcs = glob([
"*.ts",
"src/**/*.ts",
]),
visibility = ["//visibility:public"],
)
13 changes: 13 additions & 0 deletions packages/core/primitives/event-dispatch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/


export {bootstrapEventContract} from './src/register_events';
export {Dispatcher, registerDispatcher} from './src/dispatcher';
export {EventContract} from './src/eventcontract';
export {EventContractContainer} from './src/event_contract_container';
66 changes: 66 additions & 0 deletions packages/core/primitives/event-dispatch/src/a11y_click.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import * as eventLib from './event';
import * as eventInfoLib from './event_info';
import {EventType} from './event_type';

/**
* Update `EventInfo` to be `eventType = 'click'` and sets `a11yClickKey` if it
* is a a11y click.
*/
export function updateEventInfoForA11yClick(eventInfo: eventInfoLib.EventInfo) {
if (!eventLib.isActionKeyEvent(eventInfoLib.getEvent(eventInfo))) {
return;
}
eventInfoLib.setA11yClickKey(eventInfo, true);
// A 'click' triggered by a DOM keypress should be mapped to the 'click'
// jsaction.
eventInfoLib.setEventType(eventInfo, EventType.CLICK);
}

/**
* Call `preventDefault` on an a11y click if it is space key or to prevent the
* browser's default action for native HTML controls.
*/
export function preventDefaultForA11yClick(eventInfo: eventInfoLib.EventInfo) {
if (
!eventInfoLib.getA11yClickKey(eventInfo) ||
(!eventLib.isSpaceKeyEvent(eventInfoLib.getEvent(eventInfo)) &&
!eventLib.shouldCallPreventDefaultOnNativeHtmlControl(
eventInfoLib.getEvent(eventInfo),
))
) {
return;
}
eventLib.preventDefault(eventInfoLib.getEvent(eventInfo));
}

/**
* Sets the `action` to `clickonly` for a click event that is not an a11y click
* and if there is not already a click action.
*/
export function populateClickOnlyAction(
eventInfo: eventInfoLib.EventInfo,
actionMap: {[key: string]: string},
) {
if (
eventInfoLib.getEventType(eventInfo) === EventType.CLICK &&
// No a11y clicks should map to 'clickonly'.
!eventInfoLib.getA11yClickKey(eventInfo) &&
!actionMap[EventType.CLICK] &&
actionMap[EventType.CLICKONLY]
) {
// A 'click' triggered by a DOM click should be mapped to the 'click'
// jsaction, if available, or else fallback to the 'clickonly' jsaction.
// If 'click' and 'clickonly' jsactions are used together, 'click' will
// prevail.
eventInfoLib.setEventType(eventInfo, EventType.CLICKONLY);
eventInfoLib.setAction(eventInfo, actionMap[EventType.CLICKONLY]);
}
}
44 changes: 44 additions & 0 deletions packages/core/primitives/event-dispatch/src/accessibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/**
* Defines special EventInfo and Event properties used when
* A11Y_SUPPORT_IN_DISPATCHER is enabled.
*/
export enum Attribute {
/**
* An event-type set when the event contract detects a KEYDOWN event but
* doesn't know if the key press can be treated like a click. The dispatcher
* will use this event-type to parse the keypress and handle it accordingly.
*/
MAYBE_CLICK_EVENT_TYPE = 'maybe_click',

/**
* A property added to a dispatched event that had the MAYBE_CLICK_EVENTTYPE
* event-type but could not be used as a click. The dispatcher sets this
* property for non-global dispatches before it retriggers the event and it
* signifies that the event contract should not dispatch this event globally.
*/
SKIP_GLOBAL_DISPATCH = 'a11ysgd',

/**
* A property added to a dispatched event that had the MAYBE_CLICK_EVENTTYPE
* event-type but could not be used as a click. The dispatcher sets this
* property before it retriggers the event and it signifies that the event
* contract should not look at CLICK actions for KEYDOWN events.
*/
SKIP_A11Y_CHECK = 'a11ysc',
}

declare global {
interface Event {
[Attribute.MAYBE_CLICK_EVENT_TYPE]?: boolean;
[Attribute.SKIP_GLOBAL_DISPATCH]?: boolean;
[Attribute.SKIP_A11Y_CHECK]?: boolean;
}
}
77 changes: 77 additions & 0 deletions packages/core/primitives/event-dispatch/src/attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

export enum Attribute {
/**
* The jsaction attribute defines a mapping of a DOM event to a
* generic event (aka jsaction), to which the actual event handlers
* that implement the behavior of the application are bound. The
* value is a semicolon separated list of colon separated pairs of
* an optional DOM event name and a jsaction name. If the optional
* DOM event name is omitted, 'click' is assumed. The jsaction names
* are dot separated pairs of a namespace and a simple jsaction
* name. If the namespace is absent, it is taken from the closest
* ancestor element with a jsnamespace attribute, if there is
* any. If there is no ancestor with a jsnamespace attribute, the
* simple name is assumed to be the jsaction name.
*
* Used by EventContract.
*/
JSACTION = 'jsaction',

/**
* The jsnamespace attribute provides the namespace part of the
* jaction names occurring in the jsaction attribute where it's
* missing.
*
* Used by EventContract.
*/
JSNAMESPACE = 'jsnamespace',

/**
* The oi attribute is a log impression tag for impression logging
* and action tracking. For an element that carries a jsaction
* attribute, the element is identified for the purpose of
* impression logging and click tracking by the dot separated path
* of all oi attributes in the chain of ancestors of the element.
*
* Used by ActionFlow.
*/
OI = 'oi',

/**
* The ved attribute is an encoded ClickTrackingCGI proto to track
* visual elements.
*
* Used by ActionFlow.
*/
VED = 'ved',

/**
* The vet attribute is the visual element type used to identify tracked
* visual elements.
*/
VET = 'vet',

/**
* Support for iteration on reprocessing.
*
* Used by ActionFlow.
*/
JSINSTANCE = 'jsinstance',

/**
* All click jsactions that happen on the element that carries this
* attribute or its descendants are automatically logged.
* Impressions of jsactions on these elements are tracked too, if
* requested by the impression() method of ActionFlow.
*
* Used by ActionFlow.
*/
JSTRACK = 'jstrack'
}
104 changes: 104 additions & 0 deletions packages/core/primitives/event-dispatch/src/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

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}} = {};

/**
* 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
return element[Property.JSACTION];
}

/**
* 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
element[Property.JSACTION] = actionMap;
}

/**
* Looks up the parsed action map from the source jsaction attribute value.
*
* @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 {
return parseCache[text];
}

/**
* Inserts the parse result for the given source jsaction value into the cache.
*
* @param text Unparsed jsaction attribute value.
* @param parsed Attribute value parsed into the action map.
*/
export function setParsed(text: string, parsed: {[key: string]: string}) {
parseCache[text] = parsed;
}

/**
* Clears the jsaction parser cache from the given DOM Element.
*
* @param element .
*/
export function clear(element: Element) {
if (Property.JSACTION in element) {
delete element[Property.JSACTION];
}
}

/**
* Reads the cached jsaction namespace from the given DOM
* Element. Undefined means there is no cached value; null is a cached
* jsnamespace attribute that's absent.
*
* @param element .
* @return .
*/
export function getNamespace(element: Element): string | null | undefined {
// @ts-ignore
return element[Property.JSNAMESPACE];
}

/**
* Writes the cached jsaction namespace to the given DOM Element. Null
* represents a jsnamespace attribute that's absent.
*
* @param element .
* @param jsnamespace .
*/
export function setNamespace(element: Element, jsnamespace: string | null) {
// @ts-ignore
element[Property.JSNAMESPACE] = jsnamespace;
}

/**
* Clears the cached jsaction namespace from the given DOM Element.
*
* @param element .
*/
export function clearNamespace(element: Element) {
if (Property.JSNAMESPACE in element) {
delete element[Property.JSNAMESPACE];
}
}
39 changes: 39 additions & 0 deletions packages/core/primitives/event-dispatch/src/char.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

export const Char = {
/**
* The separator between the namespace and the action name in the
* jsaction attribute value.
*/
NAMESPACE_ACTION_SEPARATOR: '.',

/**
* The separator between the event name and action in the jsaction
* attribute value.
*/
EVENT_ACTION_SEPARATOR: ':',

/**
* The separator between the logged oi attribute values in the &oi=
* URL parameter value.
*/
OI_SEPARATOR: '.',

/**
* The separator between the key and the value pairs in the &cad=
* URL parameter value.
*/
CAD_KEY_VALUE_SEPARATOR: ':',

/**
* The separator between the key-value pairs in the &cad= URL
* parameter value.
*/
CAD_SEPARATOR: ',',
};
Loading

0 comments on commit e0a12d7

Please sign in to comment.