logancyang_obsidian-copilot/src/components/chat-components/attachChatViewLayoutObservers.ts
2026-05-28 16:00:38 -07:00

79 lines
2.8 KiB
TypeScript

import { Platform } from "obsidian";
/**
* Mobile-keyboard + drawer-hide observers shared by chat views (regular and
* agent). Pure DOM/lifecycle utilities — no chat-specific state.
*
* On mobile, when the soft keyboard opens we want to hide drawer chrome
* (header/tab options) by toggling a class on the surrounding workspace
* drawer; and when the drawer is hidden by Obsidian (e.g. user pressed back),
* any open Radix popover anchored inside this view needs to close so it
* doesn't jump to (0,0).
*
* Returns:
* - `dispose`: call from the view's `onClose` to unbind everything.
* - `rebindDrawerObserver`: the view can be moved between drawers without
* `onOpen` firing again; call this from a `layout-change` handler so the
* drawer-hide observer always watches the correct drawer.
*/
export function attachChatViewLayoutObservers(containerEl: HTMLElement): {
dispose: () => void;
rebindDrawerObserver: () => void;
} {
if (!Platform.isMobile) {
return { dispose: () => {}, rebindDrawerObserver: () => {} };
}
let lastDrawerEl: HTMLElement | null = null;
let drawerHideObserver: MutationObserver | null = null;
const syncKeyboardClass = () => {
const drawer = containerEl.closest<HTMLElement>(".workspace-drawer");
if (lastDrawerEl && lastDrawerEl !== drawer) {
lastDrawerEl.classList.remove("copilot-keyboard-open");
}
lastDrawerEl = drawer;
if (!drawer) return;
const isCopilotActive = !!containerEl.closest(".workspace-drawer-active-tab-content");
const kbHeight = parseFloat(
containerEl.doc.documentElement.style.getPropertyValue("--keyboard-height") || "0"
);
drawer.classList.toggle("copilot-keyboard-open", isCopilotActive && kbHeight > 0);
};
const keyboardObserver = new MutationObserver(syncKeyboardClass);
keyboardObserver.observe(containerEl.doc.documentElement, {
attributes: true,
attributeFilter: ["style"],
});
syncKeyboardClass();
const rebindDrawerObserver = () => {
drawerHideObserver?.disconnect();
const drawer = containerEl.closest(".workspace-drawer");
if (!drawer) return;
let wasHidden = drawer.classList.contains("is-hidden");
drawerHideObserver = new MutationObserver(() => {
const isHidden = drawer.classList.contains("is-hidden");
if (isHidden && !wasHidden) {
containerEl.dispatchEvent(
new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true })
);
}
wasHidden = isHidden;
});
drawerHideObserver.observe(drawer, { attributes: true, attributeFilter: ["class"] });
};
rebindDrawerObserver();
const dispose = () => {
keyboardObserver.disconnect();
drawerHideObserver?.disconnect();
lastDrawerEl?.classList.remove("copilot-keyboard-open");
lastDrawerEl = null;
};
return { dispose, rebindDrawerObserver };
}