react-zeugma 5.0.0 → 5.1.0
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.
- package/README.md +34 -6
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -295
- package/dist/index.d.ts +4 -295
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/types-CaiXD2pG.d.cts +242 -0
- package/dist/types-CaiXD2pG.d.ts +242 -0
- package/dist/utils.cjs +2 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +83 -0
- package/dist/utils.d.ts +83 -0
- package/dist/utils.js +2 -0
- package/dist/utils.js.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction, ReactNode, RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
type SplitDirection = 'row' | 'column';
|
|
4
|
+
interface SplitNode {
|
|
5
|
+
type: 'split';
|
|
6
|
+
direction: SplitDirection;
|
|
7
|
+
first: TreeNode;
|
|
8
|
+
second: TreeNode;
|
|
9
|
+
splitPercentage: number;
|
|
10
|
+
}
|
|
11
|
+
interface PaneNode {
|
|
12
|
+
type: 'pane';
|
|
13
|
+
id: string;
|
|
14
|
+
tabs: string[];
|
|
15
|
+
activeTabId: string;
|
|
16
|
+
locked?: boolean;
|
|
17
|
+
tabsMetadata?: Record<string, Record<string, unknown>>;
|
|
18
|
+
}
|
|
19
|
+
type TreeNode = SplitNode | PaneNode;
|
|
20
|
+
interface TabDetails {
|
|
21
|
+
id: string;
|
|
22
|
+
paneId: string;
|
|
23
|
+
isActive: boolean;
|
|
24
|
+
index: number;
|
|
25
|
+
metadata: Record<string, unknown> | undefined;
|
|
26
|
+
}
|
|
27
|
+
interface UseZeugmaOptions {
|
|
28
|
+
/** Initial layout tree model defining pane organization for uncontrolled mode. Only used on initial mount. */
|
|
29
|
+
initialLayout?: TreeNode | null;
|
|
30
|
+
/** Controlled layout tree model. If provided, the hook will run in controlled mode and sync with this value. */
|
|
31
|
+
layout?: TreeNode | null;
|
|
32
|
+
/** Callback triggered when the layout changes via drag-and-drop actions, splits, moves, or resizes. */
|
|
33
|
+
onChange?: (newLayout: TreeNode | null) => void;
|
|
34
|
+
/** The ID of the pane that is currently taking up the full dashboard area. Null if no pane is fullscreen. */
|
|
35
|
+
fullscreenPaneId?: string | null;
|
|
36
|
+
/** Callback triggered when a pane is toggled to/from fullscreen mode. Passes the active fullscreen paneId or null. */
|
|
37
|
+
onFullscreenChange?: (paneId: string | null) => void;
|
|
38
|
+
/** Whether the layout is locked. When locked, resizing, dragging, and dropping are disabled. */
|
|
39
|
+
locked?: boolean;
|
|
40
|
+
/** Minimum pixel distance that a user must drag a pane handle before dragging triggers. Defaults to 8. */
|
|
41
|
+
dragActivationDistance?: number;
|
|
42
|
+
/** Threshold value in pixels for snapping layout resizing handles to adjacent edges. Defaults to 8. */
|
|
43
|
+
snapThreshold?: number;
|
|
44
|
+
/** Minimum split percentage allowed when resizing split panes. Defaults to 5. */
|
|
45
|
+
minSplitPercentage?: number;
|
|
46
|
+
/** Maximum split percentage allowed when resizing split panes. Defaults to 95. */
|
|
47
|
+
maxSplitPercentage?: number;
|
|
48
|
+
/** Whether dragging a pane far enough outside the container triggers a drag-out/dismiss action. Defaults to false. */
|
|
49
|
+
enableDragToDismiss?: boolean;
|
|
50
|
+
/** The threshold in pixels beyond the container boundaries required to activate the drag-out/dismiss action. Defaults to 60. */
|
|
51
|
+
dismissThreshold?: number;
|
|
52
|
+
/** Callback triggered when a pane is removed from the dashboard layout tree. */
|
|
53
|
+
onRemove?: (paneId: string) => void;
|
|
54
|
+
/** Callback triggered when dragging starts for a pane. */
|
|
55
|
+
onDragStart?: (activeId: string) => void;
|
|
56
|
+
/** Callback triggered when dragging ends, providing details on target pane and drop action (split or move). */
|
|
57
|
+
onDragEnd?: (activeId: string, overId: string | null, dropAction: {
|
|
58
|
+
type: 'split' | 'move';
|
|
59
|
+
direction?: SplitDirection;
|
|
60
|
+
position?: 'top' | 'bottom' | 'left' | 'right' | 'center';
|
|
61
|
+
} | null) => void;
|
|
62
|
+
/** Callback triggered when the user starts dragging a resizing handle between split panes. */
|
|
63
|
+
onResizeStart?: (currentNode: SplitNode) => void;
|
|
64
|
+
/** Callback triggered continuously while the user is dragging a resizing handle. Passes the new split percentage. */
|
|
65
|
+
onResize?: (currentNode: SplitNode, percentage: number) => void;
|
|
66
|
+
/** Callback triggered when the user stops dragging a resizing handle. Passes the final split percentage. */
|
|
67
|
+
onResizeEnd?: (currentNode: SplitNode, percentage: number) => void;
|
|
68
|
+
/** Callback triggered when the drag-out/dismiss intent changes. */
|
|
69
|
+
onDismissIntentChange?: (paneId: string | null) => void;
|
|
70
|
+
}
|
|
71
|
+
interface ZeugmaController {
|
|
72
|
+
/** The current active layout tree structure, or null if empty. */
|
|
73
|
+
layout: TreeNode | null;
|
|
74
|
+
/** Updates the layout tree. */
|
|
75
|
+
setLayout: Dispatch<SetStateAction<TreeNode | null>>;
|
|
76
|
+
/** The ID of the pane currently zoomed to fullscreen, or null. */
|
|
77
|
+
fullscreenPaneId: string | null;
|
|
78
|
+
/** Programmatically sets the fullscreen pane ID. */
|
|
79
|
+
setFullscreenPaneId: (paneId: string | null) => void;
|
|
80
|
+
/** Whether the layout is globally locked. */
|
|
81
|
+
locked: boolean;
|
|
82
|
+
/** Programmatically updates the global locked status. */
|
|
83
|
+
setLocked: Dispatch<SetStateAction<boolean>>;
|
|
84
|
+
/** Removes the specified pane from the layout tree and collapses its parent split. */
|
|
85
|
+
removePane: (paneId: string) => void;
|
|
86
|
+
/** Appends/inserts a pane at the bottom-rightmost leaf of the layout tree. */
|
|
87
|
+
addPane: (paneId: string) => void;
|
|
88
|
+
/** Stable callback to update metadata for a specific tab. */
|
|
89
|
+
updateTabMetadata: (tabId: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => void;
|
|
90
|
+
/** Stable callback to update the locked status of a specific pane in the layout tree. */
|
|
91
|
+
updatePaneLock: (paneId: string, locked: boolean) => void;
|
|
92
|
+
/** Stable callback to activate a tab within a pane. */
|
|
93
|
+
selectTab: (paneId: string, tabId: string) => void;
|
|
94
|
+
/** Stable callback to merge a dragged tab/pane into another pane's tab list. */
|
|
95
|
+
mergeTab: (draggedTabId: string, targetPaneId: string) => void;
|
|
96
|
+
/** Stable callback to remove/close a specific tab from the layout. */
|
|
97
|
+
removeTab: (tabId: string) => void;
|
|
98
|
+
/** Find a PaneNode by its ID in the layout tree. */
|
|
99
|
+
findPaneById: (paneId: string) => PaneNode | null;
|
|
100
|
+
/** Find the PaneNode containing the given tab ID in the layout tree. */
|
|
101
|
+
findPaneContainingTab: (tabId: string) => PaneNode | null;
|
|
102
|
+
/** Find the details of a tab by its ID in the layout tree. */
|
|
103
|
+
findTabById: (tabId: string) => TabDetails | null;
|
|
104
|
+
}
|
|
105
|
+
/** @internal */
|
|
106
|
+
interface ZeugmaInternalController extends ZeugmaController {
|
|
107
|
+
activeId: string | null;
|
|
108
|
+
setActiveId: Dispatch<SetStateAction<string | null>>;
|
|
109
|
+
activeType: 'pane' | 'tab' | null;
|
|
110
|
+
setActiveType: Dispatch<SetStateAction<'pane' | 'tab' | null>>;
|
|
111
|
+
dismissIntentId: string | null;
|
|
112
|
+
setDismissIntentId: Dispatch<SetStateAction<string | null>>;
|
|
113
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
114
|
+
setContainerRef: (element: HTMLElement | null) => void;
|
|
115
|
+
dragActivationDistance: number;
|
|
116
|
+
snapThreshold: number;
|
|
117
|
+
minSplitPercentage: number;
|
|
118
|
+
maxSplitPercentage: number;
|
|
119
|
+
enableDragToDismiss: boolean;
|
|
120
|
+
dismissThreshold: number;
|
|
121
|
+
onRemove?: (paneId: string) => void;
|
|
122
|
+
onDragStart?: (activeId: string) => void;
|
|
123
|
+
onDragEnd?: (activeId: string, overId: string | null, dropAction: {
|
|
124
|
+
type: 'split' | 'move';
|
|
125
|
+
direction?: SplitDirection;
|
|
126
|
+
position?: 'top' | 'bottom' | 'left' | 'right' | 'center';
|
|
127
|
+
} | null) => void;
|
|
128
|
+
onResizeStart?: (currentNode: SplitNode) => void;
|
|
129
|
+
onResize?: (currentNode: SplitNode, percentage: number) => void;
|
|
130
|
+
onResizeEnd?: (currentNode: SplitNode, percentage: number) => void;
|
|
131
|
+
onDismissIntentChange?: (paneId: string | null) => void;
|
|
132
|
+
splitPane: (targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string) => void;
|
|
133
|
+
updateSplitPercentage: (currentNode: SplitNode, percentage: number) => void;
|
|
134
|
+
moveTab: (draggedTabId: string, targetTabId: string, position?: 'before' | 'after') => void;
|
|
135
|
+
}
|
|
136
|
+
interface ZeugmaClassNames {
|
|
137
|
+
/** CSS class applied to the root dashboard container. */
|
|
138
|
+
dashboard?: string;
|
|
139
|
+
/** CSS class applied to the root dashboard container when a drag-out dismiss is active. */
|
|
140
|
+
dashboardDismissActive?: string;
|
|
141
|
+
/** CSS class applied to the outer container div of each `<Pane>`. */
|
|
142
|
+
pane?: string;
|
|
143
|
+
/** CSS class applied to the pane container when locked. */
|
|
144
|
+
paneLocked?: string;
|
|
145
|
+
/** CSS class applied to drop zone indicators when hovering over layout edges to split a pane. */
|
|
146
|
+
dropPreview?: string;
|
|
147
|
+
/** CSS class applied to the custom cursor-following drag preview portal wrapper. */
|
|
148
|
+
dragOverlay?: string;
|
|
149
|
+
/** CSS class applied to the drag-to-resize split bar handles. */
|
|
150
|
+
resizer?: string;
|
|
151
|
+
/** CSS class applied to the background dismiss zone indicator during a drag-out dismiss gesture. */
|
|
152
|
+
dismissPreview?: string;
|
|
153
|
+
/** CSS class applied to root container when dashboard is globally locked. */
|
|
154
|
+
dashboardLocked?: string;
|
|
155
|
+
/** CSS class applied to drop zone indicator when hovering over a locked pane. */
|
|
156
|
+
lockedPreview?: string;
|
|
157
|
+
/** CSS class applied to tab container when dragging a tab over it. */
|
|
158
|
+
tabDropPreview?: string;
|
|
159
|
+
}
|
|
160
|
+
interface ZeugmaProps extends ZeugmaController {
|
|
161
|
+
/** Render function mapping unique pane IDs to React elements. Usually renders a <Pane> wrapper. */
|
|
162
|
+
renderPane: (paneId: string) => ReactNode;
|
|
163
|
+
/** Custom overlay renderer function used to customize the cursor-following drag preview for an active pane or tab. */
|
|
164
|
+
renderDragOverlay?: (activeId: string, type: 'pane' | 'tab') => ReactNode;
|
|
165
|
+
/** Optional CSS class name mapping overrides for custom styles of components like panes, drop previews, overlays, etc. */
|
|
166
|
+
classNames?: ZeugmaClassNames;
|
|
167
|
+
/** Render function mapping tab IDs to React elements. Used to render tab widgets inside portals. */
|
|
168
|
+
renderWidget?: (tabId: string) => ReactNode;
|
|
169
|
+
/** Child nodes nested inside the Zeugma context, usually containing a <PaneTree> or similar layout viewer. */
|
|
170
|
+
children: ReactNode;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* State context — holds reactive values that change during runtime.
|
|
174
|
+
* All consumers of this context will re-render when any of these values change.
|
|
175
|
+
*/
|
|
176
|
+
interface ZeugmaStateValue {
|
|
177
|
+
/** The current active layout tree structure, or null if empty. */
|
|
178
|
+
layout: TreeNode | null;
|
|
179
|
+
/** Callback to update the layout tree. */
|
|
180
|
+
setLayout: Dispatch<SetStateAction<TreeNode | null>>;
|
|
181
|
+
/** Renders the inner content of a pane given its unique ID. */
|
|
182
|
+
renderPane: (paneId: string) => ReactNode;
|
|
183
|
+
/** The ID of the pane currently zoomed to fullscreen, or null. */
|
|
184
|
+
fullscreenPaneId: string | null;
|
|
185
|
+
/** Normalized or overridden CSS classes for custom layout styling. */
|
|
186
|
+
classNames: ZeugmaClassNames;
|
|
187
|
+
/** Whether the layout is globally locked. */
|
|
188
|
+
locked: boolean;
|
|
189
|
+
/** Programmatically updates the global locked status. */
|
|
190
|
+
setLocked: Dispatch<SetStateAction<boolean>>;
|
|
191
|
+
/** Find a PaneNode by its ID in the layout tree. */
|
|
192
|
+
findPaneById: (paneId: string) => PaneNode | null;
|
|
193
|
+
/** Find the PaneNode containing the given tab ID in the layout tree. */
|
|
194
|
+
findPaneContainingTab: (tabId: string) => PaneNode | null;
|
|
195
|
+
/** Find the details of a tab by its ID in the layout tree. */
|
|
196
|
+
findTabById: (tabId: string) => TabDetails | null;
|
|
197
|
+
}
|
|
198
|
+
/** @internal */
|
|
199
|
+
interface ZeugmaInternalStateValue extends ZeugmaStateValue {
|
|
200
|
+
/** The ID of the tab currently hovered over during a tab drag, or null. */
|
|
201
|
+
overTabId: string | null;
|
|
202
|
+
/** The position of the tab drop preview relative to the hovered tab ('before' | 'after'). */
|
|
203
|
+
overTabPosition: 'before' | 'after' | null;
|
|
204
|
+
activeId: string | null;
|
|
205
|
+
dismissIntentId: string | null;
|
|
206
|
+
setContainerRef: (element: HTMLElement | null) => void;
|
|
207
|
+
onRemove?: (paneId: string) => void;
|
|
208
|
+
onFullscreenChange?: (paneId: string | null) => void;
|
|
209
|
+
snapThreshold?: number;
|
|
210
|
+
onResizeStart?: (currentNode: SplitNode) => void;
|
|
211
|
+
onResize?: (currentNode: SplitNode, percentage: number) => void;
|
|
212
|
+
onResizeEnd?: (currentNode: SplitNode, percentage: number) => void;
|
|
213
|
+
minSplitPercentage?: number;
|
|
214
|
+
maxSplitPercentage?: number;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Actions context — holds stable dispatch functions with permanent identity.
|
|
218
|
+
* Consumers of only this context will never re-render from layout/drag state changes.
|
|
219
|
+
*/
|
|
220
|
+
interface ZeugmaActionsValue {
|
|
221
|
+
/** Removes the specified pane from the layout tree and collapses its parent split. */
|
|
222
|
+
removePane: (paneId: string) => void;
|
|
223
|
+
/** Appends/inserts a pane at the bottom-rightmost leaf of the layout tree. */
|
|
224
|
+
addPane: (paneId: string) => void;
|
|
225
|
+
/** Stable callback to update metadata for a specific tab. */
|
|
226
|
+
updateTabMetadata: (tabId: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => void;
|
|
227
|
+
/** Stable callback to update the locked status of a specific pane in the layout tree. */
|
|
228
|
+
updatePaneLock: (paneId: string, locked: boolean) => void;
|
|
229
|
+
/** Stable callback to activate a tab within a pane. */
|
|
230
|
+
selectTab: (paneId: string, tabId: string) => void;
|
|
231
|
+
/** Stable callback to merge a dragged tab/pane into another pane's tab list. */
|
|
232
|
+
mergeTab: (draggedTabId: string, targetPaneId: string) => void;
|
|
233
|
+
/** Stable callback to remove/close a specific tab from the layout. */
|
|
234
|
+
removeTab: (tabId: string) => void;
|
|
235
|
+
}
|
|
236
|
+
interface ZeugmaContextValue extends ZeugmaStateValue, ZeugmaActionsValue {
|
|
237
|
+
}
|
|
238
|
+
interface PortalRegistryValue {
|
|
239
|
+
registerPortalTarget: (tabId: string, el: HTMLDivElement | null) => void;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type { PortalRegistryValue as P, SplitDirection as S, TreeNode as T, UseZeugmaOptions as U, ZeugmaActionsValue as Z, ZeugmaInternalStateValue as a, ZeugmaStateValue as b, ZeugmaController as c, ZeugmaContextValue as d, ZeugmaProps as e, SplitNode as f, PaneNode as g, TabDetails as h, ZeugmaClassNames as i, ZeugmaInternalController as j };
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { Dispatch, SetStateAction, ReactNode, RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
type SplitDirection = 'row' | 'column';
|
|
4
|
+
interface SplitNode {
|
|
5
|
+
type: 'split';
|
|
6
|
+
direction: SplitDirection;
|
|
7
|
+
first: TreeNode;
|
|
8
|
+
second: TreeNode;
|
|
9
|
+
splitPercentage: number;
|
|
10
|
+
}
|
|
11
|
+
interface PaneNode {
|
|
12
|
+
type: 'pane';
|
|
13
|
+
id: string;
|
|
14
|
+
tabs: string[];
|
|
15
|
+
activeTabId: string;
|
|
16
|
+
locked?: boolean;
|
|
17
|
+
tabsMetadata?: Record<string, Record<string, unknown>>;
|
|
18
|
+
}
|
|
19
|
+
type TreeNode = SplitNode | PaneNode;
|
|
20
|
+
interface TabDetails {
|
|
21
|
+
id: string;
|
|
22
|
+
paneId: string;
|
|
23
|
+
isActive: boolean;
|
|
24
|
+
index: number;
|
|
25
|
+
metadata: Record<string, unknown> | undefined;
|
|
26
|
+
}
|
|
27
|
+
interface UseZeugmaOptions {
|
|
28
|
+
/** Initial layout tree model defining pane organization for uncontrolled mode. Only used on initial mount. */
|
|
29
|
+
initialLayout?: TreeNode | null;
|
|
30
|
+
/** Controlled layout tree model. If provided, the hook will run in controlled mode and sync with this value. */
|
|
31
|
+
layout?: TreeNode | null;
|
|
32
|
+
/** Callback triggered when the layout changes via drag-and-drop actions, splits, moves, or resizes. */
|
|
33
|
+
onChange?: (newLayout: TreeNode | null) => void;
|
|
34
|
+
/** The ID of the pane that is currently taking up the full dashboard area. Null if no pane is fullscreen. */
|
|
35
|
+
fullscreenPaneId?: string | null;
|
|
36
|
+
/** Callback triggered when a pane is toggled to/from fullscreen mode. Passes the active fullscreen paneId or null. */
|
|
37
|
+
onFullscreenChange?: (paneId: string | null) => void;
|
|
38
|
+
/** Whether the layout is locked. When locked, resizing, dragging, and dropping are disabled. */
|
|
39
|
+
locked?: boolean;
|
|
40
|
+
/** Minimum pixel distance that a user must drag a pane handle before dragging triggers. Defaults to 8. */
|
|
41
|
+
dragActivationDistance?: number;
|
|
42
|
+
/** Threshold value in pixels for snapping layout resizing handles to adjacent edges. Defaults to 8. */
|
|
43
|
+
snapThreshold?: number;
|
|
44
|
+
/** Minimum split percentage allowed when resizing split panes. Defaults to 5. */
|
|
45
|
+
minSplitPercentage?: number;
|
|
46
|
+
/** Maximum split percentage allowed when resizing split panes. Defaults to 95. */
|
|
47
|
+
maxSplitPercentage?: number;
|
|
48
|
+
/** Whether dragging a pane far enough outside the container triggers a drag-out/dismiss action. Defaults to false. */
|
|
49
|
+
enableDragToDismiss?: boolean;
|
|
50
|
+
/** The threshold in pixels beyond the container boundaries required to activate the drag-out/dismiss action. Defaults to 60. */
|
|
51
|
+
dismissThreshold?: number;
|
|
52
|
+
/** Callback triggered when a pane is removed from the dashboard layout tree. */
|
|
53
|
+
onRemove?: (paneId: string) => void;
|
|
54
|
+
/** Callback triggered when dragging starts for a pane. */
|
|
55
|
+
onDragStart?: (activeId: string) => void;
|
|
56
|
+
/** Callback triggered when dragging ends, providing details on target pane and drop action (split or move). */
|
|
57
|
+
onDragEnd?: (activeId: string, overId: string | null, dropAction: {
|
|
58
|
+
type: 'split' | 'move';
|
|
59
|
+
direction?: SplitDirection;
|
|
60
|
+
position?: 'top' | 'bottom' | 'left' | 'right' | 'center';
|
|
61
|
+
} | null) => void;
|
|
62
|
+
/** Callback triggered when the user starts dragging a resizing handle between split panes. */
|
|
63
|
+
onResizeStart?: (currentNode: SplitNode) => void;
|
|
64
|
+
/** Callback triggered continuously while the user is dragging a resizing handle. Passes the new split percentage. */
|
|
65
|
+
onResize?: (currentNode: SplitNode, percentage: number) => void;
|
|
66
|
+
/** Callback triggered when the user stops dragging a resizing handle. Passes the final split percentage. */
|
|
67
|
+
onResizeEnd?: (currentNode: SplitNode, percentage: number) => void;
|
|
68
|
+
/** Callback triggered when the drag-out/dismiss intent changes. */
|
|
69
|
+
onDismissIntentChange?: (paneId: string | null) => void;
|
|
70
|
+
}
|
|
71
|
+
interface ZeugmaController {
|
|
72
|
+
/** The current active layout tree structure, or null if empty. */
|
|
73
|
+
layout: TreeNode | null;
|
|
74
|
+
/** Updates the layout tree. */
|
|
75
|
+
setLayout: Dispatch<SetStateAction<TreeNode | null>>;
|
|
76
|
+
/** The ID of the pane currently zoomed to fullscreen, or null. */
|
|
77
|
+
fullscreenPaneId: string | null;
|
|
78
|
+
/** Programmatically sets the fullscreen pane ID. */
|
|
79
|
+
setFullscreenPaneId: (paneId: string | null) => void;
|
|
80
|
+
/** Whether the layout is globally locked. */
|
|
81
|
+
locked: boolean;
|
|
82
|
+
/** Programmatically updates the global locked status. */
|
|
83
|
+
setLocked: Dispatch<SetStateAction<boolean>>;
|
|
84
|
+
/** Removes the specified pane from the layout tree and collapses its parent split. */
|
|
85
|
+
removePane: (paneId: string) => void;
|
|
86
|
+
/** Appends/inserts a pane at the bottom-rightmost leaf of the layout tree. */
|
|
87
|
+
addPane: (paneId: string) => void;
|
|
88
|
+
/** Stable callback to update metadata for a specific tab. */
|
|
89
|
+
updateTabMetadata: (tabId: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => void;
|
|
90
|
+
/** Stable callback to update the locked status of a specific pane in the layout tree. */
|
|
91
|
+
updatePaneLock: (paneId: string, locked: boolean) => void;
|
|
92
|
+
/** Stable callback to activate a tab within a pane. */
|
|
93
|
+
selectTab: (paneId: string, tabId: string) => void;
|
|
94
|
+
/** Stable callback to merge a dragged tab/pane into another pane's tab list. */
|
|
95
|
+
mergeTab: (draggedTabId: string, targetPaneId: string) => void;
|
|
96
|
+
/** Stable callback to remove/close a specific tab from the layout. */
|
|
97
|
+
removeTab: (tabId: string) => void;
|
|
98
|
+
/** Find a PaneNode by its ID in the layout tree. */
|
|
99
|
+
findPaneById: (paneId: string) => PaneNode | null;
|
|
100
|
+
/** Find the PaneNode containing the given tab ID in the layout tree. */
|
|
101
|
+
findPaneContainingTab: (tabId: string) => PaneNode | null;
|
|
102
|
+
/** Find the details of a tab by its ID in the layout tree. */
|
|
103
|
+
findTabById: (tabId: string) => TabDetails | null;
|
|
104
|
+
}
|
|
105
|
+
/** @internal */
|
|
106
|
+
interface ZeugmaInternalController extends ZeugmaController {
|
|
107
|
+
activeId: string | null;
|
|
108
|
+
setActiveId: Dispatch<SetStateAction<string | null>>;
|
|
109
|
+
activeType: 'pane' | 'tab' | null;
|
|
110
|
+
setActiveType: Dispatch<SetStateAction<'pane' | 'tab' | null>>;
|
|
111
|
+
dismissIntentId: string | null;
|
|
112
|
+
setDismissIntentId: Dispatch<SetStateAction<string | null>>;
|
|
113
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
114
|
+
setContainerRef: (element: HTMLElement | null) => void;
|
|
115
|
+
dragActivationDistance: number;
|
|
116
|
+
snapThreshold: number;
|
|
117
|
+
minSplitPercentage: number;
|
|
118
|
+
maxSplitPercentage: number;
|
|
119
|
+
enableDragToDismiss: boolean;
|
|
120
|
+
dismissThreshold: number;
|
|
121
|
+
onRemove?: (paneId: string) => void;
|
|
122
|
+
onDragStart?: (activeId: string) => void;
|
|
123
|
+
onDragEnd?: (activeId: string, overId: string | null, dropAction: {
|
|
124
|
+
type: 'split' | 'move';
|
|
125
|
+
direction?: SplitDirection;
|
|
126
|
+
position?: 'top' | 'bottom' | 'left' | 'right' | 'center';
|
|
127
|
+
} | null) => void;
|
|
128
|
+
onResizeStart?: (currentNode: SplitNode) => void;
|
|
129
|
+
onResize?: (currentNode: SplitNode, percentage: number) => void;
|
|
130
|
+
onResizeEnd?: (currentNode: SplitNode, percentage: number) => void;
|
|
131
|
+
onDismissIntentChange?: (paneId: string | null) => void;
|
|
132
|
+
splitPane: (targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string) => void;
|
|
133
|
+
updateSplitPercentage: (currentNode: SplitNode, percentage: number) => void;
|
|
134
|
+
moveTab: (draggedTabId: string, targetTabId: string, position?: 'before' | 'after') => void;
|
|
135
|
+
}
|
|
136
|
+
interface ZeugmaClassNames {
|
|
137
|
+
/** CSS class applied to the root dashboard container. */
|
|
138
|
+
dashboard?: string;
|
|
139
|
+
/** CSS class applied to the root dashboard container when a drag-out dismiss is active. */
|
|
140
|
+
dashboardDismissActive?: string;
|
|
141
|
+
/** CSS class applied to the outer container div of each `<Pane>`. */
|
|
142
|
+
pane?: string;
|
|
143
|
+
/** CSS class applied to the pane container when locked. */
|
|
144
|
+
paneLocked?: string;
|
|
145
|
+
/** CSS class applied to drop zone indicators when hovering over layout edges to split a pane. */
|
|
146
|
+
dropPreview?: string;
|
|
147
|
+
/** CSS class applied to the custom cursor-following drag preview portal wrapper. */
|
|
148
|
+
dragOverlay?: string;
|
|
149
|
+
/** CSS class applied to the drag-to-resize split bar handles. */
|
|
150
|
+
resizer?: string;
|
|
151
|
+
/** CSS class applied to the background dismiss zone indicator during a drag-out dismiss gesture. */
|
|
152
|
+
dismissPreview?: string;
|
|
153
|
+
/** CSS class applied to root container when dashboard is globally locked. */
|
|
154
|
+
dashboardLocked?: string;
|
|
155
|
+
/** CSS class applied to drop zone indicator when hovering over a locked pane. */
|
|
156
|
+
lockedPreview?: string;
|
|
157
|
+
/** CSS class applied to tab container when dragging a tab over it. */
|
|
158
|
+
tabDropPreview?: string;
|
|
159
|
+
}
|
|
160
|
+
interface ZeugmaProps extends ZeugmaController {
|
|
161
|
+
/** Render function mapping unique pane IDs to React elements. Usually renders a <Pane> wrapper. */
|
|
162
|
+
renderPane: (paneId: string) => ReactNode;
|
|
163
|
+
/** Custom overlay renderer function used to customize the cursor-following drag preview for an active pane or tab. */
|
|
164
|
+
renderDragOverlay?: (activeId: string, type: 'pane' | 'tab') => ReactNode;
|
|
165
|
+
/** Optional CSS class name mapping overrides for custom styles of components like panes, drop previews, overlays, etc. */
|
|
166
|
+
classNames?: ZeugmaClassNames;
|
|
167
|
+
/** Render function mapping tab IDs to React elements. Used to render tab widgets inside portals. */
|
|
168
|
+
renderWidget?: (tabId: string) => ReactNode;
|
|
169
|
+
/** Child nodes nested inside the Zeugma context, usually containing a <PaneTree> or similar layout viewer. */
|
|
170
|
+
children: ReactNode;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* State context — holds reactive values that change during runtime.
|
|
174
|
+
* All consumers of this context will re-render when any of these values change.
|
|
175
|
+
*/
|
|
176
|
+
interface ZeugmaStateValue {
|
|
177
|
+
/** The current active layout tree structure, or null if empty. */
|
|
178
|
+
layout: TreeNode | null;
|
|
179
|
+
/** Callback to update the layout tree. */
|
|
180
|
+
setLayout: Dispatch<SetStateAction<TreeNode | null>>;
|
|
181
|
+
/** Renders the inner content of a pane given its unique ID. */
|
|
182
|
+
renderPane: (paneId: string) => ReactNode;
|
|
183
|
+
/** The ID of the pane currently zoomed to fullscreen, or null. */
|
|
184
|
+
fullscreenPaneId: string | null;
|
|
185
|
+
/** Normalized or overridden CSS classes for custom layout styling. */
|
|
186
|
+
classNames: ZeugmaClassNames;
|
|
187
|
+
/** Whether the layout is globally locked. */
|
|
188
|
+
locked: boolean;
|
|
189
|
+
/** Programmatically updates the global locked status. */
|
|
190
|
+
setLocked: Dispatch<SetStateAction<boolean>>;
|
|
191
|
+
/** Find a PaneNode by its ID in the layout tree. */
|
|
192
|
+
findPaneById: (paneId: string) => PaneNode | null;
|
|
193
|
+
/** Find the PaneNode containing the given tab ID in the layout tree. */
|
|
194
|
+
findPaneContainingTab: (tabId: string) => PaneNode | null;
|
|
195
|
+
/** Find the details of a tab by its ID in the layout tree. */
|
|
196
|
+
findTabById: (tabId: string) => TabDetails | null;
|
|
197
|
+
}
|
|
198
|
+
/** @internal */
|
|
199
|
+
interface ZeugmaInternalStateValue extends ZeugmaStateValue {
|
|
200
|
+
/** The ID of the tab currently hovered over during a tab drag, or null. */
|
|
201
|
+
overTabId: string | null;
|
|
202
|
+
/** The position of the tab drop preview relative to the hovered tab ('before' | 'after'). */
|
|
203
|
+
overTabPosition: 'before' | 'after' | null;
|
|
204
|
+
activeId: string | null;
|
|
205
|
+
dismissIntentId: string | null;
|
|
206
|
+
setContainerRef: (element: HTMLElement | null) => void;
|
|
207
|
+
onRemove?: (paneId: string) => void;
|
|
208
|
+
onFullscreenChange?: (paneId: string | null) => void;
|
|
209
|
+
snapThreshold?: number;
|
|
210
|
+
onResizeStart?: (currentNode: SplitNode) => void;
|
|
211
|
+
onResize?: (currentNode: SplitNode, percentage: number) => void;
|
|
212
|
+
onResizeEnd?: (currentNode: SplitNode, percentage: number) => void;
|
|
213
|
+
minSplitPercentage?: number;
|
|
214
|
+
maxSplitPercentage?: number;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Actions context — holds stable dispatch functions with permanent identity.
|
|
218
|
+
* Consumers of only this context will never re-render from layout/drag state changes.
|
|
219
|
+
*/
|
|
220
|
+
interface ZeugmaActionsValue {
|
|
221
|
+
/** Removes the specified pane from the layout tree and collapses its parent split. */
|
|
222
|
+
removePane: (paneId: string) => void;
|
|
223
|
+
/** Appends/inserts a pane at the bottom-rightmost leaf of the layout tree. */
|
|
224
|
+
addPane: (paneId: string) => void;
|
|
225
|
+
/** Stable callback to update metadata for a specific tab. */
|
|
226
|
+
updateTabMetadata: (tabId: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined) => void;
|
|
227
|
+
/** Stable callback to update the locked status of a specific pane in the layout tree. */
|
|
228
|
+
updatePaneLock: (paneId: string, locked: boolean) => void;
|
|
229
|
+
/** Stable callback to activate a tab within a pane. */
|
|
230
|
+
selectTab: (paneId: string, tabId: string) => void;
|
|
231
|
+
/** Stable callback to merge a dragged tab/pane into another pane's tab list. */
|
|
232
|
+
mergeTab: (draggedTabId: string, targetPaneId: string) => void;
|
|
233
|
+
/** Stable callback to remove/close a specific tab from the layout. */
|
|
234
|
+
removeTab: (tabId: string) => void;
|
|
235
|
+
}
|
|
236
|
+
interface ZeugmaContextValue extends ZeugmaStateValue, ZeugmaActionsValue {
|
|
237
|
+
}
|
|
238
|
+
interface PortalRegistryValue {
|
|
239
|
+
registerPortalTarget: (tabId: string, el: HTMLDivElement | null) => void;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type { PortalRegistryValue as P, SplitDirection as S, TreeNode as T, UseZeugmaOptions as U, ZeugmaActionsValue as Z, ZeugmaInternalStateValue as a, ZeugmaStateValue as b, ZeugmaController as c, ZeugmaContextValue as d, ZeugmaProps as e, SplitNode as f, PaneNode as g, TabDetails as h, ZeugmaClassNames as i, ZeugmaInternalController as j };
|
package/dist/utils.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';function p(){return "pane-"+Math.random().toString(36).substring(2,11)}function P(n,t){if(n===null)return null;if(n.type==="pane")return n.id===t?null:n;let e=P(n.first,t),i=P(n.second,t);return e===null?i:i===null?e:{...n,first:e,second:i}}function T(n,t){if(n===null)return null;if(n.type==="pane"){if(n.tabs.includes(t)){let r=n.tabs.filter(l=>l!==t);if(r.length===0)return null;let u=n.activeTabId;n.activeTabId===t&&(u=r[0]);let s={...n.tabsMetadata};return delete s[t],{...n,tabs:r,activeTabId:u,tabsMetadata:Object.keys(s).length>0?s:void 0}}return n}let e=T(n.first,t),i=T(n.second,t);return e===null?i:i===null?e:{...n,first:e,second:i}}function M(n,t,e,i,r){if(n===null)return typeof r=="string"?{type:"pane",id:p(),tabs:[r],activeTabId:r}:r;if(n.type==="pane"){if(n.id===t){let u=typeof r=="string"?{type:"pane",id:p(),tabs:[r],activeTabId:r}:r,s=i==="left"||i==="top";return {type:"split",direction:e,first:s?u:n,second:s?n:u,splitPercentage:50}}return n}return {...n,first:M(n.first,t,e,i,r)||n.first,second:M(n.second,t,e,i,r)||n.second}}function L(n,t){if(n===null)return {type:"pane",id:p(),tabs:[t],activeTabId:t};function e(i,r){return i.type==="pane"?{type:"split",direction:r==="row"?"column":"row",splitPercentage:50,first:i,second:{type:"pane",id:p(),tabs:[t],activeTabId:t}}:{...i,second:e(i.second,i.direction)}}return e(n,null)}function x(n,t,e){return n===null?null:n===t?{...n,splitPercentage:e}:n.type==="split"?{...n,first:x(n.first,t,e)||n.first,second:x(n.second,t,e)||n.second}:n}function v(n,t){return n===null?null:n.type==="pane"?n.id===t?n:null:v(n.first,t)??v(n.second,t)}function N(n,t){return n===null?null:n.type==="pane"?n.tabs.includes(t)?n:null:N(n.first,t)??N(n.second,t)}function D(n,t){let e=N(n,t);if(!e)return null;let i=e.tabs.indexOf(t);return {id:t,paneId:e.id,isActive:e.activeTabId===t,index:i,metadata:e.tabsMetadata?.[t]}}function S(n,t,e){if(n===null)return null;if(n.type==="pane"){if(n.tabs.includes(t)){let i=n.tabsMetadata||{},r=i[t],u=e(r),s={...i};return u===void 0?delete s[t]:s[t]=u,{...n,tabsMetadata:Object.keys(s).length>0?s:void 0}}return n}return {...n,first:S(n.first,t,e)??n.first,second:S(n.second,t,e)??n.second}}function w(n,t,e){if(n===null)return null;if(n.type==="pane"){if(n.id===t){if(e===false){let{locked:i,...r}=n;return r}return {...n,locked:e}}return n}return {...n,first:w(n.first,t,e)??n.first,second:w(n.second,t,e)??n.second}}function C(n,t,e){return n===null?null:n.type==="pane"?n.id===t?{...n,activeTabId:e}:n:{...n,first:C(n.first,t,e)??n.first,second:C(n.second,t,e)??n.second}}function k(n,t,e){if(n===null)return null;let r=N(n,t)?.tabsMetadata?.[t],u=T(n,t);if(u===null)return {type:"pane",id:p(),tabs:[t],activeTabId:t,tabsMetadata:r?{[t]:r}:void 0};function s(l){if(l.type==="pane"){if(l.id===e){let o=[...l.tabs];o.includes(t)||o.push(t);let c={...l.tabsMetadata};return r&&(c[t]=r),{...l,tabs:o,activeTabId:t,tabsMetadata:Object.keys(c).length>0?c:void 0}}return l}return {...l,first:s(l.first),second:s(l.second)}}return s(u)}function O(n,t,e,i="before"){if(n===null)return null;let u=N(n,t)?.tabsMetadata?.[t],s=T(n,t);if(s===null)return {type:"pane",id:p(),tabs:[t],activeTabId:t,tabsMetadata:u?{[t]:u}:void 0};function l(o){if(o.type==="pane"){if(o.tabs.includes(e)){let y=[...o.tabs].filter(b=>b!==t),d=y.indexOf(e);d<0&&(d=0),i==="after"&&(d+=1),y.splice(d,0,t);let a={...o.tabsMetadata};return u&&(a[t]=u),{...o,tabs:y,activeTabId:t,tabsMetadata:Object.keys(a).length>0?a:void 0}}return o}return {...o,first:l(o.first),second:l(o.second)}}return l(s)}function m(n,t=0,e=0,i=100,r=100,u="root"){if(n===null)return {panes:[],splitters:[]};if(n.type==="pane")return {panes:[{paneId:n.id,left:t,top:e,width:i,height:r,node:n}],splitters:[]};let{direction:s,splitPercentage:l,first:o,second:c}=n,d={id:`splitter-${u}-${s}`,currentNode:n,direction:s,left:s==="row"?t+i*(l/100):t,top:s==="column"?e+r*(l/100):e,width:s==="row"?0:i,height:s==="column"?0:r,parentLeft:t,parentTop:e,parentWidth:i,parentHeight:r},a={panes:[],splitters:[]},b={panes:[],splitters:[]};if(s==="row"){let f=i*(l/100);a=m(o,t,e,f,r,`${u}-L`),b=m(c,t+f,e,i-f,r,`${u}-R`);}else {let f=r*(l/100);a=m(o,t,e,i,f,`${u}-T`),b=m(c,t,e+f,i,r-f,`${u}-B`);}return {panes:[...a.panes,...b.panes],splitters:[d,...a.splitters,...b.splitters]}}exports.addPane=L;exports.computeLayout=m;exports.findPaneById=v;exports.findPaneContainingTab=N;exports.findTabById=D;exports.generateUniqueId=p;exports.mergeTab=k;exports.moveTab=O;exports.removePane=P;exports.removeTab=T;exports.selectTab=C;exports.splitPane=M;exports.updatePaneLock=w;exports.updateSplitPercentage=x;exports.updateTabMetadata=S;//# sourceMappingURL=utils.cjs.map
|
|
2
|
+
//# sourceMappingURL=utils.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/lib/tree/tree-helpers.ts"],"names":["generateUniqueId","removePane","tree","paneId","newFirst","newSecond","removeTab","tabId","newTabs","t","newActive","newTabsMetadata","splitPane","targetId","direction","splitType","paneToAdd","addedNode","isFirst","addPane","insert","node","parentDirection","updateSplitPercentage","target","newPercentage","findPaneById","findPaneContainingTab","findTabById","pane","index","updateTabMetadata","updater","currentTabsMetadata","currentTabMeta","newTabMeta","updatePaneLock","locked","_","rest","selectTab","mergeTab","draggedTabId","targetPaneId","sourceMetadata","cleanTree","moveTab","targetTabId","position","filteredTabs","insertIndex","computeLayout","left","top","width","height","path","splitPercentage","first","second","currentSplitter","firstLayout","secondLayout","firstWidth","firstHeight"],"mappings":"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","file":"utils.cjs","sourcesContent":["import { TreeNode, SplitNode, SplitDirection, PaneNode, TabDetails } from '../../model'\n\nexport function generateUniqueId(): string {\n return 'pane-' + Math.random().toString(36).substring(2, 11)\n}\n\n/**\n * Tree Helper: Remove a pane container and consolidate the tree structure.\n */\nexport function removePane(tree: TreeNode | null, paneId: string): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n if (tree.id === paneId) return null\n return tree\n }\n const newFirst = removePane(tree.first, paneId)\n const newSecond = removePane(tree.second, paneId)\n if (newFirst === null) return newSecond\n if (newSecond === null) return newFirst\n return { ...tree, first: newFirst, second: newSecond }\n}\n\n/**\n * Tree Helper: Remove a tab from a pane and consolidate the tree structure if no tabs remain.\n */\nexport function removeTab(tree: TreeNode | null, tabId: string): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n if (tree.tabs.includes(tabId)) {\n const newTabs = tree.tabs.filter((t) => t !== tabId)\n if (newTabs.length === 0) return null\n let newActive = tree.activeTabId\n if (tree.activeTabId === tabId) {\n newActive = newTabs[0]\n }\n const newTabsMetadata = { ...tree.tabsMetadata }\n delete newTabsMetadata[tabId]\n return {\n ...tree,\n tabs: newTabs,\n activeTabId: newActive,\n tabsMetadata: Object.keys(newTabsMetadata).length > 0 ? newTabsMetadata : undefined,\n }\n }\n return tree\n }\n const newFirst = removeTab(tree.first, tabId)\n const newSecond = removeTab(tree.second, tabId)\n if (newFirst === null) return newSecond\n if (newSecond === null) return newFirst\n return { ...tree, first: newFirst, second: newSecond }\n}\n\n/**\n * Tree Helper: Insert a pane by splitting an existing target node.\n */\nexport function splitPane(\n tree: TreeNode | null,\n targetId: string,\n direction: SplitDirection,\n splitType: 'left' | 'right' | 'top' | 'bottom',\n paneToAdd: string | PaneNode,\n): TreeNode | null {\n if (tree === null) {\n if (typeof paneToAdd === 'string') {\n return { type: 'pane', id: generateUniqueId(), tabs: [paneToAdd], activeTabId: paneToAdd }\n }\n return paneToAdd\n }\n if (tree.type === 'pane') {\n if (tree.id === targetId) {\n const addedNode: PaneNode =\n typeof paneToAdd === 'string'\n ? { type: 'pane', id: generateUniqueId(), tabs: [paneToAdd], activeTabId: paneToAdd }\n : paneToAdd\n const isFirst = splitType === 'left' || splitType === 'top'\n return {\n type: 'split',\n direction,\n first: isFirst ? addedNode : tree,\n second: isFirst ? tree : addedNode,\n splitPercentage: 50,\n }\n }\n return tree\n }\n return {\n ...tree,\n first: splitPane(tree.first, targetId, direction, splitType, paneToAdd) || tree.first,\n second: splitPane(tree.second, targetId, direction, splitType, paneToAdd) || tree.second,\n }\n}\n\n/**\n * Tree Helper: Add a pane by recursively splitting the rightmost/bottommost pane in the tree.\n */\nexport function addPane(tree: TreeNode | null, paneToAdd: string): TreeNode {\n if (tree === null) {\n return { type: 'pane', id: generateUniqueId(), tabs: [paneToAdd], activeTabId: paneToAdd }\n }\n\n function insert(node: TreeNode, parentDirection: SplitDirection | null): TreeNode {\n if (node.type === 'pane') {\n const direction: SplitDirection = parentDirection === 'row' ? 'column' : 'row'\n return {\n type: 'split',\n direction,\n splitPercentage: 50,\n first: node,\n second: { type: 'pane', id: generateUniqueId(), tabs: [paneToAdd], activeTabId: paneToAdd },\n }\n }\n\n return {\n ...node,\n second: insert(node.second, node.direction),\n }\n }\n\n return insert(tree, null)\n}\n\n/**\n * Tree Helper: Update split percentage recursively.\n */\nexport function updateSplitPercentage(\n tree: TreeNode | null,\n target: SplitNode,\n newPercentage: number,\n): TreeNode | null {\n if (tree === null) return null\n if (tree === target) {\n return { ...tree, splitPercentage: newPercentage } as SplitNode\n }\n if (tree.type === 'split') {\n return {\n ...tree,\n first: updateSplitPercentage(tree.first, target, newPercentage) || tree.first,\n second: updateSplitPercentage(tree.second, target, newPercentage) || tree.second,\n }\n }\n return tree\n}\n\n/**\n * Find a PaneNode by its ID.\n */\nexport function findPaneById(tree: TreeNode | null, paneId: string): PaneNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n return tree.id === paneId ? tree : null\n }\n return findPaneById(tree.first, paneId) ?? findPaneById(tree.second, paneId)\n}\n\n/**\n * Find a PaneNode containing the given tab ID.\n */\nexport function findPaneContainingTab(tree: TreeNode | null, tabId: string): PaneNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n return tree.tabs.includes(tabId) ? tree : null\n }\n return findPaneContainingTab(tree.first, tabId) ?? findPaneContainingTab(tree.second, tabId)\n}\n\n/**\n * Find the details of a tab by its ID.\n */\nexport function findTabById(tree: TreeNode | null, tabId: string): TabDetails | null {\n const pane = findPaneContainingTab(tree, tabId)\n if (!pane) return null\n const index = pane.tabs.indexOf(tabId)\n return {\n id: tabId,\n paneId: pane.id,\n isActive: pane.activeTabId === tabId,\n index,\n metadata: pane.tabsMetadata?.[tabId],\n }\n}\n\n/**\n * Update metadata on a specific tab node using an updater function.\n */\nexport function updateTabMetadata(\n tree: TreeNode | null,\n tabId: string,\n updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined,\n): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n if (tree.tabs.includes(tabId)) {\n const currentTabsMetadata = tree.tabsMetadata || {}\n const currentTabMeta = currentTabsMetadata[tabId]\n const newTabMeta = updater(currentTabMeta)\n\n const newTabsMetadata = { ...currentTabsMetadata }\n if (newTabMeta === undefined) {\n delete newTabsMetadata[tabId]\n } else {\n newTabsMetadata[tabId] = newTabMeta\n }\n\n return {\n ...tree,\n tabsMetadata: Object.keys(newTabsMetadata).length > 0 ? newTabsMetadata : undefined,\n }\n }\n return tree\n }\n return {\n ...tree,\n first: updateTabMetadata(tree.first, tabId, updater) ?? tree.first,\n second: updateTabMetadata(tree.second, tabId, updater) ?? tree.second,\n }\n}\n\n/**\n * Update the locked status on a specific pane node in the layout tree.\n */\nexport function updatePaneLock(\n tree: TreeNode | null,\n paneId: string,\n locked: boolean,\n): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n if (tree.id === paneId) {\n if (locked === false) {\n const { locked: _, ...rest } = tree\n return rest as PaneNode\n }\n return { ...tree, locked }\n }\n return tree\n }\n return {\n ...tree,\n first: updatePaneLock(tree.first, paneId, locked) ?? tree.first,\n second: updatePaneLock(tree.second, paneId, locked) ?? tree.second,\n }\n}\n\n/**\n * Tree Helper: Activate a tab within a pane.\n */\nexport function selectTab(tree: TreeNode | null, paneId: string, tabId: string): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n if (tree.id === paneId) {\n return { ...tree, activeTabId: tabId }\n }\n return tree\n }\n return {\n ...tree,\n first: selectTab(tree.first, paneId, tabId) ?? tree.first,\n second: selectTab(tree.second, paneId, tabId) ?? tree.second,\n }\n}\n\n/**\n * Tree Helper: Merge a tab into a target pane node.\n */\nexport function mergeTab(\n tree: TreeNode | null,\n draggedTabId: string,\n targetPaneId: string,\n): TreeNode | null {\n if (tree === null) return null\n\n const sourcePane = findPaneContainingTab(tree, draggedTabId)\n const sourceMetadata = sourcePane?.tabsMetadata?.[draggedTabId]\n\n const cleanTree = removeTab(tree, draggedTabId)\n if (cleanTree === null) {\n return {\n type: 'pane',\n id: generateUniqueId(),\n tabs: [draggedTabId],\n activeTabId: draggedTabId,\n tabsMetadata: sourceMetadata ? { [draggedTabId]: sourceMetadata } : undefined,\n }\n }\n\n function insert(node: TreeNode): TreeNode {\n if (node.type === 'pane') {\n if (node.id === targetPaneId) {\n const newTabs = [...node.tabs]\n if (!newTabs.includes(draggedTabId)) {\n newTabs.push(draggedTabId)\n }\n const newTabsMetadata = { ...node.tabsMetadata }\n if (sourceMetadata) {\n newTabsMetadata[draggedTabId] = sourceMetadata\n }\n return {\n ...node,\n tabs: newTabs,\n activeTabId: draggedTabId,\n tabsMetadata: Object.keys(newTabsMetadata).length > 0 ? newTabsMetadata : undefined,\n }\n }\n return node\n }\n return {\n ...node,\n first: insert(node.first),\n second: insert(node.second),\n }\n }\n\n return insert(cleanTree)\n}\n\n/**\n * Tree Helper: Move/reorder a tab inside or to a target pane next to a target tab.\n */\nexport function moveTab(\n tree: TreeNode | null,\n draggedTabId: string,\n targetTabId: string,\n position: 'before' | 'after' = 'before',\n): TreeNode | null {\n if (tree === null) return null\n\n const sourcePane = findPaneContainingTab(tree, draggedTabId)\n const sourceMetadata = sourcePane?.tabsMetadata?.[draggedTabId]\n\n const cleanTree = removeTab(tree, draggedTabId)\n if (cleanTree === null) {\n return {\n type: 'pane',\n id: generateUniqueId(),\n tabs: [draggedTabId],\n activeTabId: draggedTabId,\n tabsMetadata: sourceMetadata ? { [draggedTabId]: sourceMetadata } : undefined,\n }\n }\n\n function insert(node: TreeNode): TreeNode {\n if (node.type === 'pane') {\n if (node.tabs.includes(targetTabId)) {\n const newTabs = [...node.tabs]\n const filteredTabs = newTabs.filter((t) => t !== draggedTabId)\n let insertIndex = filteredTabs.indexOf(targetTabId)\n if (insertIndex < 0) {\n insertIndex = 0\n }\n if (position === 'after') {\n insertIndex += 1\n }\n filteredTabs.splice(insertIndex, 0, draggedTabId)\n\n const newTabsMetadata = { ...node.tabsMetadata }\n if (sourceMetadata) {\n newTabsMetadata[draggedTabId] = sourceMetadata\n }\n return {\n ...node,\n tabs: filteredTabs,\n activeTabId: draggedTabId,\n tabsMetadata: Object.keys(newTabsMetadata).length > 0 ? newTabsMetadata : undefined,\n }\n }\n return node\n }\n return {\n ...node,\n first: insert(node.first),\n second: insert(node.second),\n }\n }\n\n return insert(cleanTree)\n}\n\nexport interface ComputedPane {\n paneId: string\n left: number\n top: number\n width: number\n height: number\n node: PaneNode\n}\n\nexport interface ComputedSplitter {\n id: string\n currentNode: SplitNode\n direction: SplitDirection\n left: number\n top: number\n width: number\n height: number\n parentLeft: number\n parentTop: number\n parentWidth: number\n parentHeight: number\n}\n\nexport function computeLayout(\n node: TreeNode | null,\n left = 0,\n top = 0,\n width = 100,\n height = 100,\n path = 'root',\n): { panes: ComputedPane[]; splitters: ComputedSplitter[] } {\n if (node === null) return { panes: [], splitters: [] }\n if (node.type === 'pane') {\n return {\n panes: [{ paneId: node.id, left, top, width, height, node }],\n splitters: [],\n }\n }\n\n const { direction, splitPercentage, first, second } = node\n const splitterId = `splitter-${path}-${direction}`\n\n const currentSplitter: ComputedSplitter = {\n id: splitterId,\n currentNode: node,\n direction,\n left: direction === 'row' ? left + width * (splitPercentage / 100) : left,\n top: direction === 'column' ? top + height * (splitPercentage / 100) : top,\n width: direction === 'row' ? 0 : width,\n height: direction === 'column' ? 0 : height,\n parentLeft: left,\n parentTop: top,\n parentWidth: width,\n parentHeight: height,\n }\n\n let firstLayout = { panes: [] as ComputedPane[], splitters: [] as ComputedSplitter[] }\n let secondLayout = { panes: [] as ComputedPane[], splitters: [] as ComputedSplitter[] }\n\n if (direction === 'row') {\n const firstWidth = width * (splitPercentage / 100)\n firstLayout = computeLayout(first, left, top, firstWidth, height, `${path}-L`)\n secondLayout = computeLayout(\n second,\n left + firstWidth,\n top,\n width - firstWidth,\n height,\n `${path}-R`,\n )\n } else {\n const firstHeight = height * (splitPercentage / 100)\n firstLayout = computeLayout(first, left, top, width, firstHeight, `${path}-T`)\n secondLayout = computeLayout(\n second,\n left,\n top + firstHeight,\n width,\n height - firstHeight,\n `${path}-B`,\n )\n }\n\n return {\n panes: [...firstLayout.panes, ...secondLayout.panes],\n splitters: [currentSplitter, ...firstLayout.splitters, ...secondLayout.splitters],\n }\n}\n"]}
|
package/dist/utils.d.cts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { g as PaneNode, f as SplitNode, S as SplitDirection, T as TreeNode, h as TabDetails } from './types-CaiXD2pG.cjs';
|
|
2
|
+
import 'react';
|
|
3
|
+
|
|
4
|
+
declare function generateUniqueId(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Tree Helper: Remove a pane container and consolidate the tree structure.
|
|
7
|
+
*/
|
|
8
|
+
declare function removePane(tree: TreeNode | null, paneId: string): TreeNode | null;
|
|
9
|
+
/**
|
|
10
|
+
* Tree Helper: Remove a tab from a pane and consolidate the tree structure if no tabs remain.
|
|
11
|
+
*/
|
|
12
|
+
declare function removeTab(tree: TreeNode | null, tabId: string): TreeNode | null;
|
|
13
|
+
/**
|
|
14
|
+
* Tree Helper: Insert a pane by splitting an existing target node.
|
|
15
|
+
*/
|
|
16
|
+
declare function splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string | PaneNode): TreeNode | null;
|
|
17
|
+
/**
|
|
18
|
+
* Tree Helper: Add a pane by recursively splitting the rightmost/bottommost pane in the tree.
|
|
19
|
+
*/
|
|
20
|
+
declare function addPane(tree: TreeNode | null, paneToAdd: string): TreeNode;
|
|
21
|
+
/**
|
|
22
|
+
* Tree Helper: Update split percentage recursively.
|
|
23
|
+
*/
|
|
24
|
+
declare function updateSplitPercentage(tree: TreeNode | null, target: SplitNode, newPercentage: number): TreeNode | null;
|
|
25
|
+
/**
|
|
26
|
+
* Find a PaneNode by its ID.
|
|
27
|
+
*/
|
|
28
|
+
declare function findPaneById(tree: TreeNode | null, paneId: string): PaneNode | null;
|
|
29
|
+
/**
|
|
30
|
+
* Find a PaneNode containing the given tab ID.
|
|
31
|
+
*/
|
|
32
|
+
declare function findPaneContainingTab(tree: TreeNode | null, tabId: string): PaneNode | null;
|
|
33
|
+
/**
|
|
34
|
+
* Find the details of a tab by its ID.
|
|
35
|
+
*/
|
|
36
|
+
declare function findTabById(tree: TreeNode | null, tabId: string): TabDetails | null;
|
|
37
|
+
/**
|
|
38
|
+
* Update metadata on a specific tab node using an updater function.
|
|
39
|
+
*/
|
|
40
|
+
declare function updateTabMetadata(tree: TreeNode | null, tabId: string, updater: (current: Record<string, unknown> | undefined) => Record<string, unknown> | undefined): TreeNode | null;
|
|
41
|
+
/**
|
|
42
|
+
* Update the locked status on a specific pane node in the layout tree.
|
|
43
|
+
*/
|
|
44
|
+
declare function updatePaneLock(tree: TreeNode | null, paneId: string, locked: boolean): TreeNode | null;
|
|
45
|
+
/**
|
|
46
|
+
* Tree Helper: Activate a tab within a pane.
|
|
47
|
+
*/
|
|
48
|
+
declare function selectTab(tree: TreeNode | null, paneId: string, tabId: string): TreeNode | null;
|
|
49
|
+
/**
|
|
50
|
+
* Tree Helper: Merge a tab into a target pane node.
|
|
51
|
+
*/
|
|
52
|
+
declare function mergeTab(tree: TreeNode | null, draggedTabId: string, targetPaneId: string): TreeNode | null;
|
|
53
|
+
/**
|
|
54
|
+
* Tree Helper: Move/reorder a tab inside or to a target pane next to a target tab.
|
|
55
|
+
*/
|
|
56
|
+
declare function moveTab(tree: TreeNode | null, draggedTabId: string, targetTabId: string, position?: 'before' | 'after'): TreeNode | null;
|
|
57
|
+
interface ComputedPane {
|
|
58
|
+
paneId: string;
|
|
59
|
+
left: number;
|
|
60
|
+
top: number;
|
|
61
|
+
width: number;
|
|
62
|
+
height: number;
|
|
63
|
+
node: PaneNode;
|
|
64
|
+
}
|
|
65
|
+
interface ComputedSplitter {
|
|
66
|
+
id: string;
|
|
67
|
+
currentNode: SplitNode;
|
|
68
|
+
direction: SplitDirection;
|
|
69
|
+
left: number;
|
|
70
|
+
top: number;
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
parentLeft: number;
|
|
74
|
+
parentTop: number;
|
|
75
|
+
parentWidth: number;
|
|
76
|
+
parentHeight: number;
|
|
77
|
+
}
|
|
78
|
+
declare function computeLayout(node: TreeNode | null, left?: number, top?: number, width?: number, height?: number, path?: string): {
|
|
79
|
+
panes: ComputedPane[];
|
|
80
|
+
splitters: ComputedSplitter[];
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export { type ComputedPane, type ComputedSplitter, addPane, computeLayout, findPaneById, findPaneContainingTab, findTabById, generateUniqueId, mergeTab, moveTab, removePane, removeTab, selectTab, splitPane, updatePaneLock, updateSplitPercentage, updateTabMetadata };
|