@bodil/dom 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/component.d.ts +165 -0
  2. package/dist/component.js +435 -0
  3. package/dist/component.js.map +1 -0
  4. package/dist/component.test.d.ts +1 -0
  5. package/dist/component.test.js +210 -0
  6. package/dist/component.test.js.map +1 -0
  7. package/dist/css.d.ts +14 -0
  8. package/dist/css.js +63 -0
  9. package/dist/css.js.map +1 -0
  10. package/dist/decorators/attribute.d.ts +48 -0
  11. package/dist/decorators/attribute.js +141 -0
  12. package/dist/decorators/attribute.js.map +1 -0
  13. package/dist/decorators/attribute.test.d.ts +1 -0
  14. package/dist/decorators/attribute.test.js +369 -0
  15. package/dist/decorators/attribute.test.js.map +1 -0
  16. package/dist/decorators/connect.d.ts +9 -0
  17. package/dist/decorators/connect.js +44 -0
  18. package/dist/decorators/connect.js.map +1 -0
  19. package/dist/decorators/connect.test.d.ts +1 -0
  20. package/dist/decorators/connect.test.js +196 -0
  21. package/dist/decorators/connect.test.js.map +1 -0
  22. package/dist/decorators/reactive.d.ts +8 -0
  23. package/dist/decorators/reactive.js +45 -0
  24. package/dist/decorators/reactive.js.map +1 -0
  25. package/dist/decorators/reactive.test.d.ts +1 -0
  26. package/dist/decorators/reactive.test.js +113 -0
  27. package/dist/decorators/reactive.test.js.map +1 -0
  28. package/dist/decorators/require.d.ts +3 -0
  29. package/dist/decorators/require.js +6 -0
  30. package/dist/decorators/require.js.map +1 -0
  31. package/dist/decorators/require.test.d.ts +1 -0
  32. package/dist/decorators/require.test.js +117 -0
  33. package/dist/decorators/require.test.js.map +1 -0
  34. package/dist/decorators/types.d.ts +7 -0
  35. package/dist/decorators/types.js +2 -0
  36. package/dist/decorators/types.js.map +1 -0
  37. package/dist/dom.d.ts +92 -0
  38. package/dist/dom.js +354 -0
  39. package/dist/dom.js.map +1 -0
  40. package/dist/emitter.d.ts +83 -0
  41. package/dist/emitter.js +57 -0
  42. package/dist/emitter.js.map +1 -0
  43. package/dist/event.d.ts +70 -0
  44. package/dist/event.js +14 -0
  45. package/dist/event.js.map +1 -0
  46. package/dist/event.test.d.ts +1 -0
  47. package/dist/event.test.js +20 -0
  48. package/dist/event.test.js.map +1 -0
  49. package/dist/geometry.d.ts +48 -0
  50. package/dist/geometry.js +117 -0
  51. package/dist/geometry.js.map +1 -0
  52. package/dist/index.d.ts +10 -0
  53. package/dist/index.js +11 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/scheduler.d.ts +2 -0
  56. package/dist/scheduler.js +54 -0
  57. package/dist/scheduler.js.map +1 -0
  58. package/dist/slot.d.ts +32 -0
  59. package/dist/slot.js +70 -0
  60. package/dist/slot.js.map +1 -0
  61. package/dist/test-global.d.ts +0 -0
  62. package/dist/test-global.js +5 -0
  63. package/dist/test-global.js.map +1 -0
  64. package/package.json +2 -2
