Skip to content

Commit 252045b

Browse files
committed
refactor(hooks): split the drag's resize target from its other var consumers
getTarget briefly accepted a list, which made the first entry both the resized element and the drag's liveness reference. A toast auto-dismisses after 5s, so had one ever led that list, its mid-drag unmount would have read as the drag target detaching and skipped the final recompute on release. The co-consumers now come through getExtraTargets, which is written but never consulted for liveness, and can come and go freely.
1 parent 9ffd45c commit 252045b

3 files changed

Lines changed: 51 additions & 43 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/hooks/use-panel-resize.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ function computePanelWidth(ev: PointerEvent): number {
1313
return Math.min(Math.max(newWidth, PANEL_WIDTH.MIN), maxWidth)
1414
}
1515

16+
/** The `.panel-container` element sizes itself from `--panel-width`. */
17+
function getPanelContainer(): HTMLElement | null {
18+
return document.querySelector<HTMLElement>('.panel-container')
19+
}
20+
1621
/**
17-
* Every subtree that reads `--panel-width`: the `.panel-container` the drag
18-
* resizes, and the toast stack, which insets its right edge by the same
19-
* variable but is portalled to `<body>` and so shares no ancestor with it.
20-
* Writing both keeps the notifications tracking the drag frame by frame instead
21-
* of jumping once it commits.
22+
* The toast stack also insets its right edge by `--panel-width`, but is
23+
* portalled to `<body>` and so shares no ancestor with the panel. See
24+
* `use-terminal-resize.ts` for why this is written alongside the primary.
2225
*/
23-
function getPanelWidthConsumers(): (HTMLElement | null)[] {
24-
return [
25-
document.querySelector<HTMLElement>('.panel-container'),
26-
document.querySelector<HTMLElement>('[data-toast-viewport]'),
27-
]
26+
function getToastViewport(): (HTMLElement | null)[] {
27+
return [document.querySelector<HTMLElement>('[data-toast-viewport]')]
2828
}
2929

3030
/**
@@ -42,7 +42,8 @@ export function usePanelResize() {
4242
return useDragResize({
4343
cursor: 'ew-resize',
4444
cssVar: '--panel-width',
45-
getTarget: getPanelWidthConsumers,
45+
getTarget: getPanelContainer,
46+
getExtraTargets: getToastViewport,
4647
compute: computePanelWidth,
4748
commit: setPanelWidth,
4849
})

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/hooks/use-terminal-resize.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,20 @@ function computeTerminalHeight(ev: PointerEvent): number {
1313
return Math.min(Math.max(newHeight, TERMINAL_HEIGHT.MIN), maxHeight)
1414
}
1515

16+
/** The `.terminal-container` element sizes itself from `--terminal-height`. */
17+
function getTerminalContainer(): HTMLElement | null {
18+
return document.querySelector<HTMLElement>('.terminal-container')
19+
}
20+
1621
/**
17-
* Every subtree that reads `--terminal-height`: the `.terminal-container` the
18-
* drag resizes, and the toast stack, which insets its bottom by the same
19-
* variable but is portalled to `<body>` and so shares no ancestor with it.
20-
* Writing both keeps the notifications tracking the drag frame by frame instead
21-
* of jumping once it commits.
22+
* The toast stack also insets its bottom by `--terminal-height`, but is
23+
* portalled to `<body>` and so shares no ancestor with the terminal. Writing it
24+
* alongside keeps the notifications tracking the drag frame by frame instead of
25+
* holding their pre-drag position until it commits. Usually absent — the stack
26+
* only mounts while a toast is showing — in which case nothing extra is written.
2227
*/
23-
function getTerminalHeightConsumers(): (HTMLElement | null)[] {
24-
return [
25-
document.querySelector<HTMLElement>('.terminal-container'),
26-
document.querySelector<HTMLElement>('[data-toast-viewport]'),
27-
]
28+
function getToastViewport(): (HTMLElement | null)[] {
29+
return [document.querySelector<HTMLElement>('[data-toast-viewport]')]
2830
}
2931

3032
/**
@@ -58,7 +60,8 @@ export function useTerminalResize() {
5860
return useDragResize({
5961
cursor: 'ns-resize',
6062
cssVar: '--terminal-height',
61-
getTarget: getTerminalHeightConsumers,
63+
getTarget: getTerminalContainer,
64+
getExtraTargets: getToastViewport,
6265
compute: computeTerminalHeight,
6366
commit: setTerminalHeight,
6467
onApply: syncExpandedThreshold,

apps/sim/hooks/use-drag-resize.ts

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,30 @@ interface UseDragResizeOptions {
99
*/
1010
cssVar: string
1111
/**
12-
* Returns the element that consumes {@link cssVar} (or an ancestor of every
13-
* consumer). During the drag the variable is written here — a style recalc
14-
* scoped to that subtree — instead of on `:root`, where on a large document
15-
* every custom-property write recalculates the whole tree (~150x slower).
16-
* Captured once on drag start; a `null` return falls back to
17-
* `document.documentElement`.
12+
* Returns the element the drag resizes, which is also the subtree
13+
* {@link cssVar} is written to during it — a style recalc scoped to that
14+
* subtree, instead of `:root`, where on a large document every
15+
* custom-property write recalculates the whole tree (~150x slower). Captured
16+
* once on drag start; a `null` return falls back to
17+
* `document.documentElement`. This element is also the drag's liveness
18+
* reference: once it detaches, the release stops recomputing from layout.
19+
*/
20+
getTarget: () => HTMLElement | null
21+
/**
22+
* Other subtrees that read {@link cssVar} but are not what the drag resizes —
23+
* the toast stack insets by `--panel-width`/`--terminal-height` yet is
24+
* portalled to `<body>`, so it shares no ancestor with either. Each is
25+
* written alongside the primary, which keeps the recalc scoped AND keeps
26+
* these consumers tracking the drag; one left off here reads the stale
27+
* `:root` value and only catches up when the drag commits.
1828
*
19-
* Return an ARRAY when consumers live in sibling subtrees with no useful
20-
* common ancestor — the toast stack is portalled to `<body>`, so it shares
21-
* one only with `:root`. Writing each subtree separately keeps the scoped
22-
* recalc and, more importantly, keeps those consumers tracking the drag live;
23-
* a consumer left off this list reads the stale `:root` value and only
24-
* catches up when the drag commits. Elements that are absent (`null`) or
25-
* repeated are ignored.
29+
* Deliberately separate from {@link getTarget} rather than one list: these
30+
* come and go independently of the drag (a toast auto-dismisses mid-drag),
31+
* so they must never become the liveness reference. Absent (`null`) or
32+
* duplicate elements are ignored, and writing to one that detaches mid-drag
33+
* is harmless.
2634
*/
27-
getTarget: () => HTMLElement | null | (HTMLElement | null)[]
35+
getExtraTargets?: () => (HTMLElement | null)[]
2836
/**
2937
* Maps a pointer position to the clamped target dimension, or `null` to
3038
* ignore the move. Runs at most once per animation frame (before the write,
@@ -98,13 +106,9 @@ export function useDragResize(options: UseDragResizeOptions) {
98106
const handle = e.currentTarget
99107
const pointerId = e.pointerId
100108
const { cssVar } = optionsRef.current
101-
const resolved = optionsRef.current.getTarget()
102-
const targets = [
103-
...new Set((Array.isArray(resolved) ? resolved : [resolved]).filter((el) => el !== null)),
104-
]
105-
if (targets.length === 0) targets.push(document.documentElement)
106-
/** Liveness is judged on the primary target — the one the drag resizes. */
107-
const target = targets[0]
109+
const target = optionsRef.current.getTarget() ?? document.documentElement
110+
const extras = optionsRef.current.getExtraTargets?.() ?? []
111+
const targets = [...new Set([target, ...extras.filter((el) => el !== null)])]
108112
document.body.style.cursor = optionsRef.current.cursor
109113
document.body.style.userSelect = 'none'
110114
handle.setPointerCapture?.(pointerId)

0 commit comments

Comments
 (0)