Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(editor): Improve touch device detection #9675

Merged
merged 7 commits into from
Jun 12, 2024
Merged
40 changes: 30 additions & 10 deletions packages/design-system/src/composables/useDeviceSupport.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
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);
global.navigator = { userAgent: 'test-agent', maxTouchPoints: 0 } as Navigator;
});

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);
});
});

Expand Down
11 changes: 10 additions & 1 deletion packages/design-system/src/composables/useDeviceSupport.ts
Original file line number Diff line number Diff line change
@@ -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') ||
Expand Down
15 changes: 15 additions & 0 deletions packages/editor-ui/src/__tests__/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})),
});
Loading