Skip to content

Commit

Permalink
Fix onDelegate used with nested targets
Browse files Browse the repository at this point in the history
  • Loading branch information
lou authored Nov 3, 2022
1 parent 7672e97 commit dd36787
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 25 deletions.
33 changes: 15 additions & 18 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe("query utilities", () => {
<div id="el1" class="class1">
<span id="el2" class="class2"></span>
<span id="el3" class="class2"></span>
<span id="el4" class="class2"></span>
<span id="el4" class="class2"><strong></strong></span>
<div id="el5" class="class3">
<div id="el6" class="class4"></div>
<div id="el7" class="class4"></div>
Expand Down Expand Up @@ -212,27 +212,24 @@ describe("query utilities", () => {

describe(".query .onDelegate", () => {
it("should call the callbacks on clicks with the correct child target", () => {
const container = query("#el1");
const container = queryStrict("#el1");

const child1Id = "el2";
const child3Id = "el4";

const child1 = query(`#${child1Id}`);
const child3 = query(`#${child3Id}`);

expect(container).not.toBeNull();
expect(child1).not.toBeNull();
expect(child3).not.toBeNull();

if (container && child1 && child3) {
const mockCallback = jest.fn();
container.onDelegate("span", "click", mockCallback);
child1.click();
child3.click();
expect(mockCallback).toHaveBeenCalledTimes(2);
expect(mockCallback.mock.calls[0][0].target.id).toBe(child1Id);
expect(mockCallback.mock.calls[1][0].target.id).toBe(child3Id);
}
const child1 = queryStrict(`#${child1Id}`);
const child3 = queryStrict(`#${child3Id}`);
const child3Child = child3.queryStrict("strong");

const mockCallback = jest.fn();
container.onDelegate("span", "click", mockCallback);
child1.click();
child3.click();
child3Child.click();
expect(mockCallback).toHaveBeenCalledTimes(3);
expect(mockCallback.mock.calls[0][0].currentTarget.id).toBe(child1Id);
expect(mockCallback.mock.calls[1][0].currentTarget.id).toBe(child3Id);
expect(mockCallback.mock.calls[2][0].currentTarget.id).toBe(child3Id);
});

describe("should have the right even type", () => {
Expand Down
25 changes: 18 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export interface EnhancedHTMLElement {
queryAll: typeof queryAll;
}

const overrideEventCurrentTarget = (event: Event, target: Element) => {
Object.defineProperty(event, "currentTarget", {
configurable: true,
enumerable: true,
get: () => target,
});
};

const enhancedHTMLElementImpl: EnhancedHTMLElement = {
isEnhancedHTMLElement: true,
on(type, callback, options) {
Expand All @@ -38,20 +46,23 @@ const enhancedHTMLElementImpl: EnhancedHTMLElement = {
},
onDelegate(childSelector, type, callback, options) {
const containerListenerCallback = (e: Event) => {
if (e && e.target && (e.target as HTMLElement).matches(childSelector)) {
callback.call(e.target, e as WindowEventMap[typeof type]);
if (options && options.once) {
// to mimic the once configuration of the native option, which is not well supported (IE 11)
this.removeEventListener(type, containerListenerCallback);
}
if (!(e.target instanceof Element)) return;

const target = e.target.closest(childSelector);

if (!this.contains(target)) return;

if (target) {
overrideEventCurrentTarget(e, target);
callback.call(target, e as WindowEventMap[typeof type]);
}
};

const removeListener = () => {
this.removeEventListener(type, containerListenerCallback);
};

this.addEventListener(type, containerListenerCallback);
this.addEventListener(type, containerListenerCallback, options);

return removeListener;
},
Expand Down

0 comments on commit dd36787

Please sign in to comment.