Skip to content

Commit

Permalink
Move base to options (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
fregante authored Apr 25, 2023
1 parent 7b3dc8a commit 247ac87
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 38 deletions.
38 changes: 19 additions & 19 deletions delegate.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import {test, vi, expect} from 'vitest';
import {container, anchor} from './vitest.setup.js';
import {base, anchor} from './vitest.setup.js';
import delegate from './delegate.js';

test('should add an event listener', () => {
const spy = vi.fn();
delegate(container, 'a', 'click', spy);
delegate('a', 'click', spy);
anchor.click();
expect(spy).toHaveBeenCalledTimes(1);
});

test('should handle events on text nodes', () => {
const spy = vi.fn();
delegate(container, 'a', 'click', spy);
delegate('a', 'click', spy);
anchor.firstChild!.dispatchEvent(new MouseEvent('click', {bubbles: true}));
expect(spy).toHaveBeenCalledTimes(1);
});

test('should remove an event listener', () => {
const spy = vi.fn();
const controller = new AbortController();
delegate(container, 'a', 'click', spy, {signal: controller.signal});
delegate('a', 'click', spy, {signal: controller.signal});
controller.abort();

anchor.click();
Expand All @@ -28,33 +28,33 @@ test('should remove an event listener', () => {

test('should not add an event listener of the controller has already aborted', () => {
const spy = vi.fn();
delegate(container, 'a', 'click', spy, {signal: AbortSignal.abort()});
delegate('a', 'click', spy, {signal: AbortSignal.abort()});

anchor.click();
expect(spy).toHaveBeenCalledTimes(0);
});

test('should not fire when the selector matches an ancestor of the base element', () => {
const spy = vi.fn();
delegate(container, 'body', 'click', spy);
delegate('body', 'click', spy, {base});

anchor.click();
expect(spy).toHaveBeenCalledTimes(0);
});

test('should not add an event listener when passed an already aborted signal', () => {
const spy = vi.spyOn(container, 'addEventListener');
delegate(container, 'a', 'click', () => ({}), {signal: AbortSignal.abort()});
const spy = vi.spyOn(base, 'addEventListener');
delegate('a', 'click', () => ({}), {base, signal: AbortSignal.abort()});

anchor.click();
expect(spy).toHaveBeenCalledTimes(0);
});

test('should call the listener once with the `once` option', () => {
const spy = vi.fn();
delegate(container, 'a', 'click', spy, {once: true});
delegate('a', 'click', spy, {base, once: true});

container.click();
base.click();
expect(spy).toHaveBeenCalledTimes(0); // It should not be called on the container
anchor.click();
expect(spy).toHaveBeenCalledTimes(1); // It should be called on the delegate target
Expand All @@ -69,27 +69,27 @@ test('should add a specific event listener only once', () => {
// https://github.com/fregante/delegate-it/pull/11#discussion_r285481625

// Capture: false
delegate(container, 'a', 'click', spy);
delegate(container, 'a', 'click', spy, {passive: true});
delegate(container, 'a', 'click', spy, {capture: false});
delegate('a', 'click', spy);
delegate('a', 'click', spy, {passive: true});
delegate('a', 'click', spy, {capture: false});

// Capture: true
delegate(container, 'a', 'click', spy, {capture: true});
delegate('a', 'click', spy, {capture: true});

// Once
delegate(container, 'a', 'click', spy, {once: true});
delegate(container, 'a', 'click', spy, {once: false});
delegate('a', 'click', spy, {once: true});
delegate('a', 'click', spy, {once: false});

anchor.click();
expect(spy).toHaveBeenCalledTimes(2);
});

test('should deduplicate identical listeners added after `once:true`', () => {
const spy = vi.fn();
delegate(container, 'a', 'click', spy, {once: true});
delegate(container, 'a', 'click', spy, {once: false});
delegate('a', 'click', spy, {once: true});
delegate('a', 'click', spy, {once: false});

container.click();
base.click();
expect(spy).toHaveBeenCalledTimes(0); // It should not be called on the container
anchor.click();
expect(spy).toHaveBeenCalledTimes(1); // It should be called on the delegate target
Expand Down
7 changes: 2 additions & 5 deletions delegate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {ParseSelector} from 'typed-query-selector/parser.d.js';

export type DelegateOptions = AddEventListenerOptions;
export type DelegateOptions = AddEventListenerOptions & {base?: EventTarget};
export type EventType = keyof GlobalEventHandlersEventMap;

export type DelegateEventHandler<
Expand Down Expand Up @@ -73,7 +73,6 @@ function delegate<
TElement extends Element = ParseSelector<Selector, HTMLElement>,
TEventType extends EventType = EventType,
>(
base: EventTarget,
selector: Selector,
type: TEventType,
callback: DelegateEventHandler<GlobalEventHandlersEventMap[TEventType], TElement>,
Expand All @@ -84,7 +83,6 @@ function delegate<
TElement extends Element = HTMLElement,
TEventType extends EventType = EventType,
>(
base: EventTarget,
selector: string,
type: TEventType,
callback: DelegateEventHandler<GlobalEventHandlersEventMap[TEventType], TElement>,
Expand All @@ -96,13 +94,12 @@ function delegate<
TElement extends Element,
TEventType extends EventType = EventType,
>(
base: EventTarget,
selector: string,
type: TEventType,
callback: DelegateEventHandler<GlobalEventHandlersEventMap[TEventType], TElement>,
options: DelegateOptions = {},
): void {
const {signal} = options;
const {signal, base = document} = options;

if (signal?.aborted) {
return;
Expand Down
8 changes: 4 additions & 4 deletions one-event.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import {test, expect} from 'vitest';
import {container, anchor} from './vitest.setup.js';
import {anchor} from './vitest.setup.js';
import oneEvent from './one-event.js';

test('should resolve after one event', async t => {
const promise = oneEvent(container, 'a', 'click');
const promise = oneEvent('a', 'click');
anchor.click();
const event = await promise;
expect(event).toBeInstanceOf(MouseEvent);
});

test('should resolve with `undefined` after it’s aborted', async t => {
const controller = new AbortController();
const promise = oneEvent(container, 'a', 'click', {signal: controller.signal});
const promise = oneEvent('a', 'click', {signal: controller.signal});
controller.abort();

const event = await promise;
expect(event).toBeUndefined();
});

test('should resolve with `undefined` if the signal has already aborted', async t => {
const promise = oneEvent(container, 'a', 'click', {signal: AbortSignal.abort()});
const promise = oneEvent('a', 'click', {signal: AbortSignal.abort()});
const event = await promise;
expect(event).toBeUndefined();
});
4 changes: 0 additions & 4 deletions one-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ async function oneEvent<
TElement extends Element = ParseSelector<Selector, HTMLElement>,
TEventType extends EventType = EventType,
>(
base: EventTarget,
selector: Selector,
type: TEventType,
options?: DelegateOptions
Expand All @@ -23,7 +22,6 @@ async function oneEvent<
TElement extends Element = HTMLElement,
TEventType extends EventType = EventType,
>(
base: EventTarget,
selector: string,
type: TEventType,
options?: DelegateOptions
Expand All @@ -34,7 +32,6 @@ async function oneEvent<
TElement extends Element,
TEventType extends EventType = EventType,
>(
base: EventTarget,
selector: string,
type: TEventType,
options: DelegateOptions = {},
Expand All @@ -51,7 +48,6 @@ async function oneEvent<
});

delegate(
base,
selector,
type,
// @ts-expect-error Seems to work fine
Expand Down
22 changes: 17 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,38 @@ import delegate from 'delegate-it';
### Add event delegation

```js
delegate(document, '.btn', 'click', event => {
delegate('.btn', 'click', event => {
console.log(event.delegateTarget);
});
```

### With listener options

```js
delegate(document, '.btn', 'click', event => {
delegate('.btn', 'click', event => {
console.log(event.delegateTarget);
}, {
capture: true
});
```

### On a custom base

Use this option if you don't want to have a global listener attached on `html`, it improves performance:

```js
delegate('.btn', 'click', event => {
console.log(event.delegateTarget);
}, {
base: document.querySelector('main')
});
```

### Remove event delegation

```js
const controller = new AbortController();
delegate(document, '.btn', 'click', event => {
delegate('.btn', 'click', event => {
console.log(event.delegateTarget);
}, {
signal: controller.signal,
Expand All @@ -61,7 +73,7 @@ controller.abort();
### Listen to one event only

```js
delegate(document, '.btn', 'click', event => {
delegate('.btn', 'click', event => {
console.log('This will only be called once');
}, {
once: true
Expand All @@ -73,7 +85,7 @@ delegate(document, '.btn', 'click', event => {
```js
import {oneEvent} from 'delegate-it';

await oneEvent(document, '.btn', 'click');
await oneEvent('.btn', 'click');
console.log('The body was clicked');
```

Expand Down
2 changes: 1 addition & 1 deletion vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ global.Document = window.Document;
global.MouseEvent = window.MouseEvent;
global.AbortController = window.AbortController;
global.document = window.document;
export const container = window.document.querySelector('ul')!;
export const base = window.document.querySelector('ul')!;
export const anchor = window.document.querySelector('a')!;

0 comments on commit 247ac87

Please sign in to comment.