Skip to content

Commit 02a67f1

Browse files
feat(devtools): reset splitter panes on double click (#515)
* feat(devtools): reset splitter panes on double click * feat(devtools): reset splitter panes on double click * Update .changeset/beige-snakes-call.md
1 parent 45f39ee commit 02a67f1

4 files changed

Lines changed: 101 additions & 0 deletions

File tree

‎.changeset/beige-snakes-call.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/devtools': patch
3+
---
4+
5+
Reset splitter panes on double-click

‎packages/devtools/src/components/plugin-workspace.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
findGroupOfTab,
3535
layoutRects,
3636
moveTab,
37+
resetSplit,
3738
resize,
3839
resizeFromPointer,
3940
setTabs,
@@ -600,6 +601,10 @@ export const PluginWorkspace = (props: {
600601
document.addEventListener('pointerup', up)
601602
}
602603

604+
const resetSplitterDrag = (handle: SplitterHandle) => {
605+
setLayout(resetSplit(layout(), handle.path, handle.gutterIndex))
606+
}
607+
603608
const resizeFromKeyboard = (handle: SplitterHandle, event: KeyboardEvent) => {
604609
const step = event.shiftKey ? KEYBOARD_STEP_COARSE : KEYBOARD_STEP
605610
const grows =
@@ -867,6 +872,7 @@ export const PluginWorkspace = (props: {
867872
// Read through the accessor at gesture time, so a gutter that has
868873
// been re-measured since render still moves the right sizes.
869874
onPointerDown={(event) => startSplitterDrag(handle(), event)}
875+
onDblClick={() => resetSplitterDrag(handle())}
870876
onKeyDown={(event) => resizeFromKeyboard(handle(), event)}
871877
/>
872878
)

‎packages/devtools/src/utils/layout-tree.test.ts‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
nextGroupId,
1414
paneRects,
1515
repairLayout,
16+
resetSplit,
1617
resize,
1718
resizeFromPointer,
1819
singleGroup,
@@ -354,6 +355,64 @@ describe('resize', () => {
354355
})
355356
})
356357

358+
describe('resetSplit', () => {
359+
it('evens out an unequal pair', () => {
360+
const tree = split(
361+
'row',
362+
[group('g0', ['a']), group('g1', ['b'])],
363+
[0.7, 0.3],
364+
)
365+
const next = resetSplit(tree, [], 0) as SplitNode
366+
expect(next.sizes[0]).toBeCloseTo(0.5, 10)
367+
expect(next.sizes[1]).toBeCloseTo(0.5, 10)
368+
expectWellFormed(next)
369+
})
370+
371+
it('only touches the gutter pair, leaving the rest alone', () => {
372+
const tree = split(
373+
'row',
374+
[group('g0', ['a']), group('g1', ['b']), group('g2', ['c'])],
375+
[0.1, 0.7, 0.2],
376+
)
377+
const next = resetSplit(tree, [], 0) as SplitNode
378+
expect(next.sizes[0]).toBeCloseTo(0.4, 10)
379+
expect(next.sizes[1]).toBeCloseTo(0.4, 10)
380+
expect(next.sizes[2]).toBeCloseTo(0.2, 10)
381+
expectWellFormed(next)
382+
})
383+
384+
it('resets a nested split by path', () => {
385+
const tree = split('row', [
386+
group('g0', ['a']),
387+
split('col', [group('g1', ['b']), group('g2', ['c'])], [0.8, 0.2]),
388+
])
389+
const next = resetSplit(tree, [1], 0) as SplitNode
390+
const inner = next.children[1] as SplitNode
391+
expect(inner.sizes[0]).toBeCloseTo(0.5, 10)
392+
expect(inner.sizes[1]).toBeCloseTo(0.5, 10)
393+
// The outer split is untouched.
394+
expect(next.sizes[0]).toBeCloseTo(0.5, 10)
395+
expectWellFormed(next)
396+
})
397+
398+
it('is a no-op when the pair is already even', () => {
399+
const tree = split(
400+
'row',
401+
[group('g0', ['a']), group('g1', ['b'])],
402+
[0.5, 0.5],
403+
)
404+
expect(resetSplit(tree, [], 0)).toEqual(tree)
405+
})
406+
407+
it('is a no-op for a bad path or gutter', () => {
408+
const tree = split('row', [group('g0', ['a']), group('g1', ['b'])])
409+
expect(resetSplit(tree, [9], 0)).toEqual(tree)
410+
expect(resetSplit(tree, [], 5)).toEqual(tree)
411+
expect(resetSplit(group('g0', ['a']), [], 0)).toEqual(group('g0', ['a']))
412+
expect(resetSplit(null, [], 0)).toBeNull()
413+
})
414+
})
415+
357416
describe('resizeFromPointer', () => {
358417
it('applies total mouse travel to the snapshot so later moves do not compound', () => {
359418
const tree = split(

‎packages/devtools/src/utils/layout-tree.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,37 @@ export const resize = (
427427
return replace(tree, 0)
428428
}
429429

430+
/** Evens out the two panes either side of a gutter, e.g. on splitter double-click. */
431+
export const resetSplit = (
432+
tree: LayoutNode | null,
433+
path: Path,
434+
gutterIndex: number,
435+
): LayoutNode | null => {
436+
const target = nodeAtPath(tree, path)
437+
if (tree === null || target === null || !isSplit(target)) return tree
438+
const before = target.sizes[gutterIndex]
439+
const after = target.sizes[gutterIndex + 1]
440+
if (before === undefined || after === undefined) return tree
441+
442+
const even = (before + after) / 2
443+
if (Math.abs(before - even) < EPSILON && Math.abs(after - even) < EPSILON) {
444+
return tree
445+
}
446+
447+
const sizes = [...target.sizes]
448+
sizes[gutterIndex] = even
449+
sizes[gutterIndex + 1] = even
450+
451+
const replace = (node: LayoutNode, depth: number): LayoutNode => {
452+
if (depth === path.length) return { ...(node as SplitNode), sizes }
453+
const index = path[depth]!
454+
const children = [...(node as SplitNode).children]
455+
children[index] = replace(children[index]!, depth + 1)
456+
return { ...(node as SplitNode), children }
457+
}
458+
return replace(tree, 0)
459+
}
460+
430461
/**
431462
* Apply a pointer drag to the layout as it was at pointer-down. `deltaPx` is
432463
* the total movement from that start, not a per-frame increment. Always pass

0 commit comments

Comments
 (0)