From 6422289b5e603a7114d4988ef11d0715a449e536 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 7 Jun 2024 17:25:04 +0200 Subject: [PATCH 1/5] fix(editor): Improve touch device detection --- .../design-system/src/composables/useDeviceSupport.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/design-system/src/composables/useDeviceSupport.ts b/packages/design-system/src/composables/useDeviceSupport.ts index 8ead46c140a9b..63d5549dccfcc 100644 --- a/packages/design-system/src/composables/useDeviceSupport.ts +++ b/packages/design-system/src/composables/useDeviceSupport.ts @@ -1,7 +1,16 @@ import { ref } from 'vue'; export function useDeviceSupport() { - const isTouchDevice = ref(window.hasOwnProperty('ontouchstart') || navigator.maxTouchPoints > 0); + /** + * Check if the device is a touch device but exclude devices that have a fine pointer (mouse or track-pad) + * - `fine` will check for an accurate pointing device. Examples include mice, touch-pads, and drawing styluses + * - `coarse` will check for a pointing device of limited accuracy. Examples include touchscreens and motion-detection sensors + * - `any-pointer` will check for the presence of any pointing device, if there are multiple of them + */ + const isTouchDevice = ref( + window.matchMedia('(any-pointer: coarse)').matches && + !window.matchMedia('(any-pointer: fine)').matches, + ); const userAgent = ref(navigator.userAgent.toLowerCase()); const isMacOs = ref( userAgent.value.includes('macintosh') || From e65c1ce7ffd5214e038042807fa283fd6dc257b1 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Fri, 7 Jun 2024 22:03:12 +0200 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=92=A9=20Adding=20debug=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/design-system/src/composables/useDeviceSupport.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/design-system/src/composables/useDeviceSupport.ts b/packages/design-system/src/composables/useDeviceSupport.ts index 63d5549dccfcc..496bb0326236d 100644 --- a/packages/design-system/src/composables/useDeviceSupport.ts +++ b/packages/design-system/src/composables/useDeviceSupport.ts @@ -27,6 +27,12 @@ export function useDeviceSupport() { return (e as KeyboardEvent).ctrlKey; } + console.log('============ useDeviceSupport ============'); + console.log('isTouchDevice', isTouchDevice.value); + console.log('FINE', window.matchMedia('(any-pointer: fine)').matches); + console.log('COARSE', window.matchMedia('(any-pointer: coarse)').matches); + console.log('=========================================='); + return { isTouchDevice: isTouchDevice.value, isMacOs: isMacOs.value, From 2fce82c986b7e8ef7ec376dc3077b7761be3b425 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Mon, 10 Jun 2024 10:31:44 +0200 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9C=85=20Adding=20useDeviceSupport=20tes?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/composables/useDeviceSupport.test.ts | 40 ++++++++++++++----- .../src/composables/useDeviceSupport.ts | 6 --- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/packages/design-system/src/composables/useDeviceSupport.test.ts b/packages/design-system/src/composables/useDeviceSupport.test.ts index 767a7516011a3..0260dcacaf8cd 100644 --- a/packages/design-system/src/composables/useDeviceSupport.test.ts +++ b/packages/design-system/src/composables/useDeviceSupport.test.ts @@ -1,5 +1,11 @@ import { useDeviceSupport } from 'n8n-design-system/composables/useDeviceSupport'; +const detectPointerType = (query: string) => { + const isCoarse = query === '(any-pointer: coarse)'; + const isFine = query === '(any-pointer: fine)'; + return { fine: isFine, coarse: isCoarse }; +}; + describe('useDeviceSupport()', () => { beforeEach(() => { global.window = Object.create(window); @@ -7,23 +13,37 @@ describe('useDeviceSupport()', () => { }); describe('isTouchDevice', () => { - it('should be true if ontouchstart is in window', () => { - Object.defineProperty(window, 'ontouchstart', {}); + it('should be false if window matches `any-pointer: fine` and `!any-pointer: coarse`', () => { + Object.defineProperty(window, 'matchMedia', { + value: vi.fn().mockImplementation((query: string) => { + const { fine, coarse } = detectPointerType(query); + return { matches: fine && !coarse }; + }), + }); const { isTouchDevice } = useDeviceSupport(); - expect(isTouchDevice).toEqual(true); + expect(isTouchDevice).toEqual(false); }); - it('should be true if navigator.maxTouchPoints > 0', () => { - Object.defineProperty(navigator, 'maxTouchPoints', { value: 1 }); + it('should be false if window matches `any-pointer: fine` and `any-pointer: coarse`', () => { + Object.defineProperty(window, 'matchMedia', { + value: vi.fn().mockImplementation((query: string) => { + const { fine, coarse } = detectPointerType(query); + return { matches: fine && coarse }; + }), + }); const { isTouchDevice } = useDeviceSupport(); - expect(isTouchDevice).toEqual(true); + expect(isTouchDevice).toEqual(false); }); - it('should be false if no touch support', () => { - delete window.ontouchstart; - Object.defineProperty(navigator, 'maxTouchPoints', { value: 0 }); + it('should be true if window matches `any-pointer: coarse` and `!any-pointer: fine`', () => { + Object.defineProperty(window, 'matchMedia', { + value: vi.fn().mockImplementation((query: string) => { + const { fine, coarse } = detectPointerType(query); + return { matches: coarse && !fine }; + }), + }); const { isTouchDevice } = useDeviceSupport(); - expect(isTouchDevice).toEqual(false); + expect(isTouchDevice).toEqual(true); }); }); diff --git a/packages/design-system/src/composables/useDeviceSupport.ts b/packages/design-system/src/composables/useDeviceSupport.ts index 496bb0326236d..63d5549dccfcc 100644 --- a/packages/design-system/src/composables/useDeviceSupport.ts +++ b/packages/design-system/src/composables/useDeviceSupport.ts @@ -27,12 +27,6 @@ export function useDeviceSupport() { return (e as KeyboardEvent).ctrlKey; } - console.log('============ useDeviceSupport ============'); - console.log('isTouchDevice', isTouchDevice.value); - console.log('FINE', window.matchMedia('(any-pointer: fine)').matches); - console.log('COARSE', window.matchMedia('(any-pointer: coarse)').matches); - console.log('=========================================='); - return { isTouchDevice: isTouchDevice.value, isMacOs: isMacOs.value, From bafe84617cbdaff8ecca3ddd3e5363e298fe74f4 Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Mon, 10 Jun 2024 11:38:28 +0200 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=94=EF=B8=8F=20Updating=20other=20tes?= =?UTF-8?q?ts=20that=20use=20deviceSupport=20composable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/__tests__/NodeDetailsView.test.ts | 2 ++ .../src/composables/__tests__/useCanvasPanning.test.ts | 5 +++++ .../src/composables/__tests__/useHistoryHelper.test.ts | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts b/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts index 19381df410e10..dca236511e355 100644 --- a/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts +++ b/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts @@ -60,6 +60,8 @@ describe('NodeDetailsView', () => { beforeAll(() => { server = setupServer(); + // Mocks for useDeviceSupport + Object.defineProperty(window, 'matchMedia', { value: vi.fn().mockImplementation(() => true) }); }); afterEach(() => { diff --git a/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts b/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts index e45a3e646313e..cf5dc7b338dc6 100644 --- a/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts @@ -20,6 +20,11 @@ describe('useCanvasPanning()', () => { let element: HTMLElement; let elementRef: Ref; + beforeAll(() => { + // Mocks for useDeviceSupport + Object.defineProperty(window, 'matchMedia', { value: vi.fn().mockImplementation(() => true) }); + }); + beforeEach(() => { element = document.createElement('div'); element.id = 'node-view'; diff --git a/packages/editor-ui/src/composables/__tests__/useHistoryHelper.test.ts b/packages/editor-ui/src/composables/__tests__/useHistoryHelper.test.ts index a41bed8547eac..ac573c22462ef 100644 --- a/packages/editor-ui/src/composables/__tests__/useHistoryHelper.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useHistoryHelper.test.ts @@ -50,6 +50,11 @@ const TestComponent = defineComponent({ }); describe('useHistoryHelper', () => { + beforeAll(() => { + // Mocks for useDeviceSupport + Object.defineProperty(window, 'matchMedia', { value: vi.fn().mockImplementation(() => true) }); + }); + beforeEach(() => { undoMock.mockClear(); redoMock.mockClear(); From 0260e35f4ce19abf6c65bf70c1c092f95382f8fc Mon Sep 17 00:00:00 2001 From: Milorad Filipovic Date: Wed, 12 Jun 2024 13:26:20 +0200 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=91=8C=20Adding=20global=20mock=20for?= =?UTF-8?q?=20`matchMedia`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor-ui/src/__tests__/setup.ts | 15 +++++++++++++++ .../components/__tests__/NodeDetailsView.test.ts | 2 -- .../__tests__/useCanvasPanning.test.ts | 5 ----- .../__tests__/useHistoryHelper.test.ts | 5 ----- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/editor-ui/src/__tests__/setup.ts b/packages/editor-ui/src/__tests__/setup.ts index 3b9c07462ea29..91700ea6163b8 100644 --- a/packages/editor-ui/src/__tests__/setup.ts +++ b/packages/editor-ui/src/__tests__/setup.ts @@ -44,3 +44,18 @@ export class IntersectionObserver { window.IntersectionObserver = IntersectionObserver; global.IntersectionObserver = IntersectionObserver; + +// Mocks for useDeviceSupport +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query) => ({ + matches: true, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), +}); diff --git a/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts b/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts index dca236511e355..19381df410e10 100644 --- a/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts +++ b/packages/editor-ui/src/components/__tests__/NodeDetailsView.test.ts @@ -60,8 +60,6 @@ describe('NodeDetailsView', () => { beforeAll(() => { server = setupServer(); - // Mocks for useDeviceSupport - Object.defineProperty(window, 'matchMedia', { value: vi.fn().mockImplementation(() => true) }); }); afterEach(() => { diff --git a/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts b/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts index 85e0cb58a7abc..be826a1b17b0c 100644 --- a/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useCanvasPanning.test.ts @@ -20,11 +20,6 @@ describe('useCanvasPanning()', () => { let element: HTMLElement; let elementRef: Ref; - beforeAll(() => { - // Mocks for useDeviceSupport - Object.defineProperty(window, 'matchMedia', { value: vi.fn().mockImplementation(() => true) }); - }); - beforeEach(() => { element = document.createElement('div'); element.id = 'node-view'; diff --git a/packages/editor-ui/src/composables/__tests__/useHistoryHelper.test.ts b/packages/editor-ui/src/composables/__tests__/useHistoryHelper.test.ts index ac573c22462ef..a41bed8547eac 100644 --- a/packages/editor-ui/src/composables/__tests__/useHistoryHelper.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useHistoryHelper.test.ts @@ -50,11 +50,6 @@ const TestComponent = defineComponent({ }); describe('useHistoryHelper', () => { - beforeAll(() => { - // Mocks for useDeviceSupport - Object.defineProperty(window, 'matchMedia', { value: vi.fn().mockImplementation(() => true) }); - }); - beforeEach(() => { undoMock.mockClear(); redoMock.mockClear();