Skip to content

Commit 8fc4dc3

Browse files
authored
special first click handling for macos. cancel the click but set block/waveai focus (#3098)
1 parent 7540b13 commit 8fc4dc3

File tree

5 files changed

+89
-6
lines changed

5 files changed

+89
-6
lines changed

emain/emain-window.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2025, Command Line Inc.
1+
// Copyright 2026, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

44
import { ClientService, ObjectService, WindowService, WorkspaceService } from "@/app/store/services";
@@ -167,6 +167,7 @@ export class WaveBrowserWindow extends BaseWindow {
167167
winOpts.titleBarStyle = "hiddenInset";
168168
winOpts.titleBarOverlay = false;
169169
winOpts.autoHideMenuBar = !settings?.["window:showmenubar"];
170+
winOpts.acceptFirstMouse = true;
170171
if (isTransparent) {
171172
winOpts.transparent = true;
172173
} else if (isBlur) {

frontend/app/aipanel/aipanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps
571571
onDrop={handleDrop}
572572
onClick={handleClick}
573573
inert={!isPanelVisible ? true : undefined}
574+
data-aipanel="true"
574575
>
575576
<ConfigChangeModeFixer />
576577
{(isDragOver || isReactDndDragOver) && allowAccess && <AIDragOverlay />}

frontend/app/app.tsx

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getBlockBadgeAtom,
99
} from "@/app/store/badge";
1010
import { ClientModel } from "@/app/store/client-model";
11+
import { FocusManager } from "@/app/store/focusManager";
1112
import { GlobalModel } from "@/app/store/global-model";
1213
import { globalStore } from "@/app/store/jotaiStore";
1314
import { getTabModelByTabId, TabModelContext } from "@/app/store/tab-model";
@@ -16,7 +17,7 @@ import { makeWaveEnvImpl } from "@/app/waveenv/waveenvimpl";
1617
import { Workspace } from "@/app/workspace/workspace";
1718
import { getLayoutModelForStaticTab } from "@/layout/index";
1819
import { ContextMenuModel } from "@/store/contextmenu";
19-
import { atoms, createBlock, getSettingsPrefixAtom } from "@/store/global";
20+
import { atoms, createBlock, getSettingsPrefixAtom, refocusNode } from "@/store/global";
2021
import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel";
2122
import { getElemAsStr } from "@/util/focusutil";
2223
import * as keyutil from "@/util/keyutil";
@@ -203,6 +204,83 @@ function AppFocusHandler() {
203204
return null;
204205
}
205206

207+
const MacOSFirstClickHandler = () => {
208+
useEffect(() => {
209+
if (PLATFORM !== "darwin") {
210+
return;
211+
}
212+
let windowFocusTime: number = null;
213+
let cancelNextClick = false;
214+
const handleWindowFocus = (e: FocusEvent) => {
215+
windowFocusTime = Date.now();
216+
};
217+
const getBlockIdFromTarget = (target: EventTarget): string => {
218+
let elem = target as HTMLElement;
219+
while (elem != null) {
220+
const blockId = elem.dataset?.blockid;
221+
if (blockId) {
222+
return blockId;
223+
}
224+
elem = elem.parentElement;
225+
}
226+
return null;
227+
};
228+
const isAIPanelTarget = (target: EventTarget): boolean => {
229+
let elem = target as HTMLElement;
230+
while (elem != null) {
231+
if (elem.dataset?.aipanel) {
232+
return true;
233+
}
234+
elem = elem.parentElement;
235+
}
236+
return false;
237+
};
238+
const handleMouseDown = (e: MouseEvent) => {
239+
const timeDiff = Date.now() - windowFocusTime;
240+
if (windowFocusTime != null && timeDiff < 50) {
241+
e.preventDefault();
242+
e.stopPropagation();
243+
e.stopImmediatePropagation();
244+
cancelNextClick = true;
245+
const blockId = getBlockIdFromTarget(e.target);
246+
if (blockId != null) {
247+
setTimeout(() => {
248+
console.log("macos first-click, focusing block", blockId);
249+
refocusNode(blockId);
250+
}, 10);
251+
} else if (isAIPanelTarget(e.target)) {
252+
setTimeout(() => {
253+
console.log("macos first-click, focusing AI panel");
254+
FocusManager.getInstance().setWaveAIFocused(true);
255+
}, 10);
256+
}
257+
console.log("macos first-click detected, canceled", timeDiff + "ms");
258+
return;
259+
}
260+
cancelNextClick = false;
261+
};
262+
const handleClick = (e: MouseEvent) => {
263+
if (!cancelNextClick) {
264+
return;
265+
}
266+
cancelNextClick = false;
267+
e.preventDefault();
268+
e.stopPropagation();
269+
e.stopImmediatePropagation();
270+
console.log("macos first-click (click event) canceled");
271+
};
272+
window.addEventListener("focus", handleWindowFocus);
273+
window.addEventListener("mousedown", handleMouseDown, true);
274+
window.addEventListener("click", handleClick, true);
275+
return () => {
276+
window.removeEventListener("focus", handleWindowFocus);
277+
window.removeEventListener("mousedown", handleMouseDown, true);
278+
window.removeEventListener("click", handleClick, true);
279+
};
280+
}, []);
281+
return null;
282+
};
283+
206284
const AppKeyHandlers = () => {
207285
useEffect(() => {
208286
const staticKeyDownHandler = keyutil.keydownWrapper(appHandleKeyDown);
@@ -300,6 +378,7 @@ const AppInner = () => {
300378
onContextMenu={handleContextMenu}
301379
>
302380
<AppBackground />
381+
<MacOSFirstClickHandler />
303382
<AppKeyHandlers />
304383
<AppFocusHandler />
305384
<AppSettingsUpdater />

frontend/app/store/focusManager.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
// Copyright 2026, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
14
import { waveAIHasFocusWithin } from "@/app/aipanel/waveai-focus-utils";
25
import { WaveAIModel } from "@/app/aipanel/waveai-model";
3-
import { atoms, getBlockComponentModel } from "@/app/store/global";
6+
import { getBlockComponentModel } from "@/app/store/global";
47
import { globalStore } from "@/app/store/jotaiStore";
5-
import { focusedBlockId } from "@/util/focusutil";
68
import { getLayoutModelForStaticTab } from "@/layout/index";
9+
import { focusedBlockId } from "@/util/focusutil";
710
import { Atom, atom, type PrimitiveAtom } from "jotai";
811

912
export type FocusStrType = "node" | "waveai";

frontend/app/store/global.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ function useBlockAtom<T>(blockId: string, name: string, makeFn: () => Atom<T>):
319319
if (atom == null) {
320320
atom = makeFn();
321321
blockCache.set(name, atom);
322-
console.log("New BlockAtom", blockId, name);
323322
}
324323
return atom as Atom<T>;
325324
}
@@ -666,8 +665,8 @@ export {
666665
getApi,
667666
getBlockComponentModel,
668667
getBlockMetaKeyAtom,
669-
getConnConfigKeyAtom,
670668
getBlockTermDurableAtom,
669+
getConnConfigKeyAtom,
671670
getConnStatusAtom,
672671
getFocusedBlockId,
673672
getHostName,

0 commit comments

Comments
 (0)