package/dist/dom.js ADDED
@@ -0,0 +1,354 @@
1
+ import { assertNever, isNullish, unreachable } from "@bodil/core/assert";
2
+ import { toDisposable } from "@bodil/core/disposable";
3
+ import { contains } from "./geometry";
4
+ /**
5
+ * Given the name of a custom DOM element registered in
6
+ * {@link Window.customElements}, test whether an object is an instance of that
7
+ * element.
8
+ *
9
+ * The element must also be declared in {@link HTMLElementTagNameMap} for the
10
+ * type checker to follow along.
11
+ */
12
+ export function isElement(name, item) {
13
+ const constructor = customElements.get(name);
14
+ return constructor === undefined ? false : item instanceof constructor;
15
+ }
16
+ /**
17
+ * Call {@link window.requestAnimationFrame} with the provided callback and return a
18
+ * {@link Disposable} which will cancel the request when disposed.
19
+ */
20
+ export function animationFrame(fn) {
21
+ // @ts-ignore requestAnimationFrame is only available in browser contexts
22
+ let id = globalThis.requestAnimationFrame((time) => {
23
+ id = null;
24
+ fn(time);
25
+ disposable[Symbol.dispose]();
26
+ });
27
+ const disposable = toDisposable(() => {
28
+ if (id !== null) {
29
+ // @ts-ignore cancelAnimationFrame is only available in browser contexts
30
+ globalThis.cancelAnimationFrame(id);
31
+ }
32
+ });
33
+ return disposable;
34
+ }
35
+ /**
36
+ * Return a promise which will resolve when the next animation frame happens.
37
+ */
38
+ export function awaitFrame() {
39
+ return new Promise((resolve) => animationFrame(() => resolve()));
40
+ }
41
+ export function intersectionObserver(element, callback, options) {
42
+ let observer = new IntersectionObserver(callback, options);
43
+ observer.observe(element);
44
+ return toDisposable(() => {
45
+ observer?.disconnect();
46
+ observer = undefined;
47
+ });
48
+ }
49
+ export function resizeObserver(element, callback, options) {
50
+ let observer = new ResizeObserver(callback);
51
+ observer.observe(element, options);
52
+ return toDisposable(() => {
53
+ observer?.disconnect();
54
+ observer = undefined;
55
+ });
56
+ }
57
+ export function visibilityObserver(element, callback) {
58
+ let oldState = false;
59
+ return intersectionObserver(element, (entries) => {
60
+ const newState = entries[0].isIntersecting;
61
+ if (oldState !== newState) {
62
+ callback(oldState, newState);
63
+ }
64
+ oldState = newState;
65
+ });
66
+ }
67
+ export class DOMIterator extends Iterator {
68
+ constructor(startNode) {
69
+ super();
70
+ this.goingForward = true;
71
+ this.currentNode = startNode;
72
+ }
73
+ reverse() {
74
+ this.goingForward = !this.goingForward;
75
+ return this;
76
+ }
77
+ skip() {
78
+ this.next();
79
+ return this;
80
+ }
81
+ next() {
82
+ if (this.currentNode === null) {
83
+ return { done: true, value: undefined };
84
+ }
85
+ const resultNode = this.currentNode;
86
+ this.currentNode = this.goingForward
87
+ ? this.currentNode.nextSibling
88
+ : this.currentNode.previousSibling;
89
+ return { done: false, value: resultNode };
90
+ }
91
+ }
92
+ export function childElements(parent) {
93
+ if (parent === null) {
94
+ return Iterator.from([]);
95
+ }
96
+ const iter = function* () {
97
+ let el = parent.firstElementChild;
98
+ while (el !== null) {
99
+ yield el;
100
+ el = el.nextElementSibling;
101
+ }
102
+ };
103
+ return Iterator.from(iter());
104
+ }
105
+ /** Test whether the user has asked for reduced motion. */
106
+ export function prefersReducedMotion() {
107
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
108
+ }
109
+ /**
110
+ * Animates an element using keyframes. Returns a promise that resolves after
111
+ * the animation completes or gets cancelled.
112
+ */
113
+ export function animateTo(el, keyframes, options) {
114
+ return new Promise((resolve) => {
115
+ if (options?.duration === Infinity) {
116
+ throw new Error("Promise-based animations must be finite.");
117
+ }
118
+ const animation = el.animate(keyframes, {
119
+ ...options,
120
+ duration: prefersReducedMotion() ? 0 : options.duration,
121
+ });
122
+ animation.addEventListener("cancel", () => resolve(), { once: true });
123
+ animation.addEventListener("finish", () => resolve(), { once: true });
124
+ });
125
+ }
126
+ /**
127
+ * Stops all active animations on the target element. Returns a promise that
128
+ * resolves after all animations are cancelled.
129
+ */
130
+ export async function stopAnimations(el) {
131
+ await Promise.all(el.getAnimations().map((animation) => {
132
+ return new Promise((resolve) => {
133
+ const handleAnimationEvent = () => requestAnimationFrame(resolve);
134
+ animation.addEventListener("cancel", () => handleAnimationEvent(), { once: true });
135
+ animation.addEventListener("finish", () => handleAnimationEvent(), { once: true });
136
+ animation.cancel();
137
+ });
138
+ }));
139
+ }
140
+ /**
141
+ * Wait for any animations running on the element to complete.
142
+ */
143
+ export async function animationEnd(el) {
144
+ await Promise.all(el.getAnimations().map((animation) => {
145
+ return new Promise((resolve) => {
146
+ const handleAnimationEvent = () => requestAnimationFrame(resolve);
147
+ animation.addEventListener("cancel", () => handleAnimationEvent(), { once: true });
148
+ animation.addEventListener("finish", () => handleAnimationEvent(), { once: true });
149
+ });
150
+ }));
151
+ }
152
+ /**
153
+ * Check if any animations are currently active on an element.
154
+ */
155
+ export function isAnimating(el) {
156
+ function findParent(el) {
157
+ if (!isNullish(el.parentElement)) {
158
+ return el.parentElement;
159
+ }
160
+ if (el.parentNode instanceof ShadowRoot && el.parentNode.host instanceof HTMLElement) {
161
+ return el.parentNode.host;
162
+ }
163
+ return undefined;
164
+ }
165
+ const path = [el];
166
+ let current = el;
167
+ while ((current = findParent(current))) {
168
+ path.push(current);
169
+ }
170
+ return path.some((item) => item.getAnimations().length > 0);
171
+ }
172
+ export function findEventTarget(e, predicate) {
173
+ const path = e.composedPath();
174
+ while (true) {
175
+ const target = path.pop();
176
+ if (target === undefined) {
177
+ return null;
178
+ }
179
+ if (predicate(target)) {
180
+ return target;
181
+ }
182
+ }
183
+ }
184
+ /**
185
+ * Dig through shadow roots to find the actual element that currently has focus.
186
+ * If nothing has focus in or below the given root, return undefined.
187
+ */
188
+ export function findActiveElement(root = document) {
189
+ return activeElementPath(root)?.pop();
190
+ }
191
+ /**
192
+ * As {@link findActiveElement}, but returns a list of the active elements as
193
+ * seen by each {@link Document} and {@link ShadowRoot} down to the actually
194
+ * active element.
195
+ */
196
+ export function activeElementPath(root = document) {
197
+ if (root === null) {
198
+ return undefined;
199
+ }
200
+ const path = [];
201
+ let currentRoot = root;
202
+ while (true) {
203
+ const element = currentRoot.activeElement;
204
+ if (element === null) {
205
+ return undefined;
206
+ }
207
+ path.push(element);
208
+ if (element.shadowRoot !== null && element.shadowRoot.activeElement !== null) {
209
+ currentRoot = element.shadowRoot;
210
+ }
211
+ else {
212
+ return path;
213
+ }
214
+ }
215
+ }
216
+ export function findAncestor(child, predicate) {
217
+ if (isNullish(child)) {
218
+ return undefined;
219
+ }
220
+ function nextParent(child) {
221
+ const node = child.parentNode;
222
+ if (node instanceof Element) {
223
+ return node;
224
+ }
225
+ if (node instanceof ShadowRoot) {
226
+ return node.host;
227
+ }
228
+ return undefined;
229
+ }
230
+ let parent = nextParent(child);
231
+ while (parent !== undefined) {
232
+ if (predicate(parent)) {
233
+ return parent;
234
+ }
235
+ parent = nextParent(parent);
236
+ }
237
+ return undefined;
238
+ }
239
+ export function isEditor(element) {
240
+ if (isNullish(element)) {
241
+ return false;
242
+ }
243
+ return (element instanceof HTMLInputElement ||
244
+ element instanceof HTMLTextAreaElement ||
245
+ element.getAttribute("contenteditable") === "true");
246
+ }
247
+ const scrollToItemOptionsDefault = {
248
+ behavior: "auto",
249
+ padStart: 0,
250
+ padEnd: 0,
251
+ };
252
+ export function scrollToItem(el, scrollToItemOptions) {
253
+ const options = {
254
+ ...scrollToItemOptionsDefault,
255
+ ...scrollToItemOptions,
256
+ };
257
+ if (options.behavior === "auto") {
258
+ options.behavior = prefersReducedMotion() ? "instant" : "smooth";
259
+ }
260
+ const horiz = options.axis === "horizontal";
261
+ const getTop = horiz ? (rect) => rect.left : (rect) => rect.top;
262
+ const getBottom = horiz ? (rect) => rect.right : (rect) => rect.bottom;
263
+ const getHeight = horiz ? (rect) => rect.width : (rect) => rect.height;
264
+ const viewRect = options.viewport.getBoundingClientRect();
265
+ const childRect = el.getBoundingClientRect();
266
+ const childTop = getTop(childRect) - getTop(viewRect);
267
+ const align = options.align;
268
+ let top = 0;
269
+ if (align === "start" || getHeight(childRect) > getHeight(viewRect)) {
270
+ top = childTop - options.padStart;
271
+ }
272
+ else if (align === "end") {
273
+ top = childTop + getHeight(childRect) - getHeight(viewRect) + options.padEnd;
274
+ }
275
+ else if (align === "center") {
276
+ top = childTop + getHeight(childRect) / 2 - getHeight(viewRect) / 2;
277
+ }
278
+ else if (align === "fit") {
279
+ if (contains(childRect, viewRect)) {
280
+ return;
281
+ }
282
+ if (getTop(childRect) < getTop(viewRect)) {
283
+ return scrollToItem(el, { ...options, align: "start" });
284
+ }
285
+ if (getBottom(childRect) > getBottom(viewRect)) {
286
+ return scrollToItem(el, { ...options, align: "end" });
287
+ }
288
+ return unreachable();
289
+ }
290
+ else {
291
+ assertNever(align);
292
+ }
293
+ if (horiz) {
294
+ options.viewport.scroll({
295
+ left: top + options.viewport.scrollLeft,
296
+ behavior: options.behavior,
297
+ });
298
+ }
299
+ else {
300
+ options.viewport.scroll({
301
+ top: top + options.viewport.scrollTop,
302
+ behavior: options.behavior,
303
+ });
304
+ }
305
+ }
306
+ // HasSlotController nicked largely verbatim from Shoelace
307
+ // https://github.com/shoelace-style/shoelace/blob/next/src/internal/slot.ts
308
+ export class HasSlotController {
309
+ constructor(host, ...slotNames) {
310
+ this.slotNames = [];
311
+ this.handleSlotChange = (event) => {
312
+ const slot = event.target;
313
+ if ((this.slotNames.includes("[default]") && !slot.name) ||
314
+ (slot.name && this.slotNames.includes(slot.name))) {
315
+ this.host.requestUpdate();
316
+ }
317
+ };
318
+ (this.host = host).addController(this);
319
+ this.slotNames = slotNames;
320
+ }
321
+ hasDefaultSlot() {
322
+ return [...this.host.childNodes].some((node) => {
323
+ if (node.nodeType === node.TEXT_NODE && node.textContent.trim() !== "") {
324
+ return true;
325
+ }
326
+ if (node.nodeType === node.ELEMENT_NODE) {
327
+ const el = node;
328
+ const tagName = el.tagName.toLowerCase();
329
+ // Ignore visually hidden elements since they aren't rendered
330
+ if (tagName === "sl-visually-hidden") {
331
+ return false;
332
+ }
333
+ // If it doesn't have a slot attribute, it's part of the default slot
334
+ if (!el.hasAttribute("slot")) {
335
+ return true;
336
+ }
337
+ }
338
+ return false;
339
+ });
340
+ }
341
+ hasNamedSlot(name) {
342
+ return this.host.querySelector(`:scope > [slot="${name}"]`) !== null;
343
+ }
344
+ test(slotName) {
345
+ return slotName === "[default]" ? this.hasDefaultSlot() : this.hasNamedSlot(slotName);
346
+ }
347
+ hostConnected() {
348
+ this.host.shadowRoot.addEventListener("slotchange", this.handleSlotChange);
349
+ }
350
+ hostDisconnected() {
351
+ this.host.shadowRoot.removeEventListener("slotchange", this.handleSlotChange);
352
+ }
353
+ }
354
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ export type CustomEventTypes<T extends keyof HTMLElementEventMap> = {
2
+ [Key in T]: HTMLElementEventMap[Key] extends CustomEvent<infer D> ? D : never;
3
+ };
4
+ /**
5
+ * Declare the events that can be emitted.
6
+ *
7
+ * @example
8
+ * class MyElement extends EmitterElement {
9
+ * emits!: Emits<"keydown" | "keyup">;
10
+ * }
11
+ */
12
+ export type Emits<A extends keyof HTMLElementEventMap> = {
13
+ [K in A]: never;
14
+ };
15
+ /**
16
+ * Declare the events that can be emitted, including the events listed in the
17
+ * provided superclass.
18
+ *
19
+ * @example
20
+ * class MyElement extends MySuperclassElement {
21
+ * emits!: EmitsSuper<MySuperclassElement, "click">;
22
+ * }
23
+ */
24
+ export type EmitsSuper<C extends EmitterElement, A extends keyof HTMLElementEventMap> = {
25
+ [K in keyof C["emits"] | A]: never;
26
+ };
27
+ /**
28
+ * Given an element type, extract the emitted events for that element.
29
+ *
30
+ * @example
31
+ * class MyElement extends EmitterElement {
32
+ * emits!: Emits<"click" | "change">;
33
+ * }
34
+ * type MyElementEvents = ElementEmits<MyElement>;
35
+ * // resolves to "click" | "change";
36
+ */
37
+ export type ElementEmits<C> = C extends EmitterElement ? keyof C["emits"] : never;
38
+ /**
39
+ * A subclass of {@link HTMLElement} which declares what events can be emitted
40
+ * by it at the type level. Thus, calling `this.emit()` with an undeclared event
41
+ * name will be a type error.
42
+ *
43
+ * It also offers proper type checking of the data provided to
44
+ * {@link EmitterElement.emit} using the types declared in
45
+ * {@link HTMLElementEventMap}.
46
+ *
47
+ * @example
48
+ * declare global {
49
+ * interface HTMLElementEventMap {
50
+ * "my-event": { name: string; count: number; }
51
+ * }
52
+ * }
53
+ *
54
+ * class MyElement extends EmitterElement {
55
+ * emits!: Emits<"my-event">;
56
+ *
57
+ * handleMyEvent() {
58
+ * this.emit("my-event", { name: "Joe", count: 1337 });
59
+ * }
60
+ * }
61
+ */
62
+ export declare class EmitterElement extends HTMLElement {
63
+ emits: {
64
+ [K in keyof HTMLElementEventMap]?: never;
65
+ };
66
+ /**
67
+ * Emit a custom event with the given name and detail.
68
+ *
69
+ * Event init options default to `{ bubbles: true, composed: true }`.
70
+ */
71
+ emit<M extends CustomEventTypes<keyof this["emits"] & keyof HTMLElementEventMap>, K extends Extract<keyof M, string>, D extends M[K]>(name: K, detail?: D, options?: EventInit): CustomEvent<D>;
72
+ /**
73
+ * Construct an event using the provided name and constructor.
74
+ *
75
+ * The constructor will be called with the name as its first argument,
76
+ * followed by any subsequent arguments provided after the constructor.
77
+ *
78
+ * @example
79
+ * this.emitEvent("click", MouseEvent, { button: 2 });
80
+ * // emits: new MouseEvent("click", { button: 2 });
81
+ */
82
+ emitEvent<K extends keyof this["emits"] & keyof HTMLElementEventMap, E extends HTMLElementEventMap[K], C extends new (type: string, ...args: Args) => E, Args extends Array<any>>(name: K, constructor: C, ...args: Args): E;
83
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * A subclass of {@link HTMLElement} which declares what events can be emitted
3
+ * by it at the type level. Thus, calling `this.emit()` with an undeclared event
4
+ * name will be a type error.
5
+ *
6
+ * It also offers proper type checking of the data provided to
7
+ * {@link EmitterElement.emit} using the types declared in
8
+ * {@link HTMLElementEventMap}.
9
+ *
10
+ * @example
11
+ * declare global {
12
+ * interface HTMLElementEventMap {
13
+ * "my-event": { name: string; count: number; }
14
+ * }
15
+ * }
16
+ *
17
+ * class MyElement extends EmitterElement {
18
+ * emits!: Emits<"my-event">;
19
+ *
20
+ * handleMyEvent() {
21
+ * this.emit("my-event", { name: "Joe", count: 1337 });
22
+ * }
23
+ * }
24
+ */
25
+ export class EmitterElement extends HTMLElement {
26
+ /**
27
+ * Emit a custom event with the given name and detail.
28
+ *
29
+ * Event init options default to `{ bubbles: true, composed: true }`.
30
+ */
31
+ emit(name, detail, options) {
32
+ const event = new CustomEvent(name, {
33
+ bubbles: true,
34
+ composed: true,
35
+ detail,
36
+ ...(options ?? {}),
37
+ });
38
+ this.dispatchEvent(event);
39
+ return event;
40
+ }
41
+ /**
42
+ * Construct an event using the provided name and constructor.
43
+ *
44
+ * The constructor will be called with the name as its first argument,
45
+ * followed by any subsequent arguments provided after the constructor.
46
+ *
47
+ * @example
48
+ * this.emitEvent("click", MouseEvent, { button: 2 });
49
+ * // emits: new MouseEvent("click", { button: 2 });
50
+ */
51
+ emitEvent(name, constructor, ...args) {
52
+ const event = new constructor(name, ...args);
53
+ this.dispatchEvent(event);
54
+ return event;
55
+ }
56
+ }
57
+ //# sourceMappingURL=emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.js","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,OAAO,cAAe,SAAQ,WAAW;IAG3C;;;;OAIG;IACH,IAAI,CAIF,IAAO,EAAE,MAAU,EAAE,OAAmB;QACtC,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE;YAChC,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;YACd,MAAM;YACN,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;SACrB,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;;;;;;;;OASG;IACH,SAAS,CAKP,IAAO,EAAE,WAAc,EAAE,GAAG,IAAU;QACpC,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,KAAK,CAAC;IACjB,CAAC;CACJ"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Utility types for DOM events.
3
+ * @module
4
+ */
5
+ import type { Present } from "@bodil/core/types";
6
+ /**
7
+ * An interface a type can extend to associate a map of event names to event
8
+ * types with itself, in the style of `HTMLElementEventMap` et al. This allows
9
+ * the type system to discover the events available for a specific
10
+ * {@link EventTarget} descendant.
11
+ *
12
+ * If you need to implement this for a class you're defining, you need to add
13
+ * `__events!: MyEventMap` as a property on the class.
14
+ */
15
+ export interface DeclareEvents<Events> extends EventTarget {
16
+ __events: Events;
17
+ }
18
+ /**
19
+ * Get the `Events` type associated with a type that extends
20
+ * {@linkcode DeclareEvents}`<Events>`.
21
+ *
22
+ * Using it on a type that doesn't extend {@link DeclareEvents} results in a
23
+ * type error.
24
+ */
25
+ export type EventMapFor<T extends DeclareEvents<unknown>> = Present<T["__events"]>;
26
+ /**
27
+ * Get the `Events` type associated with a type that extends
28
+ * {@linkcode DeclareEvents}`<Events>`.
29
+ *
30
+ * A type that doesn't extend {@link DeclareEvents} resolves to an object which
31
+ * will accept all string keys.
32
+ */
33
+ export type ExtractEventMap<T> = T extends DeclareEvents<infer Events> ? Events : {
34
+ [key: string]: Event;
35
+ };
36
+ declare global {
37
+ interface AbortSignal extends DeclareEvents<AbortSignalEventMap> {
38
+ }
39
+ interface Animation extends DeclareEvents<AnimationEventMap> {
40
+ }
41
+ interface BroadcastChannel extends DeclareEvents<BroadcastChannelEventMap> {
42
+ }
43
+ interface Document extends DeclareEvents<DocumentEventMap> {
44
+ }
45
+ interface EventSource extends DeclareEvents<EventSourceEventMap> {
46
+ }
47
+ interface HTMLBodyElement extends DeclareEvents<HTMLBodyElementEventMap> {
48
+ }
49
+ interface HTMLElement extends DeclareEvents<HTMLElementEventMap> {
50
+ }
51
+ interface MessagePort extends DeclareEvents<MessagePortEventMap> {
52
+ }
53
+ interface ShadowRoot extends DeclareEvents<ShadowRootEventMap> {
54
+ }
55
+ interface SVGElement extends DeclareEvents<SVGElementEventMap> {
56
+ }
57
+ interface SVGSVGElement extends DeclareEvents<SVGSVGElementEventMap> {
58
+ }
59
+ interface WebSocket extends DeclareEvents<WebSocketEventMap> {
60
+ }
61
+ interface Window extends DeclareEvents<WindowEventMap> {
62
+ }
63
+ interface Worker extends DeclareEvents<WorkerEventMap> {
64
+ }
65
+ }
66
+ /**
67
+ * Attach an event listener to an {@link EventTarget} and return a
68
+ * {@link Disposable} which will remove the listener when disposed.
69
+ */
70
+ export declare function eventListener<T extends EventTarget, M extends ExtractEventMap<T>, K extends keyof M & string>(target: T, type: K, callback: (e: M[K]) => any, options?: AddEventListenerOptions | boolean): Disposable;
package/dist/event.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Utility types for DOM events.
3
+ * @module
4
+ */
5
+ import { toDisposable } from "@bodil/core/disposable";
6
+ /**
7
+ * Attach an event listener to an {@link EventTarget} and return a
8
+ * {@link Disposable} which will remove the listener when disposed.
9
+ */
10
+ export function eventListener(target, type, callback, options) {
11
+ target.addEventListener(type, callback, options);
12
+ return toDisposable(() => target.removeEventListener(type, callback));
13
+ }
14
+ //# sourceMappingURL=event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event.js","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAoDtD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAKzB,MAAS,EACT,IAAO,EACP,QAA0B,EAC1B,OAA2C;IAE3C,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,QAAyB,EAAE,OAAO,CAAC,CAAC;IAClE,OAAO,YAAY,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAAyB,CAAC,CAAC,CAAC;AAC3F,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { expect, expectTypeOf, test } from "vitest";
2
+ import { eventListener } from "./event";
3
+ test("eventListener", () => {
4
+ const button = document.createElement("button");
5
+ let clicked = 0;
6
+ const listener = eventListener(button, "click", (e) => {
7
+ expectTypeOf(e).toEqualTypeOf();
8
+ expect(e.target).toBe(button);
9
+ clicked++;
10
+ });
11
+ expect(clicked).toBe(0);
12
+ button.click();
13
+ expect(clicked).toBe(1);
14
+ listener[Symbol.dispose]();
15
+ button.click();
16
+ expect(clicked).toBe(1);
17
+ // @ts-expect-error: unknown event should be a type error
18
+ eventListener(button, "chaos with Ed Miliband", () => { })[Symbol.dispose]();
19
+ });
20
+ //# sourceMappingURL=event.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event.test.js","sourceRoot":"","sources":["../src/event.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE;IACvB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;QAClD,YAAY,CAAC,CAAC,CAAC,CAAC,aAAa,EAAgB,CAAC;QAC9C,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9B,OAAO,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IAC3B,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAExB,yDAAyD;IACzD,aAAa,CAAC,MAAM,EAAE,wBAAwB,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;AAChF,CAAC,CAAC,CAAC"}