Skip to content

Commit ad9ffc1

Browse files
authored
Merge pull request uswds#6489 from uswds/replace-receptor
Replace receptor
2 parents cdd9212 + 5715021 commit ad9ffc1

File tree

7 files changed

+257
-75
lines changed

7 files changed

+257
-75
lines changed

package-lock.json

Lines changed: 2 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@
120120
},
121121
"homepage": "https://github.com/uswds/uswds#readme",
122122
"dependencies": {
123-
"lit": "^3.2.1",
124-
"receptor": "1.0.0"
123+
"lit": "^3.2.1"
125124
},
126125
"devDependencies": {
127126
"@18f/identity-stylelint-config": "4.1.0",

packages/usa-in-page-navigation/src/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const once = require("receptor/once");
21
const keymap = require("../../uswds-core/src/js/utils/keymap");
32
const selectOrMatches = require("../../uswds-core/src/js/utils/select-or-matches");
43
const behavior = require("../../uswds-core/src/js/utils/behavior");
@@ -391,9 +390,10 @@ const handleEnterFromLink = (event) => {
391390
target.focus();
392391
target.addEventListener(
393392
"blur",
394-
once(() => {
393+
() => {
395394
target.setAttribute("tabindex", -1);
396-
}),
395+
},
396+
{ once: true },
397397
);
398398
} else {
399399
// throw an error?

packages/usa-search/src/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const ignore = require("receptor/ignore");
21
const behavior = require("../../uswds-core/src/js/utils/behavior");
32
const select = require("../../uswds-core/src/js/utils/select");
43

@@ -39,13 +38,17 @@ const toggleSearch = (button, active) => {
3938
}
4039
// when the user clicks _outside_ of the form w/ignore(): hide the
4140
// search, then remove the listener
42-
const listener = ignore(form, () => {
41+
const listener = (event) => {
42+
if (form.contains(event.target)) {
43+
return;
44+
}
45+
4346
if (lastButton) {
4447
hideSearch.call(lastButton); // eslint-disable-line no-use-before-define
4548
}
4649

4750
document.body.removeEventListener(CLICK, listener);
48-
});
51+
};
4952

5053
// Normally we would just run this code without a timeout, but
5154
// IE11 and Edge will actually call the listener *immediately* because

packages/usa-skipnav/src/index.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const once = require("receptor/once");
21
const behavior = require("../../uswds-core/src/js/utils/behavior");
32
const { CLICK } = require("../../uswds-core/src/js/events");
43
const { prefix: PREFIX } = require("../../uswds-core/src/js/config");
@@ -18,12 +17,9 @@ function setTabindex() {
1817
target.style.outline = "0";
1918
target.setAttribute("tabindex", 0);
2019
target.focus();
21-
target.addEventListener(
22-
"blur",
23-
once(() => {
24-
target.setAttribute("tabindex", -1);
25-
}),
26-
);
20+
target.addEventListener("blur", () => target.setAttribute("tabindex", -1), {
21+
once: true,
22+
});
2723
} else {
2824
// throw an error?
2925
}
Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,117 @@
1-
const Behavior = require("receptor/behavior");
1+
/**
2+
* Callback handler for component setup and teardown events.
3+
*
4+
* @typedef {(target?: HTMLElement) => any} BehaviorLifecycle
5+
*/
26

37
/**
4-
* @name sequence
5-
* @param {...Function} seq an array of functions
6-
* @return { closure } callHooks
8+
* Options object for initializing a component's behavior and including additional properties in
9+
* the public interface of each initialized component.
10+
*
11+
* @typedef {Record<string, any> & { init?: BehaviorLifecycle, teardown?: BehaviorLifecycle }} BehaviorProps
12+
*/
13+
14+
/**
15+
* Component initializer.
16+
*
17+
* @typedef {BehaviorProps & {
18+
* on: BehaviorLifecycle,
19+
* off: BehaviorLifecycle,
20+
* add: BehaviorLifecycle,
21+
* remove: BehaviorLifecycle,
22+
* }} Behavior
23+
*/
24+
25+
/**
26+
* Event callback handler.
27+
*
28+
* @typedef {(
29+
* this: HTMLElement,
30+
* event: K extends keyof HTMLElementEventMap ? HTMLElementEventMap[K] : Event
31+
* ) => any} EventHandler
32+
* @template {string} K Event name(s)
33+
*/
34+
35+
/**
36+
* Callback or object of selector-scoped callbacks.
37+
*
38+
* @typedef {EventHandler<K> | Record<string, EventHandler<K>>} EventHandlerOrSelectorMap
39+
* @template {string} K Event name(s)
740
*/
8-
// We use a named function here because we want it to inherit its lexical scope
9-
// from the behavior props object, not from the module
10-
const sequence = (...seq) =>
11-
function callHooks(target = document.body) {
12-
seq.forEach((method) => {
13-
if (typeof this[method] === "function") {
14-
this[method].call(this, target);
15-
}
16-
});
17-
};
1841

1942
/**
20-
* @name behavior
21-
* @param {object} events
22-
* @param {object?} props
23-
* @return {receptor.behavior}
43+
* Object of component event handlers, where each event handler may be a callback function or an
44+
* object of selector-scoped callbacks.
45+
*
46+
* @typedef {Record<K, EventHandlerOrSelectorMap<K>>} Events
47+
* @template {string} K Event name(s)
2448
*/
25-
module.exports = (events, props) =>
26-
Behavior(events, {
27-
on: sequence("init", "add"),
28-
off: sequence("teardown", "remove"),
29-
...props,
30-
});
49+
50+
/**
51+
* @param {Events<K>} events Object of component event handlers, where each event handler may be a
52+
* callback function or an object of selector-scoped callbacks.
53+
* @param {Partial<BehaviorProps>} props Additional properties to include in public interface of
54+
* each initialized component.
55+
* @template {string} K Event name(s)
56+
*
57+
* @return {Behavior} Component initializer.
58+
*/
59+
60+
module.exports = (events, props) => {
61+
// Normalize event handlers to an array of arguments to be passed to either `addEventListener` or
62+
// `removeEventListener` during component initialization.
63+
const listeners = Object.entries(events).flatMap(([eventTypes, handlers]) =>
64+
// Each event handler can be defined for one or more space-separated event names.
65+
eventTypes.split(" ").map(
66+
(eventType) =>
67+
// Generate arguments of `[add/remove]EventListener` as [eventType, callback]
68+
/** @type {[keyof HTMLElementEventMap, EventHandler<any>]} */ ([
69+
eventType,
70+
// Event handlers can be defined as a callback or object of selector-scoped callbacks. To
71+
// normalize as a function, create a function from a given object to perform the scoping
72+
// logic.
73+
typeof handlers === "function"
74+
? handlers
75+
: (event) =>
76+
// When a handler is defined as an object, event handling should terminate if any of
77+
// the scoped callbacks return `false`. This is accomplished by iterating over the
78+
// object's values as an array and using `Array#some` to abort at the first `false`
79+
// return value.
80+
Object.entries(handlers).some(([selector, handler]) => {
81+
// Since this event is attached at an ancestor and is handled using event
82+
// delegation, ensure that the actual event target is within the scoped selector.
83+
const target = event.target && event.target.closest(selector);
84+
return target && handler.call(target, event) === false;
85+
}),
86+
]),
87+
),
88+
);
89+
90+
/**
91+
* Initialize components within the given target, defaulting to the document body.
92+
*
93+
* @param {Element} target Ancestor element in which to initialized components.
94+
*/
95+
const on = (target = document.body) => {
96+
if (props && props.init) {
97+
props.init(target);
98+
}
99+
100+
listeners.forEach((args) => target.addEventListener(...args));
101+
};
102+
103+
/**
104+
* Remove component behaviors within the given target, defaulting to the document body.
105+
*
106+
* @param {Element} target Ancestor element in which to remove component behaviors.
107+
*/
108+
const off = (target = document.body) => {
109+
if (props && props.teardown) {
110+
props.teardown(target);
111+
}
112+
113+
listeners.forEach((args) => target.removeEventListener(...args));
114+
};
115+
116+
return { on, add: on, off, remove: off, ...props };
117+
};

0 commit comments

Comments
 (0)