| ... | @@ -24,7 +24,12 @@ function createSelectionSurface({ interactiveActions = false } = {}) { | ... | @@ -24,7 +24,12 @@ function createSelectionSurface({ interactiveActions = false } = {}) { |
| 24 | }); | 24 | }); |
| 25 | const element = Object.assign(new EventTarget(), { | 25 | const element = Object.assign(new EventTarget(), { |
| 26 | ownerDocument: document, | 26 | ownerDocument: document, |
| 27 | contains: (target: unknown): boolean => target === element, | 27 | contains: (target: unknown): boolean => target === element || target === buttonIcon, |
| | 28 | closest: () => null, |
| | 29 | }); |
| | 30 | const button = new EventTarget(); |
| | 31 | const buttonIcon = Object.assign(new EventTarget(), { |
| | 32 | closest: (selector: string) => (selector === "button, [role=button]" ? button : null), |
| 28 | }); | 33 | }); |
| 29 | const onSelection = vi.fn(); | 34 | const onSelection = vi.fn(); |
| 30 | const onDismiss = vi.fn(); | 35 | const onDismiss = vi.fn(); |
| ... | @@ -58,7 +63,7 @@ function createSelectionSurface({ interactiveActions = false } = {}) { | ... | @@ -58,7 +63,7 @@ function createSelectionSurface({ interactiveActions = false } = {}) { |
| 58 | const press = event("pointerdown", { button, isPrimary }); | 63 | const press = event("pointerdown", { button, isPrimary }); |
| 59 | Object.defineProperty(press, "target", { value: target }); | 64 | Object.defineProperty(press, "target", { value: target }); |
| 60 | document.dispatchEvent(press); | 65 | document.dispatchEvent(press); |
| 61 | if (target === element && !consumed) element.dispatchEvent(press); | 66 | if (element.contains(target) && !consumed) element.dispatchEvent(press); |
| 62 | }; | 67 | }; |
| 63 | const focus = (target: EventTarget) => { | 68 | const focus = (target: EventTarget) => { |
| 64 | document.activeElement = target; | 69 | document.activeElement = target; |
| ... | @@ -92,6 +97,7 @@ function createSelectionSurface({ interactiveActions = false } = {}) { | ... | @@ -92,6 +97,7 @@ function createSelectionSurface({ interactiveActions = false } = {}) { |
| 92 | view, | 97 | view, |
| 93 | document, | 98 | document, |
| 94 | element, | 99 | element, |
| | 100 | buttonIcon, |
| 95 | actionElement, | 101 | actionElement, |
| 96 | field, | 102 | field, |
| 97 | onSelection, | 103 | onSelection, |
| ... | @@ -177,6 +183,31 @@ describe("selection action gestures", () => { | ... | @@ -177,6 +183,31 @@ describe("selection action gestures", () => { |
| 177 | expect(surface.onSelection).not.toHaveBeenCalled(); | 183 | expect(surface.onSelection).not.toHaveBeenCalled(); |
| 178 | }); | 184 | }); |
| 179 | | 185 | |
| | 186 | it.each([1, 2])( |
| | 187 | "dismisses selection actions when a button is pressed after %i clicks", |
| | 188 | (detail) => { |
| | 189 | const surface = createSelectionSurface(); |
| | 190 | surface.down(); |
| | 191 | surface.up({ detail }); |
| | 192 | surface.flush(); |
| | 193 | surface.onSelection.mockClear(); |
| | 194 | surface.onDismiss.mockClear(); |
| | 195 | |
| | 196 | surface.down({ target: surface.buttonIcon }); |
| | 197 | surface.up(); |
| | 198 | surface.change(); |
| | 199 | surface.flush(1000); |
| | 200 | expect(surface.onDismiss).toHaveBeenCalledWith("interaction"); |
| | 201 | expect(surface.onSelection).not.toHaveBeenCalled(); |
| | 202 | |
| | 203 | surface.down(); |
| | 204 | surface.change(); |
| | 205 | surface.up(); |
| | 206 | surface.flush(); |
| | 207 | expect(surface.onSelection).toHaveBeenCalledOnce(); |
| | 208 | }, |
| | 209 | ); |
| | 210 | |
| 180 | it("ignores nonprimary pointers and non-left mouseup without ending a left drag", () => { | 211 | it("ignores nonprimary pointers and non-left mouseup without ending a left drag", () => { |
| 181 | const surface = createSelectionSurface(); | 212 | const surface = createSelectionSurface(); |
| 182 | surface.down({ isPrimary: false }); | 213 | surface.down({ isPrimary: false }); |