@x1a0f3n9/dsh-client-ui-dockkit 0.1.5-rc.3

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.
@@ -0,0 +1,60 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { DockIntents, DockLabels, TabMenuExtras, TabRenderer } from '../contract/adapter.ts';
3
+ import type { LayoutState, PaneId, TabId } from '../contract/types.ts';
4
+ import type { HalvesFit } from '../engine/geometry.ts';
5
+ /** What the docked surface needs: the layout, its limits, and the outward contracts. */
6
+ export interface DockSurfaceProps {
7
+ readonly state: LayoutState;
8
+ /**
9
+ * Whether another pane may still be created: the pane budget. Width is the
10
+ * kit's own concern — a pane too narrow for two working halves keeps its
11
+ * split control disabled with `labels.splitPaneNarrow` (see README).
12
+ */
13
+ readonly canSplit: boolean;
14
+ /** Hide a blocked split control — pane budget spent or pane too narrow — instead of rendering it disabled; defaults to false. */
15
+ readonly hideSplitWhenBlocked?: boolean;
16
+ /** Body drop geometry: all edge bands, or left/right halves with whole-pane moves once splitting is unavailable. */
17
+ readonly dropZones?: 'edges' | 'horizontal';
18
+ /** Smallest share a divider may leave a pane; defaults to the kit's fraction. */
19
+ readonly minPaneFraction?: number;
20
+ /**
21
+ * Whether a pane's strip draws the add control. Called per docked pane on
22
+ * every render; omit to draw one in every pane. `false` leaves the strip's
23
+ * end controls where they are and the chips as the only shrinking part.
24
+ */
25
+ readonly canAddTab?: (paneId: PaneId) => boolean;
26
+ /**
27
+ * Whether a tab draws its close control and its menu's close item. Called
28
+ * per rendered chip on every render; omit to keep every tab closable.
29
+ * `false` removes both routes without moving the chip: the close control
30
+ * paints over the title's end rather than beside it, so the chip is the same
31
+ * width either way. The menu still opens and carries the embedder's items.
32
+ */
33
+ readonly canCloseTab?: (tabId: TabId) => boolean;
34
+ readonly intents: DockIntents;
35
+ readonly labels: DockLabels;
36
+ readonly renderTab: TabRenderer;
37
+ /**
38
+ * What a tab's chip shows as its title; omit to show the record's `title`
39
+ * text. An embedder-internal seam: the Sidebar dispatches it to a per-kind
40
+ * slot, and nothing outside that embedder is expected to supply it.
41
+ */
42
+ readonly renderTabTitle?: TabRenderer;
43
+ /** Extra items for a tab's context menu; omit for the kit's own item only. */
44
+ readonly renderTabMenuItems?: TabMenuExtras;
45
+ /**
46
+ * Surface-wide controls, drawn at the far end of the top-right pane's tab
47
+ * strip so the surface needs no header of its own. The kit places them; what
48
+ * they do is the embedder's.
49
+ */
50
+ readonly chrome?: ReactNode;
51
+ /**
52
+ * Called with the room rule's latest readings whenever they change, so an
53
+ * embedder driving splits programmatically can honour the same rule the
54
+ * split control does. A pane absent from the map has not been measured.
55
+ */
56
+ readonly onRoom?: (fits: ReadonlyMap<PaneId, HalvesFit>) => void;
57
+ }
58
+ /** The split tree and the gestures over it. */
59
+ export declare function DockSurface({ state, canSplit, canAddTab, canCloseTab, intents, labels, renderTab, renderTabTitle, renderTabMenuItems, chrome, onRoom, dropZones, minPaneFraction, hideSplitWhenBlocked, }: DockSurfaceProps): ReactNode;
60
+ //# sourceMappingURL=DockSurface.d.ts.map
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { DockIntents, DockLabels, TabRenderer } from '../contract/adapter.ts';
3
+ import type { LayoutState, TabId } from '../contract/types.ts';
4
+ /** The layout whose `floats` this layer draws. */
5
+ export interface FloatLayerProps {
6
+ readonly state: LayoutState;
7
+ readonly intents: DockIntents;
8
+ readonly labels: DockLabels;
9
+ readonly renderTab: TabRenderer;
10
+ /** Whether a floating tab offers its close control; defaults to true. Called per tab on every render. */
11
+ readonly canCloseTab?: (tabId: TabId) => boolean;
12
+ /** The panel header's title content; omit to show the record's `title` text (see `DockSurfaceProps`). */
13
+ readonly renderTabTitle?: TabRenderer;
14
+ }
15
+ /** Every floating panel, in z order. */
16
+ export declare function FloatLayer({ state, intents, labels, renderTab, renderTabTitle, canCloseTab }: FloatLayerProps): ReactNode;
17
+ //# sourceMappingURL=FloatLayer.d.ts.map
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { LayoutState, NodeId, SplitId } from '../contract/types.ts';
3
+ import type { PaneCallbacks } from './render.ts';
4
+ /** Fractions a live divider drag is previewing for one split. */
5
+ export interface SizePreview {
6
+ readonly splitId: SplitId;
7
+ readonly sizes: readonly number[];
8
+ }
9
+ /** One subtree of the docked layout. */
10
+ export interface PaneTreeProps {
11
+ readonly state: LayoutState;
12
+ readonly nodeId: NodeId;
13
+ readonly callbacks: PaneCallbacks;
14
+ readonly preview: SizePreview | undefined;
15
+ }
16
+ /** Render a split or pane node and everything under it. */
17
+ export declare function PaneTree({ state, nodeId, callbacks, preview }: PaneTreeProps): ReactNode;
18
+ //# sourceMappingURL=PaneTree.d.ts.map
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { DockLabels } from '../contract/adapter.ts';
3
+ /** What the menu offers, where it anchors, and how it closes. */
4
+ export interface TabMenuProps {
5
+ readonly labels: DockLabels;
6
+ /** The control that opened the menu; the menu hangs below its left edge. */
7
+ readonly anchor: HTMLElement;
8
+ /** Close the tab; `undefined` removes the kit's item, leaving the extras only. */
9
+ readonly onClose: (() => void) | undefined;
10
+ /** Dismiss without acting. */
11
+ readonly onDismiss: () => void;
12
+ /** Embedder items, rendered after the kit's own; absent means none. */
13
+ readonly extras: ReactNode;
14
+ }
15
+ /** The actions menu body, anchored to the control that opened it. */
16
+ export declare function TabMenu({ labels, anchor, onClose, onDismiss, extras }: TabMenuProps): ReactNode;
17
+ //# sourceMappingURL=TabMenu.d.ts.map
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { LayoutState, PaneNode } from '../contract/types.ts';
3
+ import type { PaneCallbacks } from './render.ts';
4
+ /** A pane and the live layout it reads its tabs from. */
5
+ export interface TabPanelProps {
6
+ readonly state: LayoutState;
7
+ readonly pane: PaneNode;
8
+ readonly callbacks: PaneCallbacks;
9
+ }
10
+ /** The pane's tab strip, split control, and body. */
11
+ export declare function TabPanel({ state, pane, callbacks }: TabPanelProps): ReactNode;
12
+ //# sourceMappingURL=TabPanel.d.ts.map
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ /** The title span of a strip chip or a floating panel's header chip. */
3
+ export declare function TabTitle({ children }: {
4
+ readonly children: ReactNode;
5
+ }): ReactNode;
6
+ //# sourceMappingURL=TabTitle.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * DOM side of the room rule: read each docked pane's rectangles after a commit
3
+ * and ask `halvesFit` whether a split would leave two working halves. Pixels
4
+ * live here and in `geometry.ts`; the engine's planners never see them.
5
+ */
6
+ import type { PaneId } from '../contract/types.ts';
7
+ import type { HalvesFit } from '../engine/geometry.ts';
8
+ /**
9
+ * Every docked pane element under `root`, in document order, with the pane id
10
+ * each carries.
11
+ * @param root - the docked surface's element.
12
+ * @returns pane ids paired with their elements.
13
+ */
14
+ export declare function paneElements(root: HTMLElement): readonly (readonly [PaneId, HTMLElement])[];
15
+ /**
16
+ * Measure every docked pane under `root`.
17
+ * @param root - the docked surface's element.
18
+ * @param splitHiddenWhenBlocked - whether the embedder hides blocked split
19
+ * controls (`hideSplitWhenBlocked`); the room rule then leaves the control's
20
+ * footprint out of each strip's fixed part, so the reading cannot flip with
21
+ * the control's visibility (see `PaneMeasure.splitControlWidth`).
22
+ * @returns each pane's fit, keyed by pane id.
23
+ */
24
+ export declare function measurePaneFits(root: HTMLElement, splitHiddenWhenBlocked?: boolean): ReadonlyMap<PaneId, HalvesFit>;
25
+ /**
26
+ * One pane's latest reading. A pane the map does not name has not been
27
+ * measured and fits: the rule only blocks on a positive reading.
28
+ * @param fits - the latest measurement.
29
+ * @param paneId - the pane asked about.
30
+ * @returns whether each split axis leaves two working halves.
31
+ */
32
+ export declare function fitOf(fits: ReadonlyMap<PaneId, HalvesFit>, paneId: PaneId): HalvesFit;
33
+ /**
34
+ * Whether two measurements agree, so a re-measure that changed nothing re-renders nothing.
35
+ * @param a - one measurement.
36
+ * @param b - the other.
37
+ * @returns whether both name the same panes with the same readings.
38
+ */
39
+ export declare function sameFits(a: ReadonlyMap<PaneId, HalvesFit>, b: ReadonlyMap<PaneId, HalvesFit>): boolean;
40
+ //# sourceMappingURL=measure.d.ts.map
@@ -0,0 +1,47 @@
1
+ /** The three window listeners one gesture installs. */
2
+ export interface PointerFollowers {
3
+ readonly move: (event: PointerEvent) => void;
4
+ readonly up: (event: PointerEvent) => void;
5
+ readonly cancel: () => void;
6
+ }
7
+ /**
8
+ * Take ownership of the pointer for the rest of the gesture.
9
+ * @param element - the element the gesture started on.
10
+ * @param pointerId - the pointer to capture.
11
+ */
12
+ export declare function capturePointer(element: HTMLElement, pointerId: number): void;
13
+ /**
14
+ * Capture the pointer, then follow it on the window until release or cancel.
15
+ * Only that pointer's events count: a second finger or a pen beside the mouse
16
+ * neither moves nor ends the gesture. The listeners remove themselves before
17
+ * `up` or `cancel` runs; the returned callback ends the gesture early, for an
18
+ * unmount or a superseding press.
19
+ * @param element - the element the gesture started on.
20
+ * @param pointerId - the pointer to capture and follow.
21
+ * @param followers - listeners for move, release, and cancel.
22
+ * @returns detach callback removing the three listeners.
23
+ */
24
+ export declare function followPointer(element: HTMLElement, pointerId: number, followers: PointerFollowers): () => void;
25
+ /** What one gesture does while it lasts and when it settles. */
26
+ export interface GestureFollowers {
27
+ readonly move: (event: PointerEvent) => void;
28
+ /** The release. The gesture has already ended, and its preview reset, when this runs. */
29
+ readonly up: (event: PointerEvent) => void;
30
+ }
31
+ /**
32
+ * Start a gesture from the element a press landed on.
33
+ * @param element - the pressed element; the pointer is captured on it.
34
+ * @param pointerId - the pressing pointer.
35
+ * @param followers - what the gesture does.
36
+ */
37
+ export type BeginGesture = (element: HTMLElement, pointerId: number, followers: GestureFollowers) => void;
38
+ /**
39
+ * One pointer gesture at a time for a component. A gesture ends on release, on
40
+ * cancel, or when a new press supersedes it; `reset` runs at each of those ends
41
+ * so the component clears its preview. Unmounting mid-gesture removes the
42
+ * listeners without resetting anything.
43
+ * @param reset - clears the component's gesture preview.
44
+ * @returns the gesture starter, called from a pointer-down handler.
45
+ */
46
+ export declare function useGesture(reset: () => void): BeginGesture;
47
+ //# sourceMappingURL=pointer.d.ts.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Prop shares the kit's own components pass among themselves. These are internal
3
+ * to the package — the outward contracts are in `adapter.ts`.
4
+ */
5
+ import type { PointerEvent as ReactPointerEvent, ReactNode } from 'react';
6
+ import type { DockLabels, TabMenuExtras, TabRenderer } from '../contract/adapter.ts';
7
+ import type { PaneId, SplitId, TabId } from '../contract/types.ts';
8
+ import type { DropTarget } from '../engine/geometry.ts';
9
+ /** Why a pane's split control is disabled: the pane budget, or too little width for two halves. */
10
+ export type SplitBlock = 'budget' | 'width';
11
+ /** What a pane subtree needs: settled callbacks, gesture starters, and live preview. */
12
+ export interface PaneCallbacks {
13
+ readonly onFocusTab: (tabId: TabId) => void;
14
+ readonly onFocusPane: (paneId: PaneId) => void;
15
+ readonly onSplitPane: (paneId: PaneId) => void;
16
+ readonly onAddTab: (paneId: PaneId) => void;
17
+ readonly onCloseTab: (tabId: TabId) => void;
18
+ /** Begin dragging a tab; the surface owns the gesture from here. */
19
+ readonly onTabPressed: (tabId: TabId, event: ReactPointerEvent<HTMLElement>) => void;
20
+ /** Begin dragging a divider inside `splitId`, at the boundary after `index`. */
21
+ readonly onDividerPressed: (splitId: SplitId, index: number, event: ReactPointerEvent<HTMLElement>) => void;
22
+ /** Why a pane cannot split right now, or `undefined` while it can. */
23
+ readonly splitBlock: (paneId: PaneId) => SplitBlock | undefined;
24
+ /** Hide blocked split controls instead of rendering them disabled. */
25
+ readonly hideSplitWhenBlocked?: boolean;
26
+ /** Whether a pane's strip draws the add control. */
27
+ readonly canAddTab: (paneId: PaneId) => boolean;
28
+ /** Whether a tab draws its close control and its menu's close item. */
29
+ readonly canCloseTab: (tabId: TabId) => boolean;
30
+ /** Live drop preview, or `undefined` while nothing is being dragged. */
31
+ readonly dropTarget: DropTarget | undefined;
32
+ /** Show both horizontal landing regions while a body split is being targeted. */
33
+ readonly horizontalDrops?: boolean;
34
+ /** Tab currently being dragged, so its chip can render as lifted. */
35
+ readonly draggingTabId: TabId | undefined;
36
+ readonly labels: DockLabels;
37
+ readonly renderTab: TabRenderer;
38
+ /** A chip's or panel header's title content; absent means the record's `title` text. */
39
+ readonly renderTabTitle: TabRenderer | undefined;
40
+ /** Embedder items appended to a tab's context menu; absent means the kit's item only. */
41
+ readonly renderTabMenuItems: TabMenuExtras | undefined;
42
+ /** The pane whose strip hosts the embedder's surface-wide controls. */
43
+ readonly chromePaneId: PaneId;
44
+ /** Those controls; absent means the strip ends at the kit's own split control. */
45
+ readonly chrome: ReactNode;
46
+ }
47
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The kit's outward contracts: state in, intents out.
3
+ *
4
+ * Everything host-specific arrives through these — every rendered string, every
5
+ * tab body, and every net gesture result. The kit itself holds no copy, no icon
6
+ * set, and no knowledge of what a tab's `kind` means.
7
+ */
8
+ import type { ReactNode } from 'react';
9
+ import type { DockZone, FloatRect, PaneId, SplitId, TabId, TabRecord } from './types.ts';
10
+ /**
11
+ * Every string the kit renders, already localized by the embedder.
12
+ *
13
+ * Accessible names are included: a control with no visible text still needs
14
+ * one, and the kit must not invent it.
15
+ */
16
+ export interface DockLabels {
17
+ /** Body of a pane holding no tabs. */
18
+ readonly emptyPane: string;
19
+ /** The split control, while splitting is allowed. */
20
+ readonly splitPane: string;
21
+ /** The split control, once the pane budget is spent. */
22
+ readonly splitPaneDisabled: string;
23
+ /** The split control, while the pane is too narrow for two working halves. */
24
+ readonly splitPaneNarrow: string;
25
+ /** Destroy a tab: the chip's close control and the menu's close item. */
26
+ readonly closeTab: string;
27
+ /** The strip's add control, which seats the embedder's seeded tab. */
28
+ readonly addTab: string;
29
+ /** Send a floating panel back into the docked tree. */
30
+ readonly dockFloat: string;
31
+ /** Close a floating panel. */
32
+ readonly closeFloat: string;
33
+ /** The drop hint's caption for each body zone a dragged tab can land on. */
34
+ readonly dropZone: Readonly<Record<DockZone, string>>;
35
+ }
36
+ /**
37
+ * Renders one tab's body. The embedder dispatches on `tab.kind`, which is the
38
+ * only place that string carries meaning.
39
+ */
40
+ export type TabRenderer = (tab: TabRecord) => ReactNode;
41
+ /**
42
+ * Renders extra items at the end of one tab's context menu (opened by a
43
+ * secondary press on the chip).
44
+ *
45
+ * The kit's own item is the close gesture; anything that means something about
46
+ * the tab's content comes from here. An item that acts MUST call `dismiss`,
47
+ * because the menu closes on its own items only. Every rendered item MUST
48
+ * carry `role="menuitem"`: the kit probes for that role to dismiss a menu
49
+ * that would paint empty, so items without it count as an empty menu.
50
+ * @param tab - the tab whose menu is open.
51
+ * @param dismiss - close the menu without acting.
52
+ * @returns extra actions with ARIA menuitem, menuitemcheckbox, or menuitemradio roles, or nothing.
53
+ */
54
+ export type TabMenuExtras = (tab: TabRecord, dismiss: () => void) => ReactNode;
55
+ /**
56
+ * Net gesture results the kit reports. Each call is one settled intent — never a
57
+ * drag frame — so an embedder recording them produces one operation per gesture.
58
+ *
59
+ * `DockController` satisfies this contract as-is; an embedder that routes
60
+ * through its own store implements the same names.
61
+ */
62
+ export interface DockIntents {
63
+ /** Focus a tab and its pane. */
64
+ readonly focusTab: (tabId: TabId) => void;
65
+ /** Focus a pane, raising it when it floats. */
66
+ readonly focusPane: (paneId: PaneId) => void;
67
+ /** Split a pane and seed the new one. */
68
+ readonly splitPane: (paneId: PaneId) => void;
69
+ /** Add the embedder's seeded tab to a pane (the strip's `+`). */
70
+ readonly addTab: (paneId: PaneId) => void;
71
+ /** Destroy a tab. */
72
+ readonly closeTab: (tabId: TabId) => void;
73
+ /** Copy a tab beside itself. No kit control drives this; embedders reach it through their own API. */
74
+ readonly duplicateTab: (tabId: TabId) => void;
75
+ /** Float a tab, at `rect` when the release point decided one (a drag released clear of the surface). */
76
+ readonly floatTab: (tabId: TabId, rect?: FloatRect) => void;
77
+ /** Return a floating panel's tab to the docked tree. */
78
+ readonly unfloatPane: (paneId: PaneId) => void;
79
+ /**
80
+ * Put a tab at an explicit strip slot: a reorder, a move, or a return. `index`
81
+ * is the caret slot counted over the destination strip's chips as drawn, the
82
+ * dragged chip included when the strip is its own.
83
+ */
84
+ readonly placeTab: (tabId: TabId, toPaneId: PaneId, index: number) => void;
85
+ /** Resolve a release on a pane body: the centre moves in, an edge splits. */
86
+ readonly dropTab: (tabId: TabId, paneId: PaneId, zone: DockZone) => void;
87
+ /** Net position of a floating-panel drag; the operation it records focuses and raises the panel too. */
88
+ readonly moveFloat: (paneId: PaneId, x: number, y: number) => void;
89
+ /** Net rectangle of a floating-panel resize; the operation it records focuses and raises the panel too. */
90
+ readonly resizeFloat: (paneId: PaneId, rect: FloatRect) => void;
91
+ /** Net fractions of a divider drag. */
92
+ readonly resizeSplit: (splitId: SplitId, sizes: readonly number[]) => void;
93
+ }
94
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Layout model and operation vocabulary. Types only: no runtime code, no React,
3
+ * no DOM, and no host concepts — a tab's `kind` is an opaque string this kit
4
+ * never interprets, so the embedder owns what content families exist.
5
+ *
6
+ * The model is a normalized recursive split tree. `nodes` holds every split and
7
+ * pane keyed by id; `rootId` names the docked root; `floats` lists floating
8
+ * panes bottom-to-top. A floating panel is not a second concept — it is a pane
9
+ * whose `host` is `'float'`, capacity 1 tab, drawn without a tab strip.
10
+ *
11
+ * Ids are branded: a pane, a split, and a tab id never stand in for one another
12
+ * or for a bare string, and only a mint (or a DOM round trip of an id the kit
13
+ * wrote itself) produces one.
14
+ */
15
+ import type { Branded } from '@x1a0f3n9/dsh-brand';
16
+ /** Identity of a pane node in `LayoutState.nodes`. */
17
+ export type PaneId = Branded<'PaneId'>;
18
+ /** Identity of a split node in `LayoutState.nodes`. */
19
+ export type SplitId = Branded<'SplitId'>;
20
+ /** Identity of any node in `LayoutState.nodes`. */
21
+ export type NodeId = PaneId | SplitId;
22
+ /** Identity of one open tab; distinct copies of one content share `contentId`, never `TabId`. */
23
+ export type TabId = Branded<'TabId'>;
24
+ /** Direction a split lays its children out in. */
25
+ export type SplitAxis = 'row' | 'column';
26
+ /** Which side of the reference pane a new pane takes. */
27
+ export type SplitDirection = 'before' | 'after';
28
+ /** The five drop regions a pane offers a dragged tab. */
29
+ export type DockZone = 'center' | 'top' | 'right' | 'bottom' | 'left';
30
+ /**
31
+ * How the docked area is presented.
32
+ *
33
+ * `push` takes room from its neighbours; `fullscreen` covers the viewport. The kit
34
+ * records the choice but does not implement either — the embedder reads this and
35
+ * positions the surface. It lives here, beside `expanded`, because switching is a
36
+ * recorded operation the user can step back through. The values are the kit's
37
+ * own words and no service interface repeats them.
38
+ */
39
+ export type DockMode = 'push' | 'fullscreen';
40
+ /** Viewport rectangle of a floating pane, in CSS pixels. */
41
+ export interface FloatRect {
42
+ readonly x: number;
43
+ readonly y: number;
44
+ readonly width: number;
45
+ readonly height: number;
46
+ }
47
+ /** Interior node: an ordered run of children along one axis with fractional sizes. */
48
+ export interface SplitNode {
49
+ readonly kind: 'split';
50
+ readonly id: SplitId;
51
+ readonly axis: SplitAxis;
52
+ /** At least two children; a one-child split collapses into that child. */
53
+ readonly children: readonly NodeId[];
54
+ /** Same length as `children`, each above zero, summing to 1. */
55
+ readonly sizes: readonly number[];
56
+ }
57
+ /** Where a pane is drawn: inside the docked split tree, or as a viewport overlay. */
58
+ export type PaneHost = 'dock' | 'float';
59
+ /** Leaf node: an ordered tab list with at most one active tab. */
60
+ export interface PaneNode {
61
+ readonly kind: 'pane';
62
+ readonly id: PaneId;
63
+ readonly host: PaneHost;
64
+ readonly tabs: readonly TabId[];
65
+ /** `undefined` exactly when `tabs` is empty. */
66
+ readonly activeTabId: TabId | undefined;
67
+ /** Set exactly when `host` is `'float'`. */
68
+ readonly rect: FloatRect | undefined;
69
+ }
70
+ /** Either kind of tree node. */
71
+ export type LayoutNode = SplitNode | PaneNode;
72
+ /**
73
+ * One open tab.
74
+ *
75
+ * `kind` selects the embedder's content family and is never interpreted here.
76
+ * `contentId` is the identity `openContent` de-duplicates against, so two tabs
77
+ * sharing it are deliberate copies of one thing.
78
+ */
79
+ export interface TabRecord {
80
+ readonly id: TabId;
81
+ readonly kind: string;
82
+ readonly contentId: string;
83
+ readonly title: string;
84
+ }
85
+ /**
86
+ * The whole layout of one docking surface. Every field is replaced rather than
87
+ * mutated, and untouched sub-objects keep their identity so consumers can
88
+ * compare by reference.
89
+ */
90
+ export interface LayoutState {
91
+ readonly nodes: Readonly<Record<NodeId, LayoutNode>>;
92
+ readonly tabs: Readonly<Record<TabId, TabRecord>>;
93
+ /** Root of the docked tree; always a split or pane that exists in `nodes`. */
94
+ readonly rootId: NodeId;
95
+ /** Floating panes, bottom-to-top; the last entry is on top. */
96
+ readonly floats: readonly PaneId[];
97
+ /** Focused pane, docked or floating. */
98
+ readonly activePaneId: PaneId;
99
+ /** Whether the docked area is expanded; floating panes ignore it. */
100
+ readonly expanded: boolean;
101
+ /** How the docked area is presented; floating panes ignore it. */
102
+ readonly mode: DockMode;
103
+ }
104
+ /** Recipe for putting a pane back where it was, used by `insertPane`. */
105
+ export type PaneAttachment =
106
+ /** Re-insert as a child of an existing split, restoring that split's sizes verbatim. */
107
+ {
108
+ readonly mode: 'child';
109
+ readonly parentId: SplitId;
110
+ readonly index: number;
111
+ readonly sizes: readonly number[];
112
+ }
113
+ /** Re-create a collapsed split in `targetId`'s slot; `split` already lists both children. */
114
+ | {
115
+ readonly mode: 'wrap';
116
+ readonly targetId: NodeId;
117
+ readonly split: SplitNode;
118
+ }
119
+ /** Re-insert a floating pane at its former z index. */
120
+ | {
121
+ readonly mode: 'float';
122
+ readonly index: number;
123
+ };
124
+ /**
125
+ * One recorded layout mutation. Ids that an operation creates are carried in
126
+ * the operation itself, so replaying a sequence from the same initial state
127
+ * reproduces the same ids without any minting during apply.
128
+ *
129
+ * `insertPane`, `insertTab`, and `restoreFocus` exist to express inverses
130
+ * exactly; they are applied like any other operation.
131
+ */
132
+ export type LayoutOp =
133
+ /** Give `paneId` a new empty sibling pane along `axis`. */
134
+ {
135
+ readonly type: 'split';
136
+ readonly paneId: PaneId;
137
+ readonly axis: SplitAxis;
138
+ readonly direction: SplitDirection;
139
+ readonly newPaneId: PaneId;
140
+ /** Used only when the reference pane's parent cannot host `axis` directly. */
141
+ readonly newSplitId: SplitId;
142
+ }
143
+ /** Drop an empty docked pane and collapse the split it leaves behind. */
144
+ | {
145
+ readonly type: 'merge';
146
+ readonly paneId: PaneId;
147
+ }
148
+ /** Add a new tab to a docked pane and focus it. */
149
+ | {
150
+ readonly type: 'openTab';
151
+ readonly paneId: PaneId;
152
+ readonly tab: TabRecord;
153
+ readonly index: number;
154
+ }
155
+ /** Destroy a tab and its content state; a floating host pane goes with it. */
156
+ | {
157
+ readonly type: 'closeTab';
158
+ readonly tabId: TabId;
159
+ }
160
+ /** Move a tab to a different docked pane. */
161
+ | {
162
+ readonly type: 'moveTab';
163
+ readonly tabId: TabId;
164
+ readonly toPaneId: PaneId;
165
+ readonly index: number;
166
+ }
167
+ /** Move a tab within its own pane. */
168
+ | {
169
+ readonly type: 'reorderTab';
170
+ readonly tabId: TabId;
171
+ readonly index: number;
172
+ }
173
+ /** Focus a tab, its owning pane, and raise that pane when floating. */
174
+ | {
175
+ readonly type: 'focusTab';
176
+ readonly tabId: TabId;
177
+ }
178
+ /** Focus a pane and raise it when floating. */
179
+ | {
180
+ readonly type: 'focusPane';
181
+ readonly paneId: PaneId;
182
+ }
183
+ /** Net result of a divider drag. */
184
+ | {
185
+ readonly type: 'resize';
186
+ readonly splitId: SplitId;
187
+ readonly sizes: readonly number[];
188
+ }
189
+ /** Take a tab out of the docked tree into a new floating pane. */
190
+ | {
191
+ readonly type: 'float';
192
+ readonly tabId: TabId;
193
+ readonly newPaneId: PaneId;
194
+ readonly rect: FloatRect;
195
+ }
196
+ /** Return a floating pane's only tab to a docked pane and destroy the floating pane. */
197
+ | {
198
+ readonly type: 'unfloat';
199
+ readonly paneId: PaneId;
200
+ readonly toPaneId: PaneId;
201
+ readonly index: number;
202
+ }
203
+ /** Net result of dragging a floating pane; the pane is focused and raised with it. */
204
+ | {
205
+ readonly type: 'moveFloat';
206
+ readonly paneId: PaneId;
207
+ readonly x: number;
208
+ readonly y: number;
209
+ }
210
+ /** Net result of resizing a floating pane; the pane is focused and raised with it. */
211
+ | {
212
+ readonly type: 'resizeFloat';
213
+ readonly paneId: PaneId;
214
+ readonly rect: FloatRect;
215
+ }
216
+ /** Expand or collapse the docked area. */
217
+ | {
218
+ readonly type: 'setExpanded';
219
+ readonly expanded: boolean;
220
+ }
221
+ /** Switch how the docked area is presented. */
222
+ | {
223
+ readonly type: 'setMode';
224
+ readonly mode: DockMode;
225
+ }
226
+ /** Put a pane back, with the tab records it owned. */
227
+ | {
228
+ readonly type: 'insertPane';
229
+ readonly pane: PaneNode;
230
+ readonly tabs: readonly TabRecord[];
231
+ readonly attach: PaneAttachment;
232
+ }
233
+ /** Put one tab record back into a docked pane. */
234
+ | {
235
+ readonly type: 'insertTab';
236
+ readonly paneId: PaneId;
237
+ readonly tab: TabRecord;
238
+ readonly index: number;
239
+ }
240
+ /** Restore focus facts an operation displaced. */
241
+ | {
242
+ readonly type: 'restoreFocus';
243
+ readonly activePaneId: PaneId;
244
+ readonly floats: readonly PaneId[];
245
+ /** Active tab per pane, for the panes the inverted operation touched. */
246
+ readonly paneActiveTabs: Readonly<Record<PaneId, TabId | undefined>>;
247
+ };
248
+ /** Operation kinds that only move focus; `Sequencer` collapses runs of these into one undo step. */
249
+ export type FocusOpType = 'focusTab' | 'focusPane' | 'restoreFocus';
250
+ /** Result of applying one operation: the next state plus the operations that undo it, in order. */
251
+ export interface ApplyResult {
252
+ readonly state: LayoutState;
253
+ readonly inverse: readonly LayoutOp[];
254
+ }
255
+ //# sourceMappingURL=types.d.ts.map