From 8f2081bca3c542024f938795437c4f07fc9e877f Mon Sep 17 00:00:00 2001 From: najuna-brian Date: Wed, 28 Oct 2026 16:13:49 +0300 Subject: [PATCH] fix(formplayer): ignore page swipes that start on the signature canvas --- .../renderers/SwipeLayoutRenderer.test.tsx | 102 ++++++++++++++++++ .../src/renderers/SwipeLayoutRenderer.tsx | 10 +- 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 formulus-formplayer/src/renderers/SwipeLayoutRenderer.test.tsx diff --git a/formulus-formplayer/src/renderers/SwipeLayoutRenderer.test.tsx b/formulus-formplayer/src/renderers/SwipeLayoutRenderer.test.tsx new file mode 100644 index 000000000..6dd6c1091 --- /dev/null +++ b/formulus-formplayer/src/renderers/SwipeLayoutRenderer.test.tsx @@ -0,0 +1,102 @@ +// @vitest-environment jsdom +import React from 'react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from '@testing-library/react'; +import { JsonForms } from '@jsonforms/react'; +import { materialRenderers } from '@jsonforms/material-renderers'; +import Ajv from 'ajv'; +import SwipeLayoutRenderer, { swipeLayoutTester } from './SwipeLayoutRenderer'; +import SignatureQuestionRenderer, { + signatureQuestionTester, +} from './SignatureQuestionRenderer'; + +const ajv = new Ajv({ strict: false }); +ajv.addFormat('signature', () => true); + +const schema = { + type: 'object', + properties: { + signature: { type: 'object', format: 'signature', title: 'Signature' }, + after: { type: 'string', title: 'After note' }, + }, +}; + +const uischema = { + type: 'SwipeLayout', + elements: [ + { + type: 'VerticalLayout', + elements: [ + { type: 'Label', text: 'Page one' }, + { type: 'Control', scope: '#/properties/signature' }, + ], + }, + { + type: 'VerticalLayout', + elements: [ + { type: 'Label', text: 'Page two' }, + { type: 'Control', scope: '#/properties/after' }, + ], + }, + ], +}; + +const renderers = [ + ...materialRenderers, + { tester: swipeLayoutTester, renderer: SwipeLayoutRenderer }, + { tester: signatureQuestionTester, renderer: SignatureQuestionRenderer }, +]; + +function swipeLeft(target: Element) { + fireEvent.touchStart(target, { touches: [{ clientX: 300, clientY: 50 }] }); + fireEvent.touchMove(target, { touches: [{ clientX: 200, clientY: 52 }] }); + fireEvent.touchMove(target, { touches: [{ clientX: 40, clientY: 55 }] }); + fireEvent.touchEnd(target, { touches: [] }); +} + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +describe('SwipeLayout page swipes', () => { + function renderForm() { + return render( + , + ); + } + + it('does not change page when the swipe starts on the signature canvas', async () => { + const { container } = renderForm(); + expect(screen.getByText('Page one')).toBeInTheDocument(); + + const canvas = container.querySelector('canvas'); + expect(canvas).not.toBeNull(); + swipeLeft(canvas!); + + await new Promise(resolve => setTimeout(resolve, 600)); + expect(screen.getByText('Page one')).toBeInTheDocument(); + expect(screen.queryByText('Page two')).not.toBeInTheDocument(); + }); + + it('still changes page when the swipe starts elsewhere on the page', async () => { + renderForm(); + swipeLeft(screen.getByText('Page one')); + + await waitFor(() => + expect(screen.getByText('Page two')).toBeInTheDocument(), + ); + }); +}); diff --git a/formulus-formplayer/src/renderers/SwipeLayoutRenderer.tsx b/formulus-formplayer/src/renderers/SwipeLayoutRenderer.tsx index cc30eaf69..dd41d3454 100644 --- a/formulus-formplayer/src/renderers/SwipeLayoutRenderer.tsx +++ b/formulus-formplayer/src/renderers/SwipeLayoutRenderer.tsx @@ -444,11 +444,17 @@ const SwipeLayoutRenderer = ({ ], ); + const startedOnNoSwipe = (e: { target: EventTarget | null }) => + e.target instanceof Element && + !!e.target.closest('canvas, [data-no-swipe]'); + const handlers = useSwipeable({ - onSwipedLeft: () => { + onSwipedLeft: ({ event }) => { + if (startedOnNoSwipe(event)) return; if (nextVisiblePage !== null) navigateToPage(nextVisiblePage); }, - onSwipedRight: () => { + onSwipedRight: ({ event }) => { + if (startedOnNoSwipe(event)) return; if (prevVisiblePage !== null) navigateToPage(prevVisiblePage); }, });