@jbrowse/app-core 5.0.0-beta.10 → 5.0.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/esm/HistoryManagement/index.d.ts +3 -0
  2. package/esm/JBrowseConfig/RootConfiguration.d.ts +1 -8
  3. package/esm/JBrowseConfig/RootConfiguration.js +2 -6
  4. package/esm/JBrowseConfig/index.d.ts +1 -8
  5. package/esm/JBrowseModel/index.d.ts +1 -8
  6. package/esm/JbApi/jbApi.d.ts +2 -2
  7. package/esm/JbApi/jbApi.js +12 -10
  8. package/esm/SessionSpec/loadSessionSpec.js +2 -12
  9. package/esm/WorkspaceLayout/LayoutRenderer.d.ts +0 -10
  10. package/esm/WorkspaceLayout/LayoutRenderer.js +34 -101
  11. package/esm/WorkspaceLayout/PanelView.js +19 -45
  12. package/esm/WorkspaceLayout/TabStrip.d.ts +2 -2
  13. package/esm/WorkspaceLayout/TabStrip.js +36 -104
  14. package/esm/WorkspaceLayout/WorkspaceContainer.d.ts +0 -9
  15. package/esm/WorkspaceLayout/WorkspaceContainer.js +9 -34
  16. package/esm/WorkspaceLayout/WorkspacePanelActions.d.ts +1 -16
  17. package/esm/WorkspaceLayout/WorkspacePanelActions.js +45 -50
  18. package/esm/WorkspaceLayout/WorkspaceTab.d.ts +1 -16
  19. package/esm/WorkspaceLayout/WorkspaceTab.js +11 -47
  20. package/esm/WorkspaceLayout/dropZone.d.ts +7 -37
  21. package/esm/WorkspaceLayout/dropZone.js +7 -44
  22. package/esm/WorkspaceLayout/model.d.ts +42 -112
  23. package/esm/WorkspaceLayout/model.js +139 -268
  24. package/esm/WorkspaceLayout/panelChrome.d.ts +4 -7
  25. package/esm/WorkspaceLayout/panelChrome.js +0 -2
  26. package/esm/WorkspaceLayout/spec.d.ts +10 -51
  27. package/esm/WorkspaceLayout/spec.js +12 -80
  28. package/esm/WorkspaceLayout/splitter.d.ts +3 -19
  29. package/esm/WorkspaceLayout/splitter.js +5 -21
  30. package/esm/WorkspaceLayout/tabName.d.ts +8 -0
  31. package/esm/WorkspaceLayout/tabName.js +14 -0
  32. package/esm/WorkspaceLayout/tree.d.ts +19 -76
  33. package/esm/WorkspaceLayout/tree.js +29 -122
  34. package/esm/WorkspaceLayout/useLayoutDrag.d.ts +4 -22
  35. package/esm/WorkspaceLayout/useLayoutDrag.js +46 -174
  36. package/esm/WorkspaceLayout/usePointerGesture.d.ts +24 -0
  37. package/esm/WorkspaceLayout/usePointerGesture.js +83 -0
  38. package/esm/WorkspaceLayout/workspaceTheme.d.ts +27 -0
  39. package/esm/WorkspaceLayout/workspaceTheme.js +37 -0
  40. package/esm/ui/App/App.js +3 -0
  41. package/esm/ui/App/JBrowseTabMenu.js +1 -1
  42. package/esm/ui/App/ThemeModeButton.js +5 -4
  43. package/esm/ui/App/ViewMenu.js +47 -29
  44. package/esm/ui/App/ViewsContainer.js +3 -4
  45. package/esm/ui/App/lazyParts.d.ts +0 -5
  46. package/esm/ui/App/lazyParts.js +0 -1
  47. package/esm/ui/App/preloadAppShell.js +3 -6
  48. package/esm/ui/App/types.d.ts +3 -2
  49. package/esm/ui/App/useViewVisibility.d.ts +1 -1
  50. package/esm/ui/App/useViewVisibility.js +1 -1
  51. package/package.json +5 -5
  52. package/esm/WorkspaceLayout/dockviewTheme.d.ts +0 -69
  53. package/esm/WorkspaceLayout/dockviewTheme.js +0 -82
  54. package/esm/ui/App/ClassicViewsContainer.d.ts +0 -6
  55. package/esm/ui/App/ClassicViewsContainer.js +0 -22
