Skip to content

Commit 26c30b7

Browse files
committed
frontend/aria: start making autoFocus configurable
1 parent 13bec8c commit 26c30b7

File tree

11 files changed

+370
-59
lines changed

11 files changed

+370
-59
lines changed

src/dev/ARIA.md

Lines changed: 319 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,70 +1564,342 @@ When keyboard testing menus:
15641564
3. Press Enter to activate item - should NOT trigger parent handlers
15651565
4. Verify the menu closes and the action executes cleanly
15661566

1567-
## Session Summary - October 28, 2025
1567+
## Phase 23: AutoFocus User Preference ✅ COMPLETED (Nov 6, 2025)
15681568

1569-
### Session Accomplishments
1569+
**Priority: HIGH** - Improves keyboard navigation experience
15701570

1571-
**Phases Completed**:
1571+
### Problem Statement
15721572

1573-
- ✅ Phase 11a: Projects List Page (Complete)
1574-
- ✅ Phase 9b: Account Preferences Sub-Sections (Complete)
1573+
Users with assistive technology or keyboard-only access found that search inputs and form fields would automatically grab focus when navigating pages or opening dialogs. This interfered with:
15751574

1576-
**Component Enhancements**:
1575+
- Landmark-based navigation (Alt+Shift+M to navigate regions)
1576+
- Tab-order navigation between sections
1577+
- Keyboard shortcut usage
1578+
- General page exploration before using search/input
1579+
1580+
### Solution Implemented
15771581

1578-
1. **Panel** (`antd-bootstrap.tsx`) - Now supports ARIA region props
1579-
- Added `role` and `aria-label` parameters
1580-
- Enables Panel components throughout app to declare landmark regions
1582+
Created a user-configurable preference to disable autoFocus behavior on normal page input fields. Popup dialogs (modals, confirmation dialogs) retain autoFocus for better UX since they don't interfere with overall page navigation.
15811583

1582-
2. **SettingBox** (`components/setting-box.tsx`) - Now supports ARIA region props
1583-
- Added `role` and `aria-label` parameters
1584-
- Direct ARIA support on component (not wrapped in divs)
1584+
### Architecture
15851585

1586-
**Projects List Page (Phase 11a)**:
1586+
#### 1. New Hook: `useAutoFocusPreference()` ✅
15871587

1588-
- Main workspace landmark with "Projects management" label
1589-
- Project filters section with descriptive "Project filters and controls" label
1590-
- Dynamic projects list label showing count: "Projects list (N total)"
1591-
- All controls (search, filters, buttons) have clear aria-labels
1592-
- Starred projects section with count indicator
1588+
**File**: `packages/frontend/account/use-auto-focus-preference.ts` (Created)
15931589

1594-
**Account Preferences Sub-Sections (Phase 9b)**:
1590+
```typescript
1591+
import { useTypedRedux } from "@cocalc/frontend/app-framework";
15951592

1596-
- Enhanced all account preference panels with region landmarks
1597-
- 25+ sub-sections now have clear accessibility labels
1598-
- Consistent naming pattern for easy navigation
1599-
- Components used directly with ARIA props (no wrapper divs)
1593+
export function useAutoFocusPreference(): boolean {
1594+
const other_settings = useTypedRedux("account", "other_settings");
1595+
return other_settings?.get("auto_focus") ?? false; // Default: disabled
1596+
}
1597+
```
16001598

1601-
**Sub-Sections Labeled**:
1599+
**Key Details**:
1600+
- Centralized single source of truth for the preference
1601+
- Returns boolean: true (autoFocus enabled) or false (disabled, default)
1602+
- Integrates with Redux account store for persistence across sessions
16021603

1603-
- Appearance: User Interface, Dark Mode, Editor Color Scheme, Terminal
1604-
- Editor: Basic Settings, Keyboard, Display, Editing Behavior, Auto-completion, File Operations, Jupyter, UI Elements
1605-
- Keyboard: Keyboard Shortcuts
1606-
- Communication: Notification Settings
1607-
- Security: API Keys, SSH Keys (renamed from "Security settings")
1608-
- Profile: Account Settings, Avatar Settings
1609-
- Tours: Completed Tours
1610-
- Other: AI, Theme, Browser, File Explorer, Projects
1604+
#### 2. Account Settings UI ✅
16111605

1612-
**Files Modified**: 30+ files
1606+
**File**: `packages/frontend/account/account-preferences-appearance.tsx`
16131607

