Force active-border via direct DOM manipulation in polling loop
Svelte 5's template reactivity on \`class:active={activeLeafId === leaf.id}\`
in LeafPane did NOT propagate when the activeLeafId prop changed in
this app — verified via debug overlays showing the App-level state
updating correctly but the per-pane border never moving. Root cause
unclear (possibly the recursive Pane structure interacting badly with
the 250ms polling \$effect's re-runs, or a Svelte 5 corner case in
class-binding tracking through deeply-drilled props).
Workaround: the polling loop that detects focus changes now ALSO
walks document.querySelectorAll("[data-leaf-id].leaf") on every tick
and directly toggles the .active class via element.classList. If
Svelte re-renders and reverts, the next 250ms tick puts it back.
App-level activeLeafId is still drilled as a prop (used elsewhere) and
orch keeps its delegated setActive/clearActiveIf hooks, but the
visible border is owned by the DOM-direct path. Verified working with
PowerShell+Win32 click automation: clicking pane 2 moves the border
to pane 2, clicking pane 1 moves it back.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f5f788652e
commit
854201be84
5 changed files with 80 additions and 40 deletions
|
|
@ -43,6 +43,18 @@
|
|||
let tree = $state<TreeNode>(newLeaf());
|
||||
let paletteOpen = $state(false);
|
||||
|
||||
// activeLeafId lives here (not on the orch class) because Svelte 5 didn't
|
||||
// reliably track class-field $state reads from child components that
|
||||
// obtained the orch instance via getContext. Local $state drilled via
|
||||
// prop works.
|
||||
let activeLeafId = $state<NodeId | null>(null);
|
||||
function setActive(id: NodeId) {
|
||||
activeLeafId = id;
|
||||
}
|
||||
function clearActiveIf(id: NodeId) {
|
||||
if (activeLeafId === id) activeLeafId = null;
|
||||
}
|
||||
|
||||
// ---- tree mutation handlers (closures over tree $state) -----------------
|
||||
function handleSplit(leafId: NodeId, orientation: Orientation) {
|
||||
const parent = findLeaf(tree, leafId);
|
||||
|
|
@ -55,7 +67,7 @@
|
|||
function handleClose(leafId: NodeId) {
|
||||
const next = closeLeaf(tree, leafId);
|
||||
tree = next ?? newLeaf({ distro: defaultDistro });
|
||||
orch.clearActiveIf(leafId);
|
||||
clearActiveIf(leafId);
|
||||
}
|
||||
|
||||
function handleSetDistro(leafId: NodeId, distro: string) {
|
||||
|
|
@ -100,6 +112,7 @@
|
|||
// Provide the orchestration store. All Pane / SplitNode / LeafPane
|
||||
// descendants consume it via `useOrchestration()` — no prop drilling.
|
||||
const orch = provideOrchestration(treeOps);
|
||||
orch.configureActiveHandlers(setActive, clearActiveIf);
|
||||
|
||||
function isInteractiveDistro(name: string): boolean {
|
||||
return !name.toLowerCase().startsWith("docker-desktop");
|
||||
|
|
@ -185,25 +198,28 @@
|
|||
});
|
||||
|
||||
// ---- Active-pane detector via active-element polling --------------------
|
||||
// Tried (and failed in WebView2): per-leaf onpointerdown (xterm blocks
|
||||
// propagation), document-capture pointerdown (Webview2 only delivers the
|
||||
// first one then nothing), document-capture focusin (also silently fails),
|
||||
// xterm.js term.onFocus (no such API), textarea focus listener (race).
|
||||
//
|
||||
// Polling document.activeElement is the only thing left that's bulletproof
|
||||
// — no events involved at all. 250ms is fast enough to feel instant when
|
||||
// clicking and cheap enough to not show up on a CPU profile.
|
||||
// We tried letting Svelte handle class:active reactively in LeafPane, but
|
||||
// through this app's component chain the prop changes don't trigger a
|
||||
// template re-evaluation reliably (root cause unclear — likely a Svelte 5
|
||||
// interaction with our recursive Pane / setInterval pattern). So we ALSO
|
||||
// manipulate `.leaf.active` directly via DOM as a backstop.
|
||||
$effect(() => {
|
||||
let lastLeafId: string | null = null;
|
||||
const interval = window.setInterval(() => {
|
||||
const el = document.activeElement;
|
||||
if (!el) return;
|
||||
const leafEl = el.closest("[data-leaf-id]");
|
||||
if (!leafEl) return;
|
||||
const id = leafEl.getAttribute("data-leaf-id");
|
||||
const leafEl = el?.closest("[data-leaf-id]");
|
||||
const id = leafEl?.getAttribute("data-leaf-id") ?? null;
|
||||
if (id && id !== lastLeafId) {
|
||||
lastLeafId = id;
|
||||
orch.setActive(id);
|
||||
setActive(id);
|
||||
}
|
||||
// Unconditionally re-assert the DOM state every tick — if Svelte
|
||||
// re-renders and reverts our class change, the next tick puts it back.
|
||||
if (id) {
|
||||
document.querySelectorAll("[data-leaf-id].leaf").forEach((el) => {
|
||||
if (el.getAttribute("data-leaf-id") === id) el.classList.add("active");
|
||||
else el.classList.remove("active");
|
||||
});
|
||||
}
|
||||
}, 250);
|
||||
return () => clearInterval(interval);
|
||||
|
|
@ -225,7 +241,7 @@
|
|||
});
|
||||
|
||||
function onPalettePick(leafId: string) {
|
||||
orch.setActive(leafId);
|
||||
setActive(leafId);
|
||||
paletteOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
|
@ -273,7 +289,7 @@
|
|||
|
||||
<div class="pane-wrap">
|
||||
{#if ready}
|
||||
<Pane node={tree} />
|
||||
<Pane node={tree} {activeLeafId} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue