From 5548b1472f7b74b1191cdf61a90c96f279029479 Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Thu, 24 Mar 2022 11:09:10 +0100 Subject: [PATCH 1/9] fix: prevent search input blur from closing panel in detached mode --- packages/autocomplete-core/src/getPropGetters.ts | 10 ++++++++-- packages/autocomplete-core/src/stateReducer.ts | 2 +- .../src/types/AutocompletePropGetters.ts | 1 + packages/autocomplete-js/src/autocomplete.ts | 1 + 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/autocomplete-core/src/getPropGetters.ts b/packages/autocomplete-core/src/getPropGetters.ts index 0944a2ab1..37bacdeae 100644 --- a/packages/autocomplete-core/src/getPropGetters.ts +++ b/packages/autocomplete-core/src/getPropGetters.ts @@ -29,7 +29,13 @@ export function getPropGetters< TKeyboardEvent >({ props, refresh, store, ...setters }: GetPropGettersOptions) { const getEnvironmentProps: GetEnvironmentProps = (providedProps) => { - const { inputElement, formElement, panelElement, ...rest } = providedProps; + const { + inputElement, + formElement, + panelElement, + isDetached, + ...rest + } = providedProps; return { // On touch devices, we do not rely on the native `blur` event of the @@ -60,7 +66,7 @@ export function getPropGetters< ); if (isTargetWithinAutocomplete === false) { - store.dispatch('blur', null); + store.dispatch('blur', { isDetached }); // If requests are still pending when the user closes the panel, they // could reopen the panel once they resolve. diff --git a/packages/autocomplete-core/src/stateReducer.ts b/packages/autocomplete-core/src/stateReducer.ts index ced4cfea2..544c6036e 100644 --- a/packages/autocomplete-core/src/stateReducer.ts +++ b/packages/autocomplete-core/src/stateReducer.ts @@ -144,7 +144,7 @@ export const stateReducer: Reducer = (state, action) => { } case 'blur': { - if (action.props.debug) { + if (action.props.debug || action.payload?.isDetached) { return state; } diff --git a/packages/autocomplete-core/src/types/AutocompletePropGetters.ts b/packages/autocomplete-core/src/types/AutocompletePropGetters.ts index cef3c00c6..6926c987c 100644 --- a/packages/autocomplete-core/src/types/AutocompletePropGetters.ts +++ b/packages/autocomplete-core/src/types/AutocompletePropGetters.ts @@ -22,6 +22,7 @@ export type GetEnvironmentProps = (props: { formElement: HTMLElement; inputElement: HTMLInputElement; panelElement: HTMLElement; + isDetached: boolean; }) => { onTouchStart(event: TouchEvent): void; onTouchMove(event: TouchEvent): void; diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index 28bbce84e..5aa7930ef 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -174,6 +174,7 @@ export function autocomplete( formElement: dom.value.form, panelElement: dom.value.panel, inputElement: dom.value.input, + isDetached: isDetached.value, }); setProperties(props.value.core.environment as any, environmentProps); From 9aa58b483a8325cc2f3743b6a054a83e1a08a5c3 Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Thu, 24 Mar 2022 11:19:30 +0100 Subject: [PATCH 2/9] bump bundle size --- bundlesize.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundlesize.config.json b/bundlesize.config.json index 55a2b73cd..209bf90bc 100644 --- a/bundlesize.config.json +++ b/bundlesize.config.json @@ -2,7 +2,7 @@ "files": [ { "path": "packages/autocomplete-core/dist/umd/index.production.js", - "maxSize": "6 kB" + "maxSize": "6.25 kB" }, { "path": "packages/autocomplete-js/dist/umd/index.production.js", From 8d8e1269449f067a87c7d25f9598d964e9408a59 Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Thu, 24 Mar 2022 16:18:55 +0100 Subject: [PATCH 3/9] Revert "fix: prevent search input blur from closing panel in detached mode" This reverts commit 5548b1472f7b74b1191cdf61a90c96f279029479. --- packages/autocomplete-core/src/getPropGetters.ts | 10 ++-------- packages/autocomplete-core/src/stateReducer.ts | 2 +- .../src/types/AutocompletePropGetters.ts | 1 - packages/autocomplete-js/src/autocomplete.ts | 1 - 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/autocomplete-core/src/getPropGetters.ts b/packages/autocomplete-core/src/getPropGetters.ts index 37bacdeae..0944a2ab1 100644 --- a/packages/autocomplete-core/src/getPropGetters.ts +++ b/packages/autocomplete-core/src/getPropGetters.ts @@ -29,13 +29,7 @@ export function getPropGetters< TKeyboardEvent >({ props, refresh, store, ...setters }: GetPropGettersOptions) { const getEnvironmentProps: GetEnvironmentProps = (providedProps) => { - const { - inputElement, - formElement, - panelElement, - isDetached, - ...rest - } = providedProps; + const { inputElement, formElement, panelElement, ...rest } = providedProps; return { // On touch devices, we do not rely on the native `blur` event of the @@ -66,7 +60,7 @@ export function getPropGetters< ); if (isTargetWithinAutocomplete === false) { - store.dispatch('blur', { isDetached }); + store.dispatch('blur', null); // If requests are still pending when the user closes the panel, they // could reopen the panel once they resolve. diff --git a/packages/autocomplete-core/src/stateReducer.ts b/packages/autocomplete-core/src/stateReducer.ts index 544c6036e..ced4cfea2 100644 --- a/packages/autocomplete-core/src/stateReducer.ts +++ b/packages/autocomplete-core/src/stateReducer.ts @@ -144,7 +144,7 @@ export const stateReducer: Reducer = (state, action) => { } case 'blur': { - if (action.props.debug || action.payload?.isDetached) { + if (action.props.debug) { return state; } diff --git a/packages/autocomplete-core/src/types/AutocompletePropGetters.ts b/packages/autocomplete-core/src/types/AutocompletePropGetters.ts index 6926c987c..cef3c00c6 100644 --- a/packages/autocomplete-core/src/types/AutocompletePropGetters.ts +++ b/packages/autocomplete-core/src/types/AutocompletePropGetters.ts @@ -22,7 +22,6 @@ export type GetEnvironmentProps = (props: { formElement: HTMLElement; inputElement: HTMLInputElement; panelElement: HTMLElement; - isDetached: boolean; }) => { onTouchStart(event: TouchEvent): void; onTouchMove(event: TouchEvent): void; diff --git a/packages/autocomplete-js/src/autocomplete.ts b/packages/autocomplete-js/src/autocomplete.ts index 5aa7930ef..28bbce84e 100644 --- a/packages/autocomplete-js/src/autocomplete.ts +++ b/packages/autocomplete-js/src/autocomplete.ts @@ -174,7 +174,6 @@ export function autocomplete( formElement: dom.value.form, panelElement: dom.value.panel, inputElement: dom.value.input, - isDetached: isDetached.value, }); setProperties(props.value.core.environment as any, environmentProps); From c64a86115d7eec7d0fe9784f2ce3bb5f35c750de Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Thu, 24 Mar 2022 16:37:31 +0100 Subject: [PATCH 4/9] stop touchstart propagation if coming from cancel button --- packages/autocomplete-js/src/createAutocompleteDom.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/autocomplete-js/src/createAutocompleteDom.ts b/packages/autocomplete-js/src/createAutocompleteDom.ts index aae8dc7bd..e7db71090 100644 --- a/packages/autocomplete-js/src/createAutocompleteDom.ts +++ b/packages/autocomplete-js/src/createAutocompleteDom.ts @@ -169,6 +169,11 @@ export function createAutocompleteDom({ type: 'button', class: classNames.detachedCancelButton, textContent: translations.detachedCancelButtonText, + // Prevent environment `onTouchStart` behavior from closing the panel + // if it's initiated from this button + onTouchStart(event: TouchEvent) { + event.stopPropagation(); + }, onClick(event: MouseEvent) { event.stopPropagation(); autocomplete.setIsOpen(false); From f656c78cef16614d711aedb1d6fdd87e43d3e6f2 Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:13:03 +0100 Subject: [PATCH 5/9] update test --- .../src/__tests__/detached.test.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/autocomplete-js/src/__tests__/detached.test.ts b/packages/autocomplete-js/src/__tests__/detached.test.ts index 430e61dc8..b3068f655 100644 --- a/packages/autocomplete-js/src/__tests__/detached.test.ts +++ b/packages/autocomplete-js/src/__tests__/detached.test.ts @@ -111,10 +111,32 @@ describe('detached', () => { '.aa-DetachedCancelButton' ); + // Prevent `onTouchStart` event from closing detached overlay + const windowTouchStartListener = jest.fn(); + window.addEventListener('TouchStart', windowTouchStartListener); + + fireEvent( + cancelButton, + new TouchEvent('TouchStart', { + bubbles: true, + cancelable: true, + composed: true, + }) + ); + + expect(windowTouchStartListener).toHaveBeenCalledTimes(0); + + window.removeEventListener('TouchStart', windowTouchStartListener); + + await waitFor(() => { + expect(document.querySelector('.aa-DetachedOverlay')).toBeInTheDocument(); + expect(document.body).toHaveClass('aa-Detached'); + }); + + // Close detached overlay const bodyClickListener = jest.fn(); document.querySelector('body').addEventListener('click', bodyClickListener); - // Close detached overlay cancelButton.click(); expect(bodyClickListener).toHaveBeenCalledTimes(0); From 96f8ddc3ffe0828058bdd58a36332db10c6a96d9 Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Fri, 25 Mar 2022 18:10:37 +0100 Subject: [PATCH 6/9] remove unused code --- packages/autocomplete-js/src/__tests__/detached.test.ts | 9 --------- packages/autocomplete-js/src/createAutocompleteDom.ts | 3 +-- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/autocomplete-js/src/__tests__/detached.test.ts b/packages/autocomplete-js/src/__tests__/detached.test.ts index b3068f655..7eb94dc47 100644 --- a/packages/autocomplete-js/src/__tests__/detached.test.ts +++ b/packages/autocomplete-js/src/__tests__/detached.test.ts @@ -134,17 +134,8 @@ describe('detached', () => { }); // Close detached overlay - const bodyClickListener = jest.fn(); - document.querySelector('body').addEventListener('click', bodyClickListener); - cancelButton.click(); - expect(bodyClickListener).toHaveBeenCalledTimes(0); - - document - .querySelector('body') - .removeEventListener('click', bodyClickListener); - // The detached overlay should close await waitFor(() => { expect( diff --git a/packages/autocomplete-js/src/createAutocompleteDom.ts b/packages/autocomplete-js/src/createAutocompleteDom.ts index e7db71090..6df6f16f5 100644 --- a/packages/autocomplete-js/src/createAutocompleteDom.ts +++ b/packages/autocomplete-js/src/createAutocompleteDom.ts @@ -174,8 +174,7 @@ export function createAutocompleteDom({ onTouchStart(event: TouchEvent) { event.stopPropagation(); }, - onClick(event: MouseEvent) { - event.stopPropagation(); + onClick() { autocomplete.setIsOpen(false); setIsModalOpen(false); }, From 5bad68f9f336bddbdd95564fa0b13365da16c69d Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Fri, 25 Mar 2022 20:14:17 +0100 Subject: [PATCH 7/9] apply suggestion Co-authored-by: Sarah Dayan <5370675+sarahdayan@users.noreply.github.com> --- packages/autocomplete-js/src/createAutocompleteDom.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/autocomplete-js/src/createAutocompleteDom.ts b/packages/autocomplete-js/src/createAutocompleteDom.ts index 6df6f16f5..3ce303773 100644 --- a/packages/autocomplete-js/src/createAutocompleteDom.ts +++ b/packages/autocomplete-js/src/createAutocompleteDom.ts @@ -169,8 +169,8 @@ export function createAutocompleteDom({ type: 'button', class: classNames.detachedCancelButton, textContent: translations.detachedCancelButtonText, - // Prevent environment `onTouchStart` behavior from closing the panel - // if it's initiated from this button + // Prevent `onTouchStart` from closing the panel + // since it should be initiated by `onClick` only onTouchStart(event: TouchEvent) { event.stopPropagation(); }, From 207f8bd61d9ed6d4e0cbaa84ee3d4c4907449795 Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Wed, 30 Mar 2022 12:03:04 +0200 Subject: [PATCH 8/9] fix touch event case issue --- packages/autocomplete-js/src/__tests__/detached.test.ts | 6 +++--- packages/autocomplete-js/src/utils/setProperties.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/autocomplete-js/src/__tests__/detached.test.ts b/packages/autocomplete-js/src/__tests__/detached.test.ts index 7eb94dc47..e6d6956df 100644 --- a/packages/autocomplete-js/src/__tests__/detached.test.ts +++ b/packages/autocomplete-js/src/__tests__/detached.test.ts @@ -113,11 +113,11 @@ describe('detached', () => { // Prevent `onTouchStart` event from closing detached overlay const windowTouchStartListener = jest.fn(); - window.addEventListener('TouchStart', windowTouchStartListener); + window.addEventListener('touchStart', windowTouchStartListener); fireEvent( cancelButton, - new TouchEvent('TouchStart', { + new TouchEvent('touchStart', { bubbles: true, cancelable: true, composed: true, @@ -126,7 +126,7 @@ describe('detached', () => { expect(windowTouchStartListener).toHaveBeenCalledTimes(0); - window.removeEventListener('TouchStart', windowTouchStartListener); + window.removeEventListener('touchStart', windowTouchStartListener); await waitFor(() => { expect(document.querySelector('.aa-DetachedOverlay')).toBeInTheDocument(); diff --git a/packages/autocomplete-js/src/utils/setProperties.ts b/packages/autocomplete-js/src/utils/setProperties.ts index e0ab31deb..54c3c8a3f 100644 --- a/packages/autocomplete-js/src/utils/setProperties.ts +++ b/packages/autocomplete-js/src/utils/setProperties.ts @@ -51,7 +51,7 @@ export function setProperty(dom: HTMLElement, name: string, value: any) { useCapture = name !== (name = name.replace(/Capture$/, '')); nameLower = name.toLowerCase(); if (nameLower in dom) name = nameLower; - name = name.slice(2); + name = name.slice(2).replace(/^./, (c) => c.toLowerCase()); if (!(dom as any)._listeners) (dom as any)._listeners = {}; (dom as any)._listeners[name] = value; From cd3de9f1195a082fea733cfa79aa0ee2a82423a1 Mon Sep 17 00:00:00 2001 From: Dhaya <154633+dhayab@users.noreply.github.com> Date: Wed, 30 Mar 2022 14:22:52 +0200 Subject: [PATCH 9/9] use lowercase for touch events based on spec --- .../src/__tests__/detached.test.ts | 6 +++--- .../autocomplete-js/src/utils/setProperties.ts | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/autocomplete-js/src/__tests__/detached.test.ts b/packages/autocomplete-js/src/__tests__/detached.test.ts index e6d6956df..c0cd09780 100644 --- a/packages/autocomplete-js/src/__tests__/detached.test.ts +++ b/packages/autocomplete-js/src/__tests__/detached.test.ts @@ -113,11 +113,11 @@ describe('detached', () => { // Prevent `onTouchStart` event from closing detached overlay const windowTouchStartListener = jest.fn(); - window.addEventListener('touchStart', windowTouchStartListener); + window.addEventListener('touchstart', windowTouchStartListener); fireEvent( cancelButton, - new TouchEvent('touchStart', { + new TouchEvent('touchstart', { bubbles: true, cancelable: true, composed: true, @@ -126,7 +126,7 @@ describe('detached', () => { expect(windowTouchStartListener).toHaveBeenCalledTimes(0); - window.removeEventListener('touchStart', windowTouchStartListener); + window.removeEventListener('touchstart', windowTouchStartListener); await waitFor(() => { expect(document.querySelector('.aa-DetachedOverlay')).toBeInTheDocument(); diff --git a/packages/autocomplete-js/src/utils/setProperties.ts b/packages/autocomplete-js/src/utils/setProperties.ts index 54c3c8a3f..4098b493b 100644 --- a/packages/autocomplete-js/src/utils/setProperties.ts +++ b/packages/autocomplete-js/src/utils/setProperties.ts @@ -1,5 +1,17 @@ /* eslint-disable */ +/** + * Touch-specific event aliases + * + * See https://w3c.github.io/touch-events/#extensions-to-the-globaleventhandlers-mixin + */ +const TOUCH_EVENTS_ALIASES = [ + 'ontouchstart', + 'ontouchend', + 'ontouchmove', + 'ontouchcancel', +]; + /* * Taken from Preact * @@ -50,8 +62,9 @@ export function setProperty(dom: HTMLElement, name: string, value: any) { else if (name[0] === 'o' && name[1] === 'n') { useCapture = name !== (name = name.replace(/Capture$/, '')); nameLower = name.toLowerCase(); - if (nameLower in dom) name = nameLower; - name = name.slice(2).replace(/^./, (c) => c.toLowerCase()); + if (nameLower in dom || TOUCH_EVENTS_ALIASES.includes(nameLower)) + name = nameLower; + name = name.slice(2); if (!(dom as any)._listeners) (dom as any)._listeners = {}; (dom as any)._listeners[name] = value;