diff --git a/emain/emain-window.ts b/emain/emain-window.ts index 5f481e30f1..470ae7d968 100644 --- a/emain/emain-window.ts +++ b/emain/emain-window.ts @@ -1,4 +1,4 @@ -// Copyright 2025, Command Line Inc. +// Copyright 2026, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { ClientService, ObjectService, WindowService, WorkspaceService } from "@/app/store/services"; @@ -167,6 +167,7 @@ export class WaveBrowserWindow extends BaseWindow { winOpts.titleBarStyle = "hiddenInset"; winOpts.titleBarOverlay = false; winOpts.autoHideMenuBar = !settings?.["window:showmenubar"]; + winOpts.acceptFirstMouse = true; if (isTransparent) { winOpts.transparent = true; } else if (isBlur) { diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index 37c22709fe..b903380544 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -571,6 +571,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps onDrop={handleDrop} onClick={handleClick} inert={!isPanelVisible ? true : undefined} + data-aipanel="true" > {(isDragOver || isReactDndDragOver) && allowAccess && } diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 581b8c1a9f..e9c70a35df 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -8,6 +8,7 @@ import { getBlockBadgeAtom, } from "@/app/store/badge"; import { ClientModel } from "@/app/store/client-model"; +import { FocusManager } from "@/app/store/focusManager"; import { GlobalModel } from "@/app/store/global-model"; import { globalStore } from "@/app/store/jotaiStore"; import { getTabModelByTabId, TabModelContext } from "@/app/store/tab-model"; @@ -16,7 +17,7 @@ import { makeWaveEnvImpl } from "@/app/waveenv/waveenvimpl"; import { Workspace } from "@/app/workspace/workspace"; import { getLayoutModelForStaticTab } from "@/layout/index"; import { ContextMenuModel } from "@/store/contextmenu"; -import { atoms, createBlock, getSettingsPrefixAtom } from "@/store/global"; +import { atoms, createBlock, getSettingsPrefixAtom, refocusNode } from "@/store/global"; import { appHandleKeyDown, keyboardMouseDownHandler } from "@/store/keymodel"; import { getElemAsStr } from "@/util/focusutil"; import * as keyutil from "@/util/keyutil"; @@ -203,6 +204,83 @@ function AppFocusHandler() { return null; } +const MacOSFirstClickHandler = () => { + useEffect(() => { + if (PLATFORM !== "darwin") { + return; + } + let windowFocusTime: number = null; + let cancelNextClick = false; + const handleWindowFocus = (e: FocusEvent) => { + windowFocusTime = Date.now(); + }; + const getBlockIdFromTarget = (target: EventTarget): string => { + let elem = target as HTMLElement; + while (elem != null) { + const blockId = elem.dataset?.blockid; + if (blockId) { + return blockId; + } + elem = elem.parentElement; + } + return null; + }; + const isAIPanelTarget = (target: EventTarget): boolean => { + let elem = target as HTMLElement; + while (elem != null) { + if (elem.dataset?.aipanel) { + return true; + } + elem = elem.parentElement; + } + return false; + }; + const handleMouseDown = (e: MouseEvent) => { + const timeDiff = Date.now() - windowFocusTime; + if (windowFocusTime != null && timeDiff < 50) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + cancelNextClick = true; + const blockId = getBlockIdFromTarget(e.target); + if (blockId != null) { + setTimeout(() => { + console.log("macos first-click, focusing block", blockId); + refocusNode(blockId); + }, 10); + } else if (isAIPanelTarget(e.target)) { + setTimeout(() => { + console.log("macos first-click, focusing AI panel"); + FocusManager.getInstance().setWaveAIFocused(true); + }, 10); + } + console.log("macos first-click detected, canceled", timeDiff + "ms"); + return; + } + cancelNextClick = false; + }; + const handleClick = (e: MouseEvent) => { + if (!cancelNextClick) { + return; + } + cancelNextClick = false; + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + console.log("macos first-click (click event) canceled"); + }; + window.addEventListener("focus", handleWindowFocus); + window.addEventListener("mousedown", handleMouseDown, true); + window.addEventListener("click", handleClick, true); + return () => { + window.removeEventListener("focus", handleWindowFocus); + window.removeEventListener("mousedown", handleMouseDown, true); + window.removeEventListener("click", handleClick, true); + }; + }, []); + return null; +}; + const AppKeyHandlers = () => { useEffect(() => { const staticKeyDownHandler = keyutil.keydownWrapper(appHandleKeyDown); @@ -300,6 +378,7 @@ const AppInner = () => { onContextMenu={handleContextMenu} > + diff --git a/frontend/app/store/focusManager.ts b/frontend/app/store/focusManager.ts index e0c0c99832..58f78951ee 100644 --- a/frontend/app/store/focusManager.ts +++ b/frontend/app/store/focusManager.ts @@ -1,9 +1,12 @@ +// Copyright 2026, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + import { waveAIHasFocusWithin } from "@/app/aipanel/waveai-focus-utils"; import { WaveAIModel } from "@/app/aipanel/waveai-model"; -import { atoms, getBlockComponentModel } from "@/app/store/global"; +import { getBlockComponentModel } from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; -import { focusedBlockId } from "@/util/focusutil"; import { getLayoutModelForStaticTab } from "@/layout/index"; +import { focusedBlockId } from "@/util/focusutil"; import { Atom, atom, type PrimitiveAtom } from "jotai"; export type FocusStrType = "node" | "waveai"; diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 01d4ebbc96..92ffe7a59b 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -319,7 +319,6 @@ function useBlockAtom(blockId: string, name: string, makeFn: () => Atom): if (atom == null) { atom = makeFn(); blockCache.set(name, atom); - console.log("New BlockAtom", blockId, name); } return atom as Atom; } @@ -666,8 +665,8 @@ export { getApi, getBlockComponentModel, getBlockMetaKeyAtom, - getConnConfigKeyAtom, getBlockTermDurableAtom, + getConnConfigKeyAtom, getConnStatusAtom, getFocusedBlockId, getHostName,