1614-
- Core: 2 component enhancements
1615-
- Projects: 3 files
1616-
- Account Preferences: 25+ files across all preference categories
1608+
Added new Switch control in "User Interface" section:
16171609

1618-
### Next Steps
1610+
```tsx
1611+
<Switch
1612+
checked={!!other_settings.get("auto_focus")}
1613+
onChange={(e) => on_change("auto_focus", e.target.checked)}
1614+
>
1615+
<FormattedMessage
1616+
id="account.other-settings.auto_focus"
1617+
defaultMessage={`<strong>Auto Focus Text Input:</strong>
1618+
automatically focus text input fields when they appear (e.g., in dialogs and modals)`}
1619+
/>
1620+
</Switch>
1621+
```
1622+
1623+
**Placement**: Above "Hide File Tab Popovers" in account preferences
1624+
1625+
#### 3. Implementation Pattern ✅
1626+
1627+
Used consistently across all affected input fields:
1628+
1629+
```tsx
1630+
// Import hook
1631+
import { useAutoFocusPreference } from "@cocalc/frontend/account";
1632+
1633+
// Use in component
1634+
const shouldAutoFocus = useAutoFocusPreference();
1635+
1636+
// Apply to inputs
1637+
<Input
1638+
autoFocus={shouldAutoFocus}
1639+
/>
1640+
1641+
<SearchInput
1642+
autoFocus={shouldAutoFocus}
1643+
autoSelect={shouldAutoFocus}
1644+
/>
1645+
```
1646+
1647+
### Files Modified
1648+
1649+
**Core**:
1650+
- `packages/frontend/account/use-auto-focus-preference.ts` (Created)
1651+
- `packages/frontend/account/index.ts` - Added export
1652+
- `packages/frontend/account/account-preferences-appearance.ts` - Added UI control
1653+
- `packages/frontend/components/search-input.tsx` - **Bug fix**: Fixed undefined `focus` variable in useEffect (critical fix)
1654+
1655+
**Input Fields Updated**:
1656+
- `packages/frontend/project/new/new-file-page.tsx` - 2 inputs (create folder modal, filename)
1657+
- `packages/frontend/projects/create-project.tsx` - 1 input (project title)
1658+
- `packages/frontend/projects/projects-table-controls.tsx` - 1 input (search projects)
1659+
- `packages/frontend/project/explorer/search-bar.tsx` - 1 input (filter files, select autoSelect)
1660+
- `packages/frontend/frame-editors/frame-tree/commands/generic-commands.tsx` - 1 input (command palette)
1661+
1662+
**NOT Modified** (intentional):
1663+
- Popup dialog inputs in `ai-cell-generator.tsx` and `llm-assistant-button.tsx` - These remain with autoFocus enabled since popup dialogs don't interfere with keyboard navigation
1664+
- Cell inputs in Jupyter notebooks - Cell-specific inputs remain with autoFocus enabled
1665+
1666+
### User Impact
1667+
1668+
- ✅ Keyboard-only users can navigate landmarks without inputs stealing focus
1669+
- ✅ Assistive technology users have full control over focus behavior
1670+
- ✅ Preference persists across sessions in account settings
1671+
- ✅ Default (disabled) prevents unexpected focus grabs
1672+
- ✅ Users can re-enable if they prefer automatic focusing
1673+
1674+
### Testing
1675+
1676+
**Bug Fix Validation**:
1677+
1678+
The SearchInput component had a critical bug preventing the preference from working:
1679+
1680+
```typescript
1681+
// BEFORE (broken):
1682+
useEffect(() => {
1683+
if (focus == null) return; // ← undefined variable
1684+
input_ref.current?.focus();
1685+
}, [focus]); // ← wrong dependency
1686+
1687+
// AFTER (fixed):
1688+
useEffect(() => {
1689+
if (props.focus == null) return;
1690+
input_ref.current?.focus();
1691+
}, [props.focus]); // ← correct dependency
1692+
```
1693+
1694+
This fix resolved the issue where file explorer search was still grabbing focus despite `shouldAutoFocus={false}`.
1695+
1696+
---
1697+
1698+
## Phase 24: Editor Content Landmark Navigation ⏳ PENDING
1699+
1700+
**Priority: HIGH** - Core editor accessibility
1701+
1702+
### Problem Statement
1703+
1704+
When navigating landmarks with Alt+Shift+M, pressing Alt+Shift+M on the "Content" landmark (e.g., "Content: 123.md (Main)") does not focus the editor content itself. Users land on the region but cannot immediately interact with the editor without additional navigation steps.
1705+
1706+
Current workaround: Alt+Shift+N to skip to next landmark, but this feels inefficient.
1707+
1708+
### Proposed Solution: Two-Step Interaction Pattern
1709+
1710+
#### Step 1: Focus Landmark Region
1711+
- Alt+Shift+M navigates to "Content: {filename}" landmark
1712+
- Region announces file being edited
1713+
- Region becomes focusable (`tabindex="0"`)
1714+
1715+
#### Step 2: Enter Editor (Return Key Activation)
1716+
- Pressing Return key while on Content landmark focuses the primary editor
1717+
- Dynamically chooses appropriate element:
1718+
- **CodeMirror**: Focus the editor instance
1719+
- **Frame-split layouts**: Focus the first/active frame editor
1720+
- **Jupyter notebook**: Focus active cell input
1721+
- **Other editors**: Focus primary interactive element
1722+
- Alternative: Tab key after landing on landmark navigates into editable content
1723+
1724+
### Implementation Plan
1725+
1726+
#### A. Make Content Landmark Focusable
1727+
1728+
**File**: `packages/frontend/project/page/page.tsx`
1729+
1730+
```tsx
1731+
<div
1732+
role="main"
1733+
aria-label={contentLabel}
1734+
tabIndex={0} // ← Make focusable
1735+
onKeyDown={handleContentKeyDown} // ← Handle Return key
1736+
>
1737+
{/* content */}
1738+
</div>
1739+
```
1740+
1741+
**Handler Logic**:
1742+
```typescript
1743+
function handleContentKeyDown(e: React.KeyboardEvent) {
1744+
if (e.key === "Enter") {
1745+
e.preventDefault();
1746+
e.stopPropagation();
1747+
focusPrimaryEditor();
1748+
}
1749+
}
1750+
1751+
function focusPrimaryEditor() {
1752+
// Strategy 1: Try to focus CodeMirror editor
1753+
const editor = document.querySelector(".cm-editor") as HTMLElement;
1754+
if (editor) {
1755+
editor.focus();
1756+
return;
1757+
}
1758+
1759+
// Strategy 2: Try to focus first focusable element within content
1760+
const focusable = content.querySelector(
1761+
"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"
1762+
) as HTMLElement;
1763+
if (focusable) {
1764+
focusable.focus();
1765+
}
1766+
}
1767+
```
1768+
1769+
#### B. Frame Tree Editor Focus Support
1770+
1771+
**File**: `packages/frontend/frame-editors/frame-tree/frame-tree.tsx`
1772+
1773+
Make individual frame editors focusable with keyboard support:
1774+
1775+
```tsx
1776+
<div
1777+
className="frame-tree-frame"
1778+
role="region"
1779+
aria-label={frameLabel}
1780+
tabIndex={activeFrame ? 0 : -1} // ← Focusable if active
1781+
onKeyDown={ariaKeyDown(handleFrameActivation)}
1782+
>
1783+
<FrameEditor />
1784+
</div>
1785+
```
1786+
1787+
#### C. Visual Feedback
1788+
1789+
Add subtle visual indicator when Content landmark is focused (keyboard-only users need to see focus state):
1790+
1791+
```tsx
1792+
<div
1793+
className={`content-region ${isFocused ? "focused-landmark" : ""}`}
1794+
>
1795+
```
1796+
1797+
```css
1798+
.content-region:focus {
1799+
outline: 2px solid var(--focus-color);
1800+
outline-offset: 2px;
1801+
}
1802+
1803+
.content-region.focused-landmark {
1804+
background-color: var(--focus-bg);
1805+
}
1806+
```
16191807

