From 2b2a85bf89dc3175e097b0e73ffd487d3abf0d81 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 4 Sep 2026 12:53:59 +0200 Subject: [PATCH] [Web] Refresh the DOM after a full config replace ## Description resetConfig clears touchAction, userSelect and enableContextMenu, but the DOM was only refreshed when the new config carried one of those keys. resetConfig now marks the DOM as stale and updateGestureConfig flushes it. The web delegate ignores updateDOM before init. ## Test plan Added tests in `GestureHandler.test.ts`. --- .../src/web/handlers/GestureHandler.ts | 14 +++++++------ .../handlers/__tests__/GestureHandler.test.ts | 21 +++++++++++++++++++ .../web/tools/GestureHandlerWebDelegate.ts | 8 +++++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts index fe94a0e20b..74188ef1ff 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts @@ -64,6 +64,7 @@ export default abstract class GestureHandler implements IGestureHandler { private _activeCursor?: ActiveCursor | undefined = undefined; private _touchAction?: TouchAction | undefined = undefined; private _userSelect?: UserSelect | undefined = undefined; + private needsDOMUpdate = false; // Orchestrator properties private _activationIndex = 0; @@ -833,29 +834,29 @@ export default abstract class GestureHandler implements IGestureHandler { this._activeCursor = config.activeCursor; } - let shouldUpdateDOM = false; - if (config.enableContextMenu !== undefined) { this.enableContextMenu = config.enableContextMenu; - shouldUpdateDOM = true; + this.needsDOMUpdate = true; } if (config.touchAction !== undefined) { this._touchAction = config.touchAction; - shouldUpdateDOM = true; + this.needsDOMUpdate = true; } if (config.userSelect !== undefined) { this._userSelect = config.userSelect; - shouldUpdateDOM = true; + this.needsDOMUpdate = true; } if (enabledChanged) { this.delegate.onEnabledChange(); - } else if (shouldUpdateDOM) { + } else if (this.needsDOMUpdate) { this.delegate.updateDOM(); } + this.needsDOMUpdate = false; + if (this.enabled) { return; } @@ -960,6 +961,7 @@ export default abstract class GestureHandler implements IGestureHandler { this._activeCursor = undefined; this._touchAction = undefined; this._userSelect = undefined; + this.needsDOMUpdate = true; } public onDestroy(): void { diff --git a/packages/react-native-gesture-handler/src/web/handlers/__tests__/GestureHandler.test.ts b/packages/react-native-gesture-handler/src/web/handlers/__tests__/GestureHandler.test.ts index 42656cf598..f5115ee52b 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/__tests__/GestureHandler.test.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/__tests__/GestureHandler.test.ts @@ -1,6 +1,7 @@ import type { Config } from '../../interfaces'; import EventManager from '../../tools/EventManager'; import type { GestureHandlerDelegate } from '../../tools/GestureHandlerDelegate'; +import { GestureHandlerWebDelegate } from '../../tools/GestureHandlerWebDelegate'; import GestureHandler from '../GestureHandler'; import type IGestureHandler from '../IGestureHandler'; @@ -34,6 +35,26 @@ describe('GestureHandler web config reset', () => { expect(handler.enabled).toBe(true); }); + + test('a config without touchAction refreshes the DOM', () => { + const delegate = { + onEnabledChange: jest.fn(), + updateDOM: jest.fn(), + }; + const handler = new TestGestureHandler( + delegate as unknown as GestureHandlerDelegate + ); + handler.setGestureConfig({ enabled: true, touchAction: 'pan-y' }); + + handler.setGestureConfig({ enabled: true }); + + expect(handler.touchAction).toBeUndefined(); + expect(delegate.updateDOM).toHaveBeenCalledTimes(1); + }); + + test('the web delegate ignores DOM updates before init', () => { + expect(() => new GestureHandlerWebDelegate().updateDOM()).not.toThrow(); + }); }); describe('GestureHandler web event manager attachment', () => { diff --git a/packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts b/packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts index edce77d9a8..1271cd7abc 100644 --- a/packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts +++ b/packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts @@ -76,9 +76,9 @@ export class GestureHandlerWebDelegate this.gestureHandler.attachEventManager(manager) ); - this.updateDOM(); - this.isInitialized = true; + + this.updateDOM(); } detach(): void { @@ -110,6 +110,10 @@ export class GestureHandlerWebDelegate } updateDOM(): void { + if (!this.isInitialized) { + return; + } + this.setUserSelect(); this.setTouchAction(); this.setContextMenu();