Skip to content

Commit c758b0a

Browse files
committed
frontend/aria: Fix top navigation layout and note about zooming, debugging
1 parent ed156c4 commit c758b0a

File tree

8 files changed

+93
-41
lines changed

8 files changed

+93
-41
lines changed

src/dev/ARIA.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,4 +414,13 @@ SortableTabs role="tablist"
414414

415415
1. Decide on approach to fix aria-required-parent/children (requires deeper restructuring)
416416

417-
**Future**: 2. **DEFER**: color-contrast (8+ items) - plan custom antd theme with accessibility option
417+
**Future**:
418+
419+
2. **DEFER**: color-contrast (8+ items) - plan custom antd theme with accessibility option
420+
421+
3. **DEFER**: Browser zoom scaling issue - When users enable zoom (`user-scalable=yes`), the page scales but doesn't properly overflow/scroll. Changed PAGE_STYLE width from `100vw` to `100%`, but issue persists. Need to investigate:
422+
- Whether content is actually overflowing or being reflowed
423+
- If parent containers (html/body) need explicit overflow handling
424+
- Possible interactions with fixed positioning elements (nav bar, sidebars)
425+
- Test on mobile vs desktop browsers
426+
- May require restructuring how viewport constraints are applied

src/packages/frontend/account/account-page.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -501,16 +501,6 @@ export const AccountPage: React.FC = () => {
501501
</nav>
502502
<div
503503
className="smc-vfill"
504-
role="region"
505-
aria-label={
506-
active_page === "preferences" && active_sub_tab
507-
? `${
508-
titles[active_sub_tab]?.props?.children?.[1] || active_sub_tab
509-
} settings`
510-
: `${
511-
titles[active_page]?.props?.children?.[1] || active_page
512-
} settings`
513-
}
514504
style={{
515505
overflow: "auto",
516506
paddingLeft: "15px",
@@ -532,7 +522,7 @@ export const AccountPage: React.FC = () => {
532522
}
533523

534524
return (
535-
<div className="smc-vfill" role="main" aria-label="Account page">
525+
<div className="smc-vfill">
536526
{is_logged_in && !get_api_key ? (
537527
render_logged_in_view()
538528
) : (

src/packages/frontend/app/hotkey/dialog.tsx

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,16 @@ export const QuickNavigationDialog: React.FC<QuickNavigationDialogProps> = ({
588588

589589
// Keyboard handler - handle number shortcuts and navigation
590590
const handleKeyDown = async (e: React.KeyboardEvent) => {
591+
// Only handle keyboard events when dialog is visible
592+
if (!visible) {
593+
return;
594+
}
595+
596+
const blockEvent = () => {
597+
e.preventDefault();
598+
e.stopPropagation();
599+
};
600+
591601
// Number shortcuts (0-9) - always available to jump to frames
592602
const num = parseInt(e.key, 10);
593603
if (!isNaN(num) && num >= 0 && num <= 9) {
@@ -597,25 +607,15 @@ export const QuickNavigationDialog: React.FC<QuickNavigationDialogProps> = ({
597607
// Check if chat is already open for this file
598608
const fileInfo = open_files?.get(activeFileName);
599609
const chatState = fileInfo?.get("chatState");
600-
const isChatOpen = !!chatState; // truthy chatState means chat is open
601-
602-
if (DEBUG) {
603-
console.log("Chat toggle key 0 pressed:", {
604-
activeFileName,
605-
activeProjectId,
606-
fileInfo: fileInfo?.toJS?.() || fileInfo,
607-
chatState,
608-
isChatOpen,
609-
});
610-
}
611610

612611
// Toggle chat using shared function
613612
toggleChat(activeProjectId, activeFileName, chatState, "hotkey-0");
614613

615614
onClose();
616-
e.preventDefault();
615+
blockEvent();
617616
return;
618617
}
618+
blockEvent();
619619
return;
620620
}
621621

@@ -630,14 +630,17 @@ export const QuickNavigationDialog: React.FC<QuickNavigationDialogProps> = ({
630630

631631
if (targetFrameId) {
632632
await handleFrameClick(targetFrameId);
633-
e.preventDefault();
633+
blockEvent();
634634
return;
635635
}
636+
blockEvent();
637+
return;
636638
}
637639

638640
// Arrow keys and Return - navigate through filtered leaf nodes only
639641
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
640642
if (!searchValue || filteredSearchList.length === 0) {
643+
blockEvent();
641644
return;
642645
}
643646
// filteredSearchList contains only leaf nodes matching the search (if any)
@@ -670,18 +673,18 @@ export const QuickNavigationDialog: React.FC<QuickNavigationDialogProps> = ({
670673
});
671674
}
672675
}
673-
e.preventDefault();
676+
blockEvent();
674677
} else if (e.key === "Enter") {
675678
// Activate selected node
676679
const node = filteredSearchList.find((item) => item.key === selectedKey);
677680
if (node?.node.navigationData) {
678681
await triggerAction(node.node.navigationData.action);
679682
onClose();
680683
}
681-
e.preventDefault();
684+
blockEvent();
682685
} else if (e.key === "Escape") {
683686
onClose();
684-
e.preventDefault();
687+
blockEvent();
685688
}
686689
};
687690

@@ -798,6 +801,7 @@ export const QuickNavigationDialog: React.FC<QuickNavigationDialogProps> = ({
798801
"Placeholder text for the search input in quick navigation",
799802
})}
800803
onChange={handleSearch}
804+
onKeyDown={handleKeyDown}
801805
value={searchValue}
802806
autoFocus
803807
allowClear

src/packages/frontend/app/hotkey/util.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,30 @@ const DOM_ONLY_EDITOR_TYPES = new Set([
5959
"latex_table_of_contents",
6060
]);
6161

62+
// Editors that still need DOM focus even after their internal focus()
63+
// logic runs (e.g., to move keyboard events to the correct frame).
64+
const NEEDS_DOM_FOCUS_EDITOR_TYPES = new Set(["jupyter_cell_notebook"]);
65+
6266
interface FocusOptions {
6367
editorType?: string;
6468
}
6569

70+
function isDomOnlyEditorType(editorType?: string): boolean {
71+
if (!editorType) {
72+
return false;
73+
}
74+
if (DOM_ONLY_EDITOR_TYPES.has(editorType)) {
75+
return true;
76+
}
77+
if (editorType.startsWith("pdf")) {
78+
return true;
79+
}
80+
if (editorType.endsWith("_preview")) {
81+
return true;
82+
}
83+
return false;
84+
}
85+
6686
/**
6787
* Decide whether we should rely on the editor actions' focus() method for a frame.
6888
* TODO: detect this from frame/editor metadata instead of a hard-coded list.
@@ -71,16 +91,17 @@ function shouldUseEditorFocus(editorType?: string): boolean {
7191
if (!editorType) {
7292
return true;
7393
}
74-
if (DOM_ONLY_EDITOR_TYPES.has(editorType)) {
75-
return false;
76-
}
77-
if (editorType.startsWith("pdf")) {
94+
return !isDomOnlyEditorType(editorType);
95+
}
96+
97+
function needsDomFocus(editorType?: string): boolean {
98+
if (!editorType) {
7899
return false;
79100
}
80-
if (editorType.endsWith("_preview")) {
81-
return false;
101+
if (NEEDS_DOM_FOCUS_EDITOR_TYPES.has(editorType)) {
102+
return true;
82103
}
83-
return true;
104+
return isDomOnlyEditorType(editorType);
84105
}
85106

86107
/**
@@ -140,9 +161,10 @@ export function focusFrameWithRetry(
140161
return false;
141162
};
142163

164+
const requireDomFocus = needsDomFocus(options?.editorType);
143165
setTimeout(() => {
144166
const handled = focusEditor();
145-
if (!handled) {
167+
if (!handled || requireDomFocus) {
146168
focusDomFallback();
147169
}
148170
}, 50);

src/packages/frontend/app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ const PAGE_STYLE: CSS = {
8080
display: "flex",
8181
flexDirection: "column",
8282
height: PAGE_HEIGHT, // see note
83-
width: "100vw",
84-
overflow: "hidden",
83+
width: "100%",
84+
overflow: "auto",
8585
background: "white",
8686
} as const;
8787

src/packages/frontend/app/use-context.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,12 @@ export function calcStyle(isNarrow: boolean): PageStyle {
6565
const height = isNarrow ? NAV_HEIGHT_NARROW_PX : NAV_HEIGHT_PX;
6666

6767
const topBarStyle = {
68+
display: "flex",
69+
alignItems: "center",
70+
gap: "5px",
6871
height: `${height}px`,
6972
background: "#fafafa",
73+
width: "100vw",
7074
} as const;
7175

7276
const fileUseStyle = {
@@ -97,6 +101,8 @@ export function calcStyle(isNarrow: boolean): PageStyle {
97101
} as const)
98102
: ({
99103
flex: "1 1 auto", // necessary to stretch out to the full width
104+
minWidth: 0, // allow tabs to shrink below content size
105+
overflow: "hidden", // prevent tabs from overflowing the nav
100106
} as const);
101107

102108
return {

src/packages/frontend/frame-editors/frame-tree/frame-tree.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ import { FrameTitleBar } from "./title-bar";
5656
import * as tree_ops from "./tree-ops";
5757
import { EditorDescription, EditorSpec, EditorState, NodeDesc } from "./types";
5858

59+
// Editors that must receive a raw Enter key (e.g., Jupyter notebooks) so they can
60+
// handle mode changes like entering edit mode themselves.
61+
const ENTER_HANDLER_SKIP_TYPES = ["jupyter_cell_notebook"] as const;
62+
5963
interface FrameTreeProps {
6064
actions: Actions; // The Actions object for dispatching state changes
6165
active_id: string; // ID of the currently active frame
@@ -395,8 +399,22 @@ export const FrameTree: React.FC<FrameTreeProps> = React.memo(
395399
}
396400

397401
function handleFrameKeyDown(e: React.KeyboardEvent) {
398-
// Return/Enter: focus the editor content within this frame
399-
if (e.key === "Enter") {
402+
// Only handle a plain Enter pressed directly on the frame container.
403+
// Let modified Enter keys (Shift/Ctrl/Cmd/Alt) and descendant inputs
404+
// handle the event themselves so notebook shortcuts keep working.
405+
const isPlainEnter =
406+
e.key === "Enter" &&
407+
!e.shiftKey &&
408+
!e.ctrlKey &&
409+
!e.metaKey &&
410+
!e.altKey;
411+
const canHandleEnter =
412+
!ENTER_HANDLER_SKIP_TYPES.includes(type) &&
413+
isPlainEnter &&
414+
!e.defaultPrevented &&
415+
e.target === e.currentTarget;
416+
if (canHandleEnter) {
417+
// Return/Enter: focus the editor content within this frame
400418
e.preventDefault();
401419
e.stopPropagation();
402420
const frameContainer = e.currentTarget as HTMLElement;

src/packages/static/src/meta.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ export default function Meta() {
88
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
99
<meta name="apple-mobile-web-app-title" content="CoCalc" />
1010
<meta name="theme-color" content="#fbb635" />
11-
<meta name="viewport" content="width=device-width,initial-scale=1" />
11+
<meta
12+
name="viewport"
13+
content="width=device-width,initial-scale=1,user-scalable=yes"
14+
/>
1215
<meta name="google" content="notranslate" />
1316
</>
1417
);

0 commit comments

Comments
 (0)