@@ -1,17 +1,14 @@
1
1
  import type { PanelNode, TabNode } from './tree.ts';
2
- import type { TabDragHandlers } from './useLayoutDrag.ts';
2
+ import type { PointerGestureHandlers } from './usePointerGesture.ts';
3
3
  /**
4
- * The app's half of a panel — what a tab is *called* and what it *contains*.
5
- *
6
- * One object rather than five props because every one is forwarded unchanged
7
- * down the whole recursion, and because one object can be memoised: it reaches
8
- * every panel, so a fresh one per render re-renders the entire workspace.
4
+ * The app's half of a panel, as one object so it can be memoised: it reaches
5
+ * every panel, and a fresh one re-renders the whole workspace.
9
6
  */
10
7
  export interface PanelChrome {
11
8
  renderTabLabel: (tab: TabNode) => React.ReactNode;
12
9
  renderTabContent: (tab: TabNode) => React.ReactNode;
13
10
  renderPanelActions?: (panel: PanelNode) => React.ReactNode;
14
- dragHandlers: TabDragHandlers;
11
+ dragHandlers: PointerGestureHandlers;
15
12
  /** middle-click; the caller pairs it with closing that tab's views */
16
13
  onTabClose?: (tabId: string) => void;
17
14
  }
@@ -1,4 +1,2 @@
1
- // The tablist wires `aria-controls`/`aria-labelledby` with these, and the strip
2
- // mints one end of each pair while the tabpanel mints the other.
3
1
  export const tabDomId = (tabId) => `jbrowse-tab-${tabId}`;
4
2
  export const tabPanelDomId = (panelId) => `jbrowse-tabpanel-${panelId}`;
@@ -6,15 +6,10 @@ import type { LayoutTree, NodeKind } from './tree.ts';
6
6
  */
7
7
  export type LayoutViewRef = number | string;
8
8
  /**
9
- * The `layout` a session spec / URL param states, in the vocabulary users
10
- * write it in, and the same shape the live `applyLayoutSpec` action takes — one
11
- * shape, so an agent that has written one has written the other. **This shape
12
- * is public** — it is documented as a URL parameter — so it keeps
13
- * `horizontal`/`vertical`/`tabs` and percentage `size` rather than being
14
- * renamed to match the internal tree.
15
- *
16
- * `resolveLayoutSpec` turns the indexes into ids; the tree builder below takes
17
- * the resolved form only.
9
+ * The `layout` a session spec or URL param states, and the shape
10
+ * `applyLayoutSpec` takes. Public, documented as a URL parameter, so it keeps
11
+ * `horizontal`/`vertical`/`tabs` and percentage `size` rather than the
12
+ * internal tree's vocabulary.
18
13
  */
