## Problem Tab navigation between input widgets was not working in Fixed Layout applications. Users reported that pressing the Tab key would not move focus to the next input widget in the expected order (top-to-bottom, left-to-right), instead following the browser's default DOM-based tab order. This issues was raised by an Enterprise user [here](https://theappsmith.slack.com/archives/C0341RERY4R/p1758112042665109) ## Root Cause The issue was caused by a **timing problem** in the `useWidgetFocus` hook: 1. The `useEffect` hook was running immediately when the component mounted 2. However, the canvas element ref (`ref.current`) was set later via the React ref callback 3. This caused the event listeners for Tab navigation to never be attached, as `ref.current` was `null` when `useEffect` ran 4. Without the custom Tab event listeners, the browser fell back to its default tab navigation behavior ## Solution Refactored the `useWidgetFocus` hook to attach event listeners **immediately when the ref is set**, rather than waiting for a `useEffect` that runs too early: ### Before (Broken): ```typescript useEffect(() => { if (!ref.current) return; // ❌ Always true - ref not set yet const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Tab") handleTab(event); }; ref.current.addEventListener("keydown", handleKeyDown); }, []); // ❌ Runs before ref is set ``` ### After (Fixed): ```typescript const setRef = useCallback((node: HTMLElement | null) => { if (node === null) return; if (ref.current === node) return; ref.current = node; attachEventListeners(node); // ✅ Attach immediately when ref is set }, [attachEventListeners]); ``` ## Why This Solution Works 1. **Correct Timing**: Event listeners are now attached immediately when React calls the ref callback with the DOM element 2. **No Race Conditions**: Eliminates the timing issue between `useEffect` and ref assignment 3. **Maintains Functionality**: Preserves all existing tab navigation logic (position-based sorting, modal focus trapping, etc.) 4. **Clean Architecture**: Separates event listener attachment logic into a reusable callback ## Testing - ✅ Tab navigation now works correctly in Fixed Layout applications - ✅ Maintains proper top-to-bottom, left-to-right tab order - ✅ Modal focus trapping continues to work - ✅ Auto Layout behavior unchanged (tab navigation disabled as intended) - ✅ No regressions in existing functionality ## Files Changed - `app/client/src/utils/hooks/useWidgetFocus/useWidgetFocus.tsx` - Fixed event listener timing - `app/client/src/utils/hooks/useWidgetFocus/handleTab.ts` - Cleaned up (no functional changes) - `app/client/src/utils/hooks/useWidgetFocus/tabbable.ts` - Cleaned up (no functional changes) ## Automation /ok-to-test tags="@tag.Widget" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/18034264649> > Commit: ab9af8404302eb19c243dea583160bc9e74f33aa > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=18034264649&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Widget` > Spec: > <hr>Fri, 26 Sep 2025 11:09:55 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved reliability of focusing widgets on click. * More consistent Tab key navigation across widgets. * Prevents unintended focus behavior in non–auto-layout mode. * **Refactor** * Streamlined event listener management for focus and keyboard interactions, improving stability and reducing potential memory leaks. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|---|---|---|
| .. | ||
| .husky | ||
| .yarn | ||
| config | ||
| cypress | ||
| docker/templates | ||
| generators | ||
| packages | ||
| public | ||
| scripts | ||
| src | ||
| test | ||
| typings | ||
| .babelrc | ||
| .dockerignore | ||
| .editorconfig | ||
| .eslintrc.base.json | ||
| .eslintrc.js | ||
| .fork-ts-checkerrc | ||
| .gitignore | ||
| .lintstagedrc.json | ||
| .nvmrc | ||
| .prettierignore | ||
| .prettierrc | ||
| .sentryclirc | ||
| .yarnrc.yml | ||
| build.sh | ||
| cypress_ci_custom.config.ts | ||
| cypress_ci_hosted.config.ts | ||
| cypress_ci.config.ts | ||
| cypress-add-tags.js | ||
| cypress.config.ts | ||
| download-assets.js | ||
| jest.config.js | ||
| knip.json | ||
| package.json | ||
| README.md | ||
| README.old.md | ||
| start-caddy.sh | ||
| start-https.sh | ||
| tailwind.config.js | ||
| tsconfig.json | ||
| tsconfig.path.json | ||
| vercel.json | ||
| yarn.lock | ||
Appsmith Client
This project was bootstrapped with Create React App.
For details on setting up your development machine, please refer to the Setup Guide