1620-
**Phase 11b: Project Page** - Ready to start
1808+
### Expected User Experience
1809+
1810+
**Scenario**: User navigates to a LaTeX editor file
1811+
1812+
1. **Alt+Shift+M** → "Content: document.tex (Main)" landmark focused
1813+
2. **Return key** → CodeMirror editor receives focus
1814+
3. **Type** → User can immediately edit the LaTeX content
1815+
4. **Alt+Shift+M** → Next landmark (sidebar, etc.)
1816+
1817+
**Alternative Flow**:
1818+
1819+
1. **Alt+Shift+M** → Content landmark focused
1820+
2. **Tab** → Focus moves to first interactive element (button bar, editor, etc.)
1821+
3. **Shift+Tab** → Navigate backwards through editor controls
1822+
1823+
### Potential Enhancements
1824+
1825+
1. **Visual Highlight**: Subtle glow or border when Content landmark receives focus
1826+
2. **Announcement**: "Content region focused. Press Return to enter editor or Tab to navigate controls"
1827+
3. **Escape Key**: Exit editor focus, return to landmark (for later phase)
1828+
4. **Multiple Frames**: When split editors are active, focus primary/active frame first
1829+
1830+
### Files to Modify
1831+
1832+
1. `packages/frontend/project/page/page.tsx` - Main content region (tabindex, keydown handler)
1833+
2. `packages/frontend/frame-editors/frame-tree/frame-tree.tsx` - Frame focusability
1834+
3. `packages/frontend/frame-editors/frame-tree/editor.tsx` - Editor focus management
1835+
4. `packages/frontend/app/aria.tsx` - Potential helper functions for editor focusing
1836+
5. Styling: Add focus indicator CSS to frame-tree or global styles
1837+
1838+
### Testing Checklist
1839+
1840+
- [ ] Alt+Shift+M navigates to Content landmark
1841+
- [ ] Content landmark is announced with current file name
1842+
- [ ] Return key while on landmark focuses the editor
1843+
- [ ] Tab/Shift+Tab navigate from landmark to content
1844+
- [ ] Visual focus indicator is visible for keyboard users
1845+
- [ ] Works with CodeMirror editors (LaTeX, Python, JavaScript, etc.)
1846+
- [ ] Works with split editors (multiple frames)
1847+
- [ ] Works with Jupyter notebooks
1848+
- [ ] Works with other editor types (Markdown, CSV, PDF, etc.)
1849+
- [ ] Escape key behavior (future phase)
1850+
1851+
---
1852+
1853+
## Session Summary - November 6, 2025
1854+
1855+
### Session Accomplishments
1856+
1857+
**Phases Completed**:
1858+
1859+
- ✅ Phase 23: AutoFocus User Preference (Complete)
1860+
- ✅ Phase 24: Editor Content Landmark Navigation (Planned)
1861+
1862+
**Phase 23: AutoFocus User Preference** ✅:
1863+
1864+
- New user preference: "Auto Focus Text Input" in account appearance settings
1865+
- Created reusable hook: `useAutoFocusPreference()` for consistent behavior
1866+
- Applied to 5 major input locations across the frontend
1867+
- **Critical bug fix**: Fixed undefined `focus` variable in `search-input.tsx` that was preventing preference from working
1868+
- Default: autoFocus disabled (false) for better landmark navigation
1869+
- Popup dialogs remain with autoFocus enabled (no interference with navigation)
1870+
1871+
**Files Modified**: 9 files
1872+
1873+
- Core infrastructure: 4 files (hook, export, account UI, bug fix)
1874+
- Page input fields: 5 files (file creation, project creation, search inputs, command palette)
1875+
1876+
**Component Changes**:
1877+
1878+
- `use-auto-focus-preference.ts` (Created) - Centralized preference hook
1879+
- `account-preferences-appearance.tsx` - New UI switch control
1880+
- `search-input.tsx` - **Bug fix**: Fixed focus prop reference
1881+
- 5 input components updated with conditional autoFocus based on preference
1882+
1883+
### Phase 24: Editor Content Landmark Navigation
1884+
1885+
- **Status**: Planned, not yet implemented
1886+
- **Approach**: Two-step interaction pattern
1887+
- Step 1: Alt+Shift+M navigates to Content landmark (announces file)
1888+
- Step 2: Return key while on landmark focuses the editor
1889+
- Alternative: Tab key navigates into editable content
1890+
- **Expected files to modify**: 5 core files (page.tsx, frame-tree.tsx, editor.tsx, aria.tsx, CSS)
1891+
- **Testing checklist**: 10 verification points prepared
1892+
1893+
### Next Steps
16211894

1622-
- Main project workspace layout
1623-
- Activity bar with tab semantics
1624-
- File tabs navigation
1625-
- Content area routing
1895+
**Phase 24: Editor Content Landmark Navigation** - Ready to implement
16261896

1627-
**Phase 12: App Shell & Navigation** - Pending
1897+
- Make Content region focusable with tabindex="0"
1898+
- Add Return key handler to focus primary editor
1899+
- Add visual feedback for keyboard users
1900+
- Test with all editor types
16281901

1629-
- Top-level navigation structure
1630-
- Connection status indicators
1631-
- Notification management
1902+
**Future Phases**:
16321903

1633-
**Phase 13+**: Form fields, tables, modals, etc.
1904+
- Phase 25: Additional keyboard shortcuts and editor conveniences
1905+
- Phase 26+: Form fields, tables, modals, etc.

0 commit comments

Comments
 (0)