19
14
  export interface LayoutSpecNode<View = LayoutViewRef> {
20
15
  /** a leaf: the views to stack vertically in one tab */
@@ -28,20 +23,11 @@ export interface LayoutSpecNode<View = LayoutViewRef> {
28
23
  export type ResolvedLayoutSpecNode = LayoutSpecNode<string>;
29
24
  /**
30
25
  * Resolve every view a spec names to an id against `allViewIds`, the list its
31
- * indexes count into. Anything that resolves to nothing throws, naming what was
32
- * received: an untyped caller (the MCP `run_javascript` tool) that writes
33
- * `viewIds` where `views` goes, or an index past the end, used to be accepted
34
- * as an empty leaf and collapse the workspace into one blank tab with nothing
35
- * said.
36
- *
37
- * A node stating no `views` and no `children` is NOT that slip — it is the
38
- * empty panel `treeFromSpec` has always built, and refusing it here made the
39
- * two surfaces disagree about the one shape they share. The slip is an
40
- * unrecognized KEY, which is what `viewIds` actually is, and naming it beats
41
- * the old message that could only say the node needed something else.
26
+ * indexes count into. An unknown key, index or id throws, naming what was
27
+ * received, so an untyped caller writing `viewIds` for `views` hears about it.
28
+ * A node with neither `views` nor `children` is a legal empty panel.
42
29
  *
43
- * `allViewIds` is undefined only on a model that composes no view list, where
44
- * an id cannot be checked and an index cannot mean anything.
30
+ * `allViewIds` is undefined only on a model that composes no view list.
45
31
  */
46
32
  export declare function resolveLayoutSpec(spec: LayoutSpecNode, allViewIds: string[] | undefined): ResolvedLayoutSpecNode;
47
33
  /** A request to move one view relative to the others. Public plugin API. */
@@ -49,40 +35,13 @@ export interface PendingMove {
49
35
  type: 'newTab' | 'splitRight';
50
36
  viewId: string;
51
37
  }
52
- /**
53
- * Build a layout tree from a spec.
54
- *
55
- * The old converter had to answer "which dockview group does a nested split
56
- * address?" and could not, because dockview forces orientation to alternate by
57
- * depth — which is why `size` was honoured only on the top-level split, and
58
- * only if every panel there carried one. Here the spec's nesting is the tree's
59
- * nesting, so `size` applies wherever it is written and there is no all-or-
60
- * nothing pass.
61
- */
62
38
  export declare function treeFromSpec(spec: ResolvedLayoutSpecNode, nextId: (kind: NodeKind) => string): LayoutTree;
63
39
  /** Every viewId a spec names, depth-first — the order it states. */
64
40
  export declare function viewIdsInSpec(spec: ResolvedLayoutSpecNode): string[];
65
41
  /** How "arrange everything" lays the whole session out. */
66
42
  export type TileMode = 'tabs' | 'horizontal' | 'vertical' | 'grid';
67
43
  /**
68
- * Re-arrange every view at once, one view per cell.
69
- *
70
- * The four whole-workspace commands, as a spec. dockview had these as imperative
71
- * re-tiling — walk every panel and re-add it relative to the first, in an order
72
- * chosen so the grid came out right — and the grid case had to compute its own
73
- * row/column arithmetic against `api.panels` because there was no way to state
74
- * the shape. Here the shape IS the statement, so a tiling is a spec and goes
75
- * through the same `applyLayoutSpec` a session spec does.
76
- *
77
- * `grid` fills row-major at ceil(sqrt(n)) columns, which is what the dockview
78
- * version computed; a trailing row shorter than the rest keeps its cells at full
79
- * width, since `resolveSizes` reads bare siblings as an even share of their own
80
- * branch rather than of the grid.
44
+ * One view per cell, as a spec. `grid` fills row-major at ceil(sqrt(n))
45
+ * columns; a short trailing row's cells stretch to full width.
81
46
  */
82
47
  export declare function tileLayoutSpec(viewIds: string[], mode: TileMode): ResolvedLayoutSpecNode;
83
- /**
84
- * `setPendingMove` as a spec: everything else keeps its side, the named view
85
- * takes the other. With nothing else on screen there is nothing to split from,
86
- * so the view just takes the space.
87
- */
88
- export declare function specForPendingMove(move: PendingMove, allViewIds: string[]): ResolvedLayoutSpecNode;
@@ -1,23 +1,12 @@
1
1
  import { normalize } from './tree.js';
2
- // `views`/`children` are pulled off by name; the rest pass through to the tree,
3
- // so an unrecognized one is a key the writer expected to mean something
4
2
  const layoutNodeKeys = new Set(['views', 'direction', 'children', 'size']);
5
3
  /**
6
4
  * Resolve every view a spec names to an id against `allViewIds`, the list its
7
- * indexes count into. Anything that resolves to nothing throws, naming what was
8
- * received: an untyped caller (the MCP `run_javascript` tool) that writes
9
- * `viewIds` where `views` goes, or an index past the end, used to be accepted
10
- * as an empty leaf and collapse the workspace into one blank tab with nothing
11
- * said.
5
+ * indexes count into. An unknown key, index or id throws, naming what was
6
+ * received, so an untyped caller writing `viewIds` for `views` hears about it.
7
+ * A node with neither `views` nor `children` is a legal empty panel.
12
8
  *
13
- * A node stating no `views` and no `children` is NOT that slip — it is the
14
- * empty panel `treeFromSpec` has always built, and refusing it here made the
15
- * two surfaces disagree about the one shape they share. The slip is an
16
- * unrecognized KEY, which is what `viewIds` actually is, and naming it beats
17
- * the old message that could only say the node needed something else.
18
- *
19
- * `allViewIds` is undefined only on a model that composes no view list, where
20
- * an id cannot be checked and an index cannot mean anything.
9
+ * `allViewIds` is undefined only on a model that composes no view list.
21
10
  */
22
11
  export function resolveLayoutSpec(spec, allViewIds) {
23
12
  const describe = (value) => typeof value === 'string' ? `"${value}"` : String(value);
@@ -73,22 +62,9 @@ export function resolveLayoutSpec(spec, allViewIds) {
73
62
  return resolved;
74
63
  }
75
64
  /**
76
- * The effective size of each child of one branch.
77
- *
78
- * `size` is documented as a **percentage**, so a spec that sizes some siblings
79
- * and leaves the rest bare means the rest to share what is left over: `70` and
80
- * blank is 70/30. Defaulting a bare sibling to 1 instead reads a weight against
81
- * a percentage, and the panel comes out at 1/71 of the width — present, about a
82
- * pixel wide, and reported by nothing.
83
- *
84
- * The mixed case used to be unreachable: dockview forced orientation to
85
- * alternate by depth, so the converter discarded any partially-sized branch
86
- * wholesale and raised a notification saying so. Nesting works now, which means
87
- * this has to mean something instead.
88
- *
89
- * All-sized and none-sized both stay plain weights — `normalize` renormalises
90
- * them, so `70`/`30` and `7`/`3` are the same layout and neither has to sum to
91
- * anything in particular.
65
+ * `size` is a percentage, so bare siblings share what the sized ones leave:
66
+ * `70` and blank is 70/30, not a 1/71 sliver. All-sized and none-sized stay
67
+ * plain weights for `normalize`.
92
68
  */
93
69
  function resolveSizes(children) {
94
70
  const stated = children.map(child => child.size);
@@ -100,21 +76,10 @@ function resolveSizes(children) {
100
76
  const remainder = 100 - claimed;
101
77
  const share = remainder > 0
102
78
  ? remainder / (children.length - named.length)
103
- : // an over-subscribed branch has nothing left to hand out, so a bare
104
- // sibling takes a typical share rather than collapsing to nothing
79
+ : // over-subscribed: a bare sibling takes an average share
105
80
  claimed / named.length;
106
81
  return stated.map(size => size ?? share);
107
82
  }
108
- /**
109
- * Build a layout tree from a spec.
110
- *
111
- * The old converter had to answer "which dockview group does a nested split
112
- * address?" and could not, because dockview forces orientation to alternate by
113
- * depth — which is why `size` was honoured only on the top-level split, and
114
- * only if every panel there carried one. Here the spec's nesting is the tree's
115
- * nesting, so `size` applies wherever it is written and there is no all-or-
116
- * nothing pass.
117
- */
118
83
  export function treeFromSpec(spec, nextId) {
119
84
  function build(node, size) {
120
85
  if (node.views) {
@@ -129,14 +94,8 @@ export function treeFromSpec(spec, nextId) {
129
94
  if (children.length === 0) {
130
95
  return undefined;
131
96
  }
132
- // `tabs` is not a split: every child's views become a tab in one cell.
133
- //
134
- // A tab holds a FLAT stack of views, so a container child has no split to
135
- // become — and the docs say containers nest arbitrarily deep, so one can be
136
- // written. Its views are flattened into a single tab; they used to be
137
- // dropped from the layout outright, which is invisible rather than wrong,
138
- // since homing then swept them into whichever tab happened to be showing.
139
- // `loadSessionSpec` reports the flattening, as it does the sizes below.
97
+ // every child becomes a tab in one cell; a tab holds a flat stack, so a
98
+ // container child's views flatten into it (`loadSessionSpec` reports this)
140
99
  if (node.direction === 'tabs') {
141
100
  const tabs = children.flatMap(child => {
142
101
  const viewIds = viewIdsInSpec(child);
@@ -176,23 +135,10 @@ export function viewIdsInSpec(spec) {
176
135
  ];
177
136
  }
178
137
  /**
179
- * Re-arrange every view at once, one view per cell.
180
- *
181
- * The four whole-workspace commands, as a spec. dockview had these as imperative
182
- * re-tiling — walk every panel and re-add it relative to the first, in an order
183
- * chosen so the grid came out right — and the grid case had to compute its own
184
- * row/column arithmetic against `api.panels` because there was no way to state
185
- * the shape. Here the shape IS the statement, so a tiling is a spec and goes
186
- * through the same `applyLayoutSpec` a session spec does.
187
- *
188
- * `grid` fills row-major at ceil(sqrt(n)) columns, which is what the dockview
189
- * version computed; a trailing row shorter than the rest keeps its cells at full
190
- * width, since `resolveSizes` reads bare siblings as an even share of their own
191
- * branch rather than of the grid.
138
+ * One view per cell, as a spec. `grid` fills row-major at ceil(sqrt(n))
139
+ * columns; a short trailing row's cells stretch to full width.
192
140
  */
193
141
  export function tileLayoutSpec(viewIds, mode) {
194
- // Nothing to arrange: one view is the whole workspace whatever the mode, and
195
- // no views leaves the empty panel the tree already guarantees.
196
142
  if (viewIds.length <= 1) {
197
143
  return { views: [...viewIds] };
198
144
  }
@@ -210,17 +156,3 @@ export function tileLayoutSpec(viewIds, mode) {
210
156
  }
211
157
  return { direction: 'vertical', children: rows };
212
158
  }
213
- /**
214
- * `setPendingMove` as a spec: everything else keeps its side, the named view
215
- * takes the other. With nothing else on screen there is nothing to split from,
216
- * so the view just takes the space.
217
- */
218
- export function specForPendingMove(move, allViewIds) {
219
- const others = allViewIds.filter(id => id !== move.viewId);
220
- return others.length > 0
221
- ? {
222
- direction: move.type === 'splitRight' ? 'horizontal' : 'tabs',
223
- children: [{ views: others }, { views: [move.viewId] }],
224
- }
225
- : { views: [move.viewId] };
226
- }
@@ -1,25 +1,9 @@
1
- /**
2
- * Moving the boundary between two panes: pure, so it is checkable without a DOM
3
- * or a synthetic pointer. The handle in `LayoutRenderer.tsx` only measures.
4
- */
5
- /**
6
- * A pane never shrinks below this many pixels.
7
- *
8
- * dockview's `MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH`/`_HEIGHT`. A `flex-grow`
9
- * share of zero is legal, so without it a pane can be dragged — or `Home`'d —
10
- * to nothing, taking its tab strip and views with it.
11
- */
12
1
  export declare const MIN_PANE_PX = 100;
13
2
  /** The space the two panes either side of the boundary before `index` share. */
14
3
  export declare function pairSpan(sizes: number[], index: number): number;
15
4
  /**
16
- * `sizes` with that boundary moved to `position`, measured in the same units.
17
- *
18
- * The move stays inside the pair, so every other pane holds still. The pointer
19
- * and the arrow keys are the same gesture at two resolutions and both land here.
20
- *
21
- * `pairPx` is the pair's current pixel span, needed only to convert
22
- * `MIN_PANE_PX` into a share; 0 means nothing measurable (jsdom, or a pane not
23
- * laid out yet) and gives the unconstrained clamp.
5
+ * `sizes` with that boundary moved to `position`, in the same units; other
6
+ * panes hold still. `pairPx` converts `MIN_PANE_PX` to a share; 0 (jsdom, or
7
+ * not laid out) skips the minimum.
24
8
  */
25
9
  export declare function withBoundaryAt(sizes: number[], index: number, position: number, pairPx?: number): number[];
@@ -1,33 +1,17 @@
1
- /**
2
- * Moving the boundary between two panes: pure, so it is checkable without a DOM
3
- * or a synthetic pointer. The handle in `LayoutRenderer.tsx` only measures.
4
- */
5
- /**
6
- * A pane never shrinks below this many pixels.
7
- *
8
- * dockview's `MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH`/`_HEIGHT`. A `flex-grow`
9
- * share of zero is legal, so without it a pane can be dragged — or `Home`'d —
10
- * to nothing, taking its tab strip and views with it.
11
- */
1
+ // a `flex-grow` share of zero is legal, so this keeps a pane from vanishing
12
2
  export const MIN_PANE_PX = 100;
13
3
  /** The space the two panes either side of the boundary before `index` share. */
14
4
  export function pairSpan(sizes, index) {
15
5
  return sizes[index - 1] + sizes[index];
16
6
  }
17
7
  /**
18
- * `sizes` with that boundary moved to `position`, measured in the same units.
19
- *
20
- * The move stays inside the pair, so every other pane holds still. The pointer
21
- * and the arrow keys are the same gesture at two resolutions and both land here.
22
- *
23
- * `pairPx` is the pair's current pixel span, needed only to convert
24
- * `MIN_PANE_PX` into a share; 0 means nothing measurable (jsdom, or a pane not
25
- * laid out yet) and gives the unconstrained clamp.
8
+ * `sizes` with that boundary moved to `position`, in the same units; other
9
+ * panes hold still. `pairPx` converts `MIN_PANE_PX` to a share; 0 (jsdom, or
10
+ * not laid out) skips the minimum.
26
11
  */
27
12
  export function withBoundaryAt(sizes, index, position, pairPx = 0) {
28
13
  const pair = pairSpan(sizes, index);
29
- // no room for two minimums: the boundary stops in the middle rather than
30
- // pinning both ends past each other
14
+ // with no room for two minimums the boundary stops in the middle
31
15
  const floor = pairPx > 0 ? Math.min((MIN_PANE_PX / pairPx) * pair, pair / 2) : 0;
32
16
  const before = Math.min(Math.max(position, floor), pair - floor);
33
17
  const next = [...sizes];
@@ -0,0 +1,8 @@
1
+ import type { TabNode } from './tree.ts';
2
+ import type { AbstractViewModel } from '@jbrowse/core/util';
3
+ /** A tab's name: the user's title if it has one, else derived from its views */
4
+ export declare function tabDisplayName(tab: TabNode, views: AbstractViewModel[], session: {
5
+ assemblyManager: {
6
+ getDisplayName: (name: string) => string;
7
+ };
8
+ }): string;
@@ -0,0 +1,14 @@
1
+ import { viewName } from '../ui/App/viewTitle.js';
2
+ /** A tab's name: the user's title if it has one, else derived from its views */
3
+ export function tabDisplayName(tab, views, session) {
4
+ if (tab.title) {
5
+ return tab.title;
6
+ }
7
+ if (views.length === 0) {
8
+ return 'Empty';
9
+ }
10
+ if (views.length === 1) {
11
+ return viewName(views[0], r => session.assemblyManager.getDisplayName(r));
12
+ }
13
+ return `${views.length} views`;
14
+ }
@@ -1,24 +1,9 @@
1
1
  /**
2
- * The layout tree, as pure functions over plain snapshots.
3
- *
4
- * This is the half of an MST-native workspace that carries the risk, so it is
5
- * deliberately not MST: no nodes, no parents, no lifecycle, nothing to be alive
6
- * or dead. Every operation is `tree in -> tree out`, which means the invariants
7
- * below can be tested exhaustively instead of observed to hold.
8
- *
9
- * The trade being made is worth naming, because it is not "complexity for no
10
- * complexity". Reconciling with dockview is replaced by **normalisation**: a
11
- * tree that has just had a panel removed, or a branch split, is usually not in
12
- * canonical form, and every operation has to put it back. That is real work and
13
- * it is where this design's bugs would live. What it is not is timing: there is
14
- * no event, no re-entrancy, no window during which the tree is half-updated,
15
- * and no second owner to disagree with.
16
- */
17
- /**
18
- * One tab. Its content is a **vertical stack of views**, which is JBrowse's own
19
- * concept and the reason a generic window manager never fits cleanly: dockview
20
- * has a group holding tabs, and we need a third level under that.
2
+ * The layout tree as pure functions over plain snapshots, deliberately not MST
3
+ * so its invariants can be tested exhaustively. Every operation returns a tree
4
+ * in canonical form (`normalize`).
21
5
  */
6
+ /** One tab: a vertical stack of views. */
22
7
  export interface TabNode {
23
8
  id: string;
24
9
  /** membership only — `session.views` is the order (see WorkspaceLayout/CLAUDE.md) */
@@ -66,23 +51,13 @@ export declare function panelContainingView(node: LayoutTree, viewId: string): P
66
51
  * it, its children's sizes scaled to preserve their share of the whole
67
52
  * 4. sizes are renormalised so a node's siblings always sum to 1
68
53
  *
69
- * Rule 3 is the one dockview cannot express: it forces orientation to alternate
70
- * by depth, so `row` inside `row` is not representable and a nested split gets
71
- * silently reparented. Here it is representable AND canonicalised, which is
72
- * what makes `size` work at any depth rather than only on the top-level split.
73
- *
74
- * Empty panels are NOT dropped, and neither are empty tabs. A tab with no views
75
- * is exactly what "new empty tab" creates — it shows the view launcher — so
76
- * pruning empties wholesale would delete the thing the user just asked for.
54
+ * Empty panels and empty tabs stay: "new empty tab" creates one on purpose, to
55
+ * show the view launcher.
77
56
  */
78
57
  export declare function normalize(node: LayoutTree): LayoutTree;
79
58
  /**
80
- * Split `panelId`, putting `newPanel` beside it in `direction`.
81
- *
82
- * Always builds a branch around the panel and lets `normalize` flatten it into
83
- * the parent when the directions agree. Trying to decide up front whether to
84
- * nest or to insert as a sibling is the same decision rule twice, and the two
85
- * copies drift.
59
+ * Split `panelId`, putting `newPanel` beside it in `direction`. Always wraps
60
+ * the panel in a branch and lets `normalize` flatten it when directions agree.
86
61
  */
87
62
  export declare function splitPanel(root: LayoutTree, panelId: string, direction: 'row' | 'column', newPanel: PanelNode, before?: boolean): LayoutTree;
88
63
  /** Drop a panel. Its space goes back to its siblings via renormalisation. */
@@ -96,25 +71,11 @@ export declare function addTab(root: LayoutTree, panelId: string, tab: TabNode):
96
71
  */
97
72
  export declare function removeTab(root: LayoutTree, tabId: string): LayoutTree;
98
73
  /**
99
- * Move a tab into another panel, at `index` if given.
100
- *
101
- * One function returning one tree, so there is no instant at which the tab is
102
- * in both panels or neither — the state the imperative bridge had to wrap an
103
- * explicit `runInAction` around to hide from its own reconcile autorun.
74
+ * Move a tab into another panel, at `index` if given, or append.
104
75
  *
105
- * **`index` counts the tabs as they are ordered NOW**, before the move. That is
106
- * the only reading a caller can supply, because it is the strip the user is
107
- * looking at — and it differs from the post-removal ordering exactly when the
108
- * tab is moving within its own panel and is currently to the LEFT of the gap it
109
- * was dropped in. Dragging A to the gap between B and C in `[A, B, C]` is index
110
- * 2 on screen; taking A out first makes that gap index 1, and inserting at 2
111
- * lands it after C instead. Adjusting here rather than in the caller keeps the
112
- * whole "remove then insert" mechanic inside the one function that does it.
113
- *
114
- * dockview reaches the same rule from the other end and it is worth knowing
115
- * they agree, since this is the fiddliest arithmetic in the file: its tabs
116
- * container computes `insertionIndex - (sourceIndex !== -1 && sourceIndex <
117
- * insertionIndex ? 1 : 0)`, which is the condition below with the same bounds.
76
+ * `index` counts the strip the user sees, before the move. Within one panel a
77
+ * tab left of the gap shifts it by one once removed: dragging A between B and C
78
+ * in `[A, B, C]` is index 2 on screen but 1 after A comes out.
118
79
  */
119
80
  export declare function moveTabToPanel(root: LayoutTree, tabId: string, targetPanelId: string, index?: number): LayoutTree;
120
81
  export declare function setActiveTab(root: LayoutTree, panelId: string, tabId: string): LayoutTree;
@@ -123,36 +84,18 @@ export declare function addViewToTab(root: LayoutTree, tabId: string, viewId: st
123
84
  /** Take a view out of whatever tab holds it, leaving the tab in place. */
124
85
  export declare function removeView(root: LayoutTree, viewId: string): LayoutTree;
125
86
  /**
126
- * Make the tree's membership agree with the session's list of views.
127
- *
128
- * The only reconciliation left, and one-directional: `session.views` owns which
129
- * views exist, and nothing reads back.
130
- *
131
- * **`viewIds` is the session's WHOLE set.** A shorter list does not leave the
132
- * rest alone — it unhomes them, and the next homing pass sweeps them into one
133
- * tab.
87
+ * Make the tree's membership agree with `session.views`, which owns which views
88
+ * exist. `viewIds` is the WHOLE set: a shorter list unhomes the rest.
134
89
  */
135
90
  export declare function homeViews(root: LayoutTree, viewIds: string[], activePanelId: string | undefined, nextTabId: () => string): LayoutTree;
136
91
  /**
137
- * Drop a panel that has been left with no tabs, unless it is the only one.
138
- *
139
- * Deliberately NOT a normalisation rule. A panel with no tabs is reachable on
140
- * purpose, so pruning empties wholesale would delete a space the user asked
141
- * for. It is a step the *drag* gesture takes about its own source panel,
142
- * because dragging the last tab out of a split and leaving a blank half is the
143
- * one place an empty panel is clearly not what was meant.
92
+ * Drop a panel left with no tabs, unless it is the only one. Not a
93
+ * normalisation rule: only the gesture that emptied the panel calls it.
144
94
  */
145
95
  export declare function pruneEmptyPanel(root: LayoutTree, panelId: string): LayoutTree;
146
96
  /**
147
- * Drop a tab that a move has left with no views, unless it is the panel's last.
148
- *
149
- * The mirror of `pruneEmptyPanel`, and deliberately as narrow: an empty tab is
150
- * legitimate (it shows the view launcher), so this only ever runs on the tab a
151
- * move just emptied, never as a rule over the tree.
152
- *
153
- * Which is why closing a tab's last VIEW leaves the tab standing where a move
154
- * out of it does not: the move acted somewhere else and left this behind, and
155
- * the close acted here, where an empty tab is a usable place to open the next
156
- * thing. A cell's last tab prunes because a blank cell is not usable.
97
+ * Drop a tab a move has left with no views, unless it is the panel's last.
98
+ * Closing a tab's last view leaves the tab standing, as a place to open the
99
+ * next one; only a move out of it prunes.
157
100
  */
158
101
  export declare function pruneEmptyTabIn(root: LayoutTree, panelId: string, tabId: string): LayoutTree;