react-panel-layout 0.2.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 +121 -0
- package/dist/GridLayout-CmzKfbPP.js +1295 -0
- package/dist/GridLayout-CmzKfbPP.js.map +1 -0
- package/dist/GridLayout-Dx3Qofl0.cjs +2 -0
- package/dist/GridLayout-Dx3Qofl0.cjs.map +1 -0
- package/dist/PanelSystemContext.d.ts +25 -0
- package/dist/components/grid/GridLayerList.d.ts +10 -0
- package/dist/components/grid/GridLayerResizeHandles.d.ts +22 -0
- package/dist/components/grid/GridLayout.d.ts +11 -0
- package/dist/components/grid/GridTrackResizeHandle.d.ts +18 -0
- package/dist/components/paneling/FloatingPanelFrame.d.ts +32 -0
- package/dist/components/panels/DropSuggestOverlay.d.ts +13 -0
- package/dist/components/panels/PanelGroupView.d.ts +20 -0
- package/dist/components/resizer/HorizontalDivider.d.ts +14 -0
- package/dist/components/resizer/ResizeHandle.d.ts +24 -0
- package/dist/components/tabs/TabBar.d.ts +22 -0
- package/dist/components/tabs/TabBarTab.d.ts +20 -0
- package/dist/components/tabs/TabDragOverlay.d.ts +5 -0
- package/dist/components/window/DialogOverlay.d.ts +17 -0
- package/dist/components/window/Drawer.d.ts +22 -0
- package/dist/components/window/DrawerLayers.d.ts +9 -0
- package/dist/components/window/PopupLayerPortal.d.ts +10 -0
- package/dist/config/PanelContentDeclaration.d.ts +79 -0
- package/dist/config/index.d.ts +32 -0
- package/dist/config/panelRouter.d.ts +57 -0
- package/dist/config.cjs +2 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.js +210 -0
- package/dist/config.js.map +1 -0
- package/dist/constants/styles.d.ts +181 -0
- package/dist/floating/index.d.ts +8 -0
- package/dist/floating.cjs +2 -0
- package/dist/floating.cjs.map +1 -0
- package/dist/floating.js +67 -0
- package/dist/floating.js.map +1 -0
- package/dist/hooks/useCSSMatrix.d.ts +47 -0
- package/dist/hooks/useClonedElementPreview.d.ts +7 -0
- package/dist/hooks/useDocumentPointerEvents.d.ts +37 -0
- package/dist/hooks/useEffectEvent.d.ts +17 -0
- package/dist/hooks/useElementComponentWrapper.d.ts +18 -0
- package/dist/hooks/useIntersectionObserver.d.ts +20 -0
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts +23 -0
- package/dist/hooks/useResizeObserver.d.ts +15 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +1863 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/grid/GridLayoutContext.d.ts +34 -0
- package/dist/modules/grid/LayerInstanceContext.d.ts +26 -0
- package/dist/modules/grid/useGridPlacements.d.ts +7 -0
- package/dist/modules/grid/useGridTracks.d.ts +26 -0
- package/dist/modules/grid/useLayerDragHandle.d.ts +2 -0
- package/dist/modules/grid/useLayerInteractions.d.ts +12 -0
- package/dist/modules/keybindings/KeybindingsProvider.d.ts +17 -0
- package/dist/modules/panels/dom/DomRegistry.d.ts +19 -0
- package/dist/modules/panels/index.d.ts +11 -0
- package/dist/modules/panels/interactions/InteractionsContext.d.ts +50 -0
- package/dist/modules/panels/interactions/InteractionsContext.test.d.ts +1 -0
- package/dist/modules/panels/interactions/dnd.d.ts +5 -0
- package/dist/modules/panels/keybindings/KeybindingsInstaller.d.ts +5 -0
- package/dist/modules/panels/layout/adapter.d.ts +21 -0
- package/dist/modules/panels/rendering/GroupContainer.d.ts +11 -0
- package/dist/modules/panels/rendering/RenderBridge.d.ts +8 -0
- package/dist/modules/panels/rendering/RenderContext.d.ts +19 -0
- package/dist/modules/panels/state/PanelSplitHandles.d.ts +8 -0
- package/dist/modules/panels/state/PanelSystemContext.d.ts +148 -0
- package/dist/modules/panels/state/StateContext.d.ts +5 -0
- package/dist/modules/panels/state/cleanup.d.ts +11 -0
- package/dist/modules/panels/state/commands.d.ts +16 -0
- package/dist/modules/panels/state/focus/Context.d.ts +14 -0
- package/dist/modules/panels/state/focus/logic.d.ts +9 -0
- package/dist/modules/panels/state/groups/Context.d.ts +19 -0
- package/dist/modules/panels/state/groups/logic.d.ts +11 -0
- package/dist/modules/panels/state/splitLimits.d.ts +15 -0
- package/dist/modules/panels/state/tree/Context.d.ts +16 -0
- package/dist/modules/panels/state/tree/logic.d.ts +42 -0
- package/dist/modules/panels/state/types.d.ts +135 -0
- package/dist/modules/panels/system/PanelSystem.d.ts +6 -0
- package/dist/modules/resizer/useResizeDrag.d.ts +26 -0
- package/dist/modules/window/useDrawerState.d.ts +6 -0
- package/dist/styles-BMEhL6I0.cjs +2 -0
- package/dist/styles-BMEhL6I0.cjs.map +1 -0
- package/dist/styles-BnvLfp6e.js +49 -0
- package/dist/styles-BnvLfp6e.js.map +1 -0
- package/dist/types.d.ts +114 -0
- package/dist/utils/CSSMatrix.d.ts +42 -0
- package/dist/utils/dialogUtils.d.ts +18 -0
- package/dist/utils/math.d.ts +5 -0
- package/dist/utils/polyfills/createDialogPolyfill.d.ts +8 -0
- package/dist/utils/typedActions.d.ts +37 -0
- package/package.json +78 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DOM registry for panel-system. React-first: components register their own refs
|
|
3
|
+
* instead of global querySelectorAll scans.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import type { GroupId } from "../state/types";
|
|
7
|
+
export type RegisteredEls = {
|
|
8
|
+
group: HTMLElement | null;
|
|
9
|
+
tabbar: HTMLElement | null;
|
|
10
|
+
content: HTMLElement | null;
|
|
11
|
+
};
|
|
12
|
+
export type DomRegistryContextValue = {
|
|
13
|
+
setGroupEl: (groupId: GroupId, el: HTMLElement | null) => void;
|
|
14
|
+
setTabbarEl: (groupId: GroupId, el: HTMLElement | null) => void;
|
|
15
|
+
setContentEl: (groupId: GroupId, el: HTMLElement | null) => void;
|
|
16
|
+
getAll: () => Map<GroupId, RegisteredEls>;
|
|
17
|
+
};
|
|
18
|
+
export declare const useDomRegistry: () => DomRegistryContextValue;
|
|
19
|
+
export declare const DomRegistryProvider: React.FC<React.PropsWithChildren<unknown>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Public state operations aggregator (flattened under src/modules/panels).
|
|
3
|
+
*/
|
|
4
|
+
export type { PanelId, GroupId, SplitDirection, TabDefinition, GroupModel, PanelTree, PanelSystemState, DropZone, DraggingTab, PanelCommands, PanelSystemProps, PanelSplitLimits, } from "./state/types";
|
|
5
|
+
export { collectGroupsInOrder, splitLeaf, closeLeaf, isGroup, setSplitRatio } from "./state/tree/logic";
|
|
6
|
+
export { createEmptyGroup, addTabToGroup, removeTabFromGroup, moveTab, setActiveTab, reorderTabWithinGroup, addTabToGroupAtIndex } from "./state/groups/logic";
|
|
7
|
+
export { setFocusedGroup, focusGroupIndex, nextGroup, prevGroup, refreshGroupOrder } from "./state/focus/logic";
|
|
8
|
+
import type { GroupId as Gid, PanelSystemState as PState, SplitDirection as Dir, TabDefinition } from "./state/types";
|
|
9
|
+
export type IdFactory = () => Gid;
|
|
10
|
+
export declare const splitGroup: (state: PState, groupId: Gid, direction: Dir, createGroupId: IdFactory) => PState;
|
|
11
|
+
export declare const buildInitialState: (tabs: TabDefinition[]) => PState;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Centralized panel interactions (DnD) using Context + reducer.
|
|
3
|
+
* Handles content (panel area) split/move and tabbar reordering/cross-move.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import type { DropZone, GroupId, PanelId } from "../state/types";
|
|
7
|
+
export type SuggestInfo = {
|
|
8
|
+
rect: DOMRectReadOnly;
|
|
9
|
+
zone: DropZone;
|
|
10
|
+
} | null;
|
|
11
|
+
export type InteractionsContextValue = {
|
|
12
|
+
suggest: SuggestInfo;
|
|
13
|
+
onStartContentDrag: (groupId: GroupId, tabId: PanelId, e: React.PointerEvent<HTMLDivElement>) => void;
|
|
14
|
+
onStartTabDrag: (tabId: PanelId, groupId: GroupId, e: React.PointerEvent) => void;
|
|
15
|
+
isTabDragging: boolean;
|
|
16
|
+
draggingTabId: PanelId | null;
|
|
17
|
+
dragPointer: {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
} | null;
|
|
21
|
+
tabbarHover: {
|
|
22
|
+
groupId: GroupId;
|
|
23
|
+
index: number;
|
|
24
|
+
rect: DOMRectReadOnly;
|
|
25
|
+
insertX: number;
|
|
26
|
+
} | null;
|
|
27
|
+
draggingTabElement: HTMLElement | null;
|
|
28
|
+
};
|
|
29
|
+
export declare const usePanelInteractions: () => InteractionsContextValue;
|
|
30
|
+
export type InteractionsProviderProps = React.PropsWithChildren<{
|
|
31
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
32
|
+
dragThresholdPx: number;
|
|
33
|
+
onCommitContentDrop: (payload: {
|
|
34
|
+
fromGroupId: GroupId;
|
|
35
|
+
tabId: PanelId;
|
|
36
|
+
targetGroupId: GroupId;
|
|
37
|
+
zone: DropZone;
|
|
38
|
+
}) => void;
|
|
39
|
+
onCommitTabDrop: (payload: {
|
|
40
|
+
fromGroupId: GroupId;
|
|
41
|
+
tabId: PanelId;
|
|
42
|
+
targetGroupId: GroupId;
|
|
43
|
+
targetIndex: number;
|
|
44
|
+
}) => void;
|
|
45
|
+
isContentZoneAllowed?: (payload: {
|
|
46
|
+
targetGroupId: GroupId;
|
|
47
|
+
zone: DropZone;
|
|
48
|
+
}) => boolean;
|
|
49
|
+
}>;
|
|
50
|
+
export declare const InteractionsProvider: React.FC<InteractionsProviderProps>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Adapters bridging panel-system model to existing GridLayout definitions.
|
|
3
|
+
* Strategy: Use a 1x1 relative grid and place groups as absolute layers using computed rects.
|
|
4
|
+
*/
|
|
5
|
+
import type { LayerDefinition, PanelLayoutConfig } from "../../../types";
|
|
6
|
+
import type { GroupId, PanelSystemState, PanelTree } from "../state/types";
|
|
7
|
+
export type Rect = {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
w: number;
|
|
11
|
+
h: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const computeRects: (node: PanelTree, bounds?: Rect) => Map<GroupId, Rect>;
|
|
14
|
+
export declare const buildGridForAbsolutePanels: (state: PanelSystemState, renderGroup: (groupId: GroupId) => React.ReactNode) => {
|
|
15
|
+
config: PanelLayoutConfig;
|
|
16
|
+
layers: LayerDefinition[];
|
|
17
|
+
};
|
|
18
|
+
export declare const buildGridFromRects: (state: PanelSystemState, renderGroup: (groupId: GroupId) => React.ReactNode, interactiveTracks: boolean) => {
|
|
19
|
+
config: PanelLayoutConfig;
|
|
20
|
+
layers: LayerDefinition[];
|
|
21
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Connected group container bridging panel contexts to the presentational view.
|
|
3
|
+
*/
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import type { GroupId, PanelGroupRenderProps, TabBarRenderProps } from "../../panels/state/types";
|
|
6
|
+
export type ConnectedGroupContainerProps = {
|
|
7
|
+
id: GroupId;
|
|
8
|
+
TabBarComponent?: React.ComponentType<TabBarRenderProps>;
|
|
9
|
+
PanelGroupComponent?: React.ComponentType<PanelGroupRenderProps>;
|
|
10
|
+
};
|
|
11
|
+
export declare const GroupContainer: React.FC<ConnectedGroupContainerProps>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Bridge component that binds InteractionsContext to PanelRenderContext using panel state.
|
|
3
|
+
*/
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
export declare const RenderBridge: React.FC<React.PropsWithChildren<{
|
|
6
|
+
emptyContentComponent?: React.ComponentType;
|
|
7
|
+
doubleClickToAdd?: boolean;
|
|
8
|
+
}>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Panel render context providing per-group accessors and actions.
|
|
3
|
+
*/
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import type { GroupId, GroupModel, PanelId } from "../state/types";
|
|
6
|
+
export type PanelRenderContextValue = {
|
|
7
|
+
getGroup: (id: GroupId) => GroupModel | null;
|
|
8
|
+
getGroupContent: (id: GroupId) => React.ReactNode;
|
|
9
|
+
onClickTab: (groupId: GroupId, tabId: PanelId) => void;
|
|
10
|
+
onAddTab?: (groupId: GroupId) => void;
|
|
11
|
+
onCloseTab?: (groupId: GroupId, tabId: PanelId) => void;
|
|
12
|
+
onStartTabDrag: (tabId: PanelId, groupId: GroupId, e: React.PointerEvent) => void;
|
|
13
|
+
onStartContentDrag: (groupId: GroupId, e: React.PointerEvent<HTMLDivElement>) => void;
|
|
14
|
+
doubleClickToAdd?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare const usePanelRenderContext: () => PanelRenderContextValue;
|
|
17
|
+
export declare const PanelRenderProvider: React.FC<React.PropsWithChildren<{
|
|
18
|
+
value: PanelRenderContextValue;
|
|
19
|
+
}>>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Root PanelSystemContext that combines subdomain contexts (groups, tree, focus).
|
|
3
|
+
* Provides unified state and dispatch while exposing domain-specific actions via subcontexts.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import type { DropZone, GroupId, PanelId, PanelSplitLimits, PanelSystemState, SplitDirection, TabDefinition } from "./types";
|
|
7
|
+
import { type NodePath } from "./tree/logic";
|
|
8
|
+
import { type BoundActionCreators } from "../../../utils/typedActions";
|
|
9
|
+
declare const actions: {
|
|
10
|
+
readonly splitFocused: ((direction: SplitDirection) => {
|
|
11
|
+
type: "panelState/splitFocused";
|
|
12
|
+
payload: {
|
|
13
|
+
direction: SplitDirection;
|
|
14
|
+
};
|
|
15
|
+
}) & {
|
|
16
|
+
readonly type: "panelState/splitFocused";
|
|
17
|
+
};
|
|
18
|
+
readonly focusGroupIndex: ((index1Based: number) => {
|
|
19
|
+
type: "panelState/focusGroupIndex";
|
|
20
|
+
payload: {
|
|
21
|
+
index1Based: number;
|
|
22
|
+
};
|
|
23
|
+
}) & {
|
|
24
|
+
readonly type: "panelState/focusGroupIndex";
|
|
25
|
+
};
|
|
26
|
+
readonly focusNextGroup: (() => {
|
|
27
|
+
type: "panelState/focusNextGroup";
|
|
28
|
+
}) & {
|
|
29
|
+
readonly type: "panelState/focusNextGroup";
|
|
30
|
+
};
|
|
31
|
+
readonly focusPrevGroup: (() => {
|
|
32
|
+
type: "panelState/focusPrevGroup";
|
|
33
|
+
}) & {
|
|
34
|
+
readonly type: "panelState/focusPrevGroup";
|
|
35
|
+
};
|
|
36
|
+
readonly setActiveTab: ((groupId: string, tabId: string) => {
|
|
37
|
+
type: "panelState/setActiveTab";
|
|
38
|
+
payload: {
|
|
39
|
+
groupId: string;
|
|
40
|
+
tabId: string;
|
|
41
|
+
};
|
|
42
|
+
}) & {
|
|
43
|
+
readonly type: "panelState/setActiveTab";
|
|
44
|
+
};
|
|
45
|
+
readonly addTab: ((payload: {
|
|
46
|
+
groupId: GroupId;
|
|
47
|
+
tab: TabDefinition;
|
|
48
|
+
index?: number;
|
|
49
|
+
makeActive?: boolean;
|
|
50
|
+
}) => {
|
|
51
|
+
type: "panelState/addTab";
|
|
52
|
+
payload: {
|
|
53
|
+
groupId: GroupId;
|
|
54
|
+
tab: TabDefinition;
|
|
55
|
+
index?: number;
|
|
56
|
+
makeActive?: boolean;
|
|
57
|
+
};
|
|
58
|
+
}) & {
|
|
59
|
+
readonly type: "panelState/addTab";
|
|
60
|
+
};
|
|
61
|
+
readonly addNewTab: ((payload: {
|
|
62
|
+
groupId: GroupId;
|
|
63
|
+
title: string;
|
|
64
|
+
index?: number;
|
|
65
|
+
makeActive?: boolean;
|
|
66
|
+
}) => {
|
|
67
|
+
type: "panelState/addNewTab";
|
|
68
|
+
payload: {
|
|
69
|
+
groupId: GroupId;
|
|
70
|
+
title: string;
|
|
71
|
+
index?: number;
|
|
72
|
+
makeActive?: boolean;
|
|
73
|
+
};
|
|
74
|
+
}) & {
|
|
75
|
+
readonly type: "panelState/addNewTab";
|
|
76
|
+
};
|
|
77
|
+
readonly removeTab: ((groupId: string, tabId: string) => {
|
|
78
|
+
type: "panelState/removeTab";
|
|
79
|
+
payload: {
|
|
80
|
+
groupId: string;
|
|
81
|
+
tabId: string;
|
|
82
|
+
};
|
|
83
|
+
}) & {
|
|
84
|
+
readonly type: "panelState/removeTab";
|
|
85
|
+
};
|
|
86
|
+
readonly contentDrop: ((payload: {
|
|
87
|
+
fromGroupId: GroupId;
|
|
88
|
+
tabId: PanelId;
|
|
89
|
+
targetGroupId: GroupId;
|
|
90
|
+
zone: DropZone;
|
|
91
|
+
}) => {
|
|
92
|
+
type: "panelState/contentDrop";
|
|
93
|
+
payload: {
|
|
94
|
+
fromGroupId: GroupId;
|
|
95
|
+
tabId: PanelId;
|
|
96
|
+
targetGroupId: GroupId;
|
|
97
|
+
zone: DropZone;
|
|
98
|
+
};
|
|
99
|
+
}) & {
|
|
100
|
+
readonly type: "panelState/contentDrop";
|
|
101
|
+
};
|
|
102
|
+
readonly tabDrop: ((payload: {
|
|
103
|
+
fromGroupId: GroupId;
|
|
104
|
+
tabId: PanelId;
|
|
105
|
+
targetGroupId: GroupId;
|
|
106
|
+
targetIndex: number;
|
|
107
|
+
}) => {
|
|
108
|
+
type: "panelState/tabDrop";
|
|
109
|
+
payload: {
|
|
110
|
+
fromGroupId: GroupId;
|
|
111
|
+
tabId: PanelId;
|
|
112
|
+
targetGroupId: GroupId;
|
|
113
|
+
targetIndex: number;
|
|
114
|
+
};
|
|
115
|
+
}) & {
|
|
116
|
+
readonly type: "panelState/tabDrop";
|
|
117
|
+
};
|
|
118
|
+
readonly adjustSplitRatio: ((payload: {
|
|
119
|
+
path: NodePath;
|
|
120
|
+
deltaRatio: number;
|
|
121
|
+
}) => {
|
|
122
|
+
type: "panelState/adjustSplitRatio";
|
|
123
|
+
payload: {
|
|
124
|
+
path: NodePath;
|
|
125
|
+
deltaRatio: number;
|
|
126
|
+
};
|
|
127
|
+
}) & {
|
|
128
|
+
readonly type: "panelState/adjustSplitRatio";
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
type PanelStateAction = ReturnType<(typeof actions)[keyof typeof actions]>;
|
|
132
|
+
type PanelStateActions = BoundActionCreators<typeof actions>;
|
|
133
|
+
export type PanelSystemContextValue = {
|
|
134
|
+
state: PanelSystemState;
|
|
135
|
+
dispatch: (action: PanelStateAction) => void;
|
|
136
|
+
actions: PanelStateActions;
|
|
137
|
+
};
|
|
138
|
+
export declare const usePanelSystem: () => PanelSystemContextValue;
|
|
139
|
+
export type PanelSystemProviderProps = React.PropsWithChildren<{
|
|
140
|
+
initialState: PanelSystemState;
|
|
141
|
+
createGroupId: () => GroupId;
|
|
142
|
+
createPanelId?: () => PanelId;
|
|
143
|
+
state?: PanelSystemState;
|
|
144
|
+
onStateChange?: (next: PanelSystemState) => void;
|
|
145
|
+
splitLimits?: PanelSplitLimits;
|
|
146
|
+
}>;
|
|
147
|
+
export declare const PanelSystemProvider: React.FC<PanelSystemProviderProps>;
|
|
148
|
+
export type { PanelStateAction, PanelStateActions };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file State context for VSCode-like panel system (backward compatibility layer).
|
|
3
|
+
* This file re-exports PanelSystemContext for compatibility with existing code.
|
|
4
|
+
*/
|
|
5
|
+
export { PanelSystemProvider as PanelStateProvider, usePanelSystem as usePanelState, type PanelSystemProviderProps as PanelStateProviderProps, type PanelSystemContextValue as PanelStateContextValue, type PanelStateAction, type PanelStateActions, } from "./PanelSystemContext";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Cleanup utilities for PanelSystem state.
|
|
3
|
+
* - Removes groups with no tabs and collapses the tree accordingly.
|
|
4
|
+
*/
|
|
5
|
+
import type { PanelSystemState } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Remove empty groups (tabs.length === 0) by collapsing corresponding leaves.
|
|
8
|
+
* Keeps at least one group in the system.
|
|
9
|
+
* Returns the same reference if no changes are necessary to help callers short-circuit updates.
|
|
10
|
+
*/
|
|
11
|
+
export declare const cleanupEmptyGroups: (state: PanelSystemState) => PanelSystemState;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DropZone, PanelCommands } from "../state/types";
|
|
2
|
+
export declare const usePanelCommands: () => PanelCommands;
|
|
3
|
+
export declare const useCommitHandlers: () => {
|
|
4
|
+
onCommitContentDrop: (payload: {
|
|
5
|
+
fromGroupId: string;
|
|
6
|
+
tabId: string;
|
|
7
|
+
targetGroupId: string;
|
|
8
|
+
zone: DropZone;
|
|
9
|
+
}) => void;
|
|
10
|
+
onCommitTabDrop: (payload: {
|
|
11
|
+
fromGroupId: string;
|
|
12
|
+
tabId: string;
|
|
13
|
+
targetGroupId: string;
|
|
14
|
+
targetIndex: number;
|
|
15
|
+
}) => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file FocusContext provides focus/navigation actions for PanelSystem.
|
|
3
|
+
* This context delegates to the root PanelSystemContext dispatch.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
export type FocusActions = {
|
|
7
|
+
focusGroupIndex: (index1Based: number) => void;
|
|
8
|
+
focusNextGroup: () => void;
|
|
9
|
+
focusPrevGroup: () => void;
|
|
10
|
+
};
|
|
11
|
+
export declare const useFocus: () => FocusActions;
|
|
12
|
+
export declare const FocusProvider: React.FC<React.PropsWithChildren<{
|
|
13
|
+
value: FocusActions;
|
|
14
|
+
}>>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Focus and navigation helpers for panel-system.
|
|
3
|
+
*/
|
|
4
|
+
import type { GroupId, PanelSystemState } from "../types";
|
|
5
|
+
export declare const setFocusedGroup: (state: PanelSystemState, groupId: GroupId) => PanelSystemState;
|
|
6
|
+
export declare const focusGroupIndex: (state: PanelSystemState, index1Based: number) => PanelSystemState;
|
|
7
|
+
export declare const nextGroup: (state: PanelSystemState) => PanelSystemState;
|
|
8
|
+
export declare const prevGroup: (state: PanelSystemState) => PanelSystemState;
|
|
9
|
+
export declare const refreshGroupOrder: (state: PanelSystemState) => PanelSystemState;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file GroupsContext provides group/tab management actions for PanelSystem.
|
|
3
|
+
* This context delegates to the root PanelSystemContext dispatch.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import type { GroupId, PanelId } from "../types";
|
|
7
|
+
export type GroupsActions = {
|
|
8
|
+
setActiveTab: (groupId: GroupId, tabId: PanelId) => void;
|
|
9
|
+
tabDrop: (payload: {
|
|
10
|
+
fromGroupId: GroupId;
|
|
11
|
+
tabId: PanelId;
|
|
12
|
+
targetGroupId: GroupId;
|
|
13
|
+
targetIndex: number;
|
|
14
|
+
}) => void;
|
|
15
|
+
};
|
|
16
|
+
export declare const useGroups: () => GroupsActions;
|
|
17
|
+
export declare const GroupsProvider: React.FC<React.PropsWithChildren<{
|
|
18
|
+
value: GroupsActions;
|
|
19
|
+
}>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Group and tab registry operations (pure, no tree logic).
|
|
3
|
+
*/
|
|
4
|
+
import type { GroupId, GroupModel, PanelId, PanelSystemState, TabDefinition } from "../types";
|
|
5
|
+
export declare const createEmptyGroup: (id: GroupId) => GroupModel;
|
|
6
|
+
export declare const addTabToGroup: (state: PanelSystemState, groupId: GroupId, tab: TabDefinition, makeActive: boolean) => PanelSystemState;
|
|
7
|
+
export declare const removeTabFromGroup: (state: PanelSystemState, groupId: GroupId, tabId: PanelId) => PanelSystemState;
|
|
8
|
+
export declare const moveTab: (state: PanelSystemState, fromGroupId: GroupId, toGroupId: GroupId, tabId: PanelId, makeActive: boolean) => PanelSystemState;
|
|
9
|
+
export declare const setActiveTab: (state: PanelSystemState, groupId: GroupId, tabId: PanelId) => PanelSystemState;
|
|
10
|
+
export declare const reorderTabWithinGroup: (state: PanelSystemState, groupId: GroupId, tabId: PanelId, toIndex: number) => PanelSystemState;
|
|
11
|
+
export declare const addTabToGroupAtIndex: (state: PanelSystemState, groupId: GroupId, tab: TabDefinition, index: number, makeActive: boolean) => PanelSystemState;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Split-limit helpers for the panel system.
|
|
3
|
+
*/
|
|
4
|
+
import type { GroupId, PanelSplitLimits, PanelTree, SplitDirection } from "./types";
|
|
5
|
+
export type NormalizedSplitLimits = {
|
|
6
|
+
rows: number;
|
|
7
|
+
cols: number;
|
|
8
|
+
};
|
|
9
|
+
export declare const normalizeSplitLimits: (limits?: PanelSplitLimits) => NormalizedSplitLimits;
|
|
10
|
+
export type SplitExtents = {
|
|
11
|
+
horizontal: number;
|
|
12
|
+
vertical: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const measureSplitExtents: (tree: PanelTree) => SplitExtents;
|
|
15
|
+
export declare const canSplitDirection: (tree: PanelTree, groupId: GroupId, direction: SplitDirection, limits: NormalizedSplitLimits) => boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file TreeContext provides tree/split management actions for PanelSystem.
|
|
3
|
+
* This context delegates to the root PanelSystemContext dispatch.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import type { NodePath } from "./logic";
|
|
7
|
+
export type TreeActions = {
|
|
8
|
+
adjustSplitRatio: (payload: {
|
|
9
|
+
path: NodePath;
|
|
10
|
+
deltaRatio: number;
|
|
11
|
+
}) => void;
|
|
12
|
+
};
|
|
13
|
+
export declare const useTree: () => TreeActions;
|
|
14
|
+
export declare const TreeProvider: React.FC<React.PropsWithChildren<{
|
|
15
|
+
value: TreeActions;
|
|
16
|
+
}>>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tree operations for panel-system (no registry or focus concerns).
|
|
3
|
+
*/
|
|
4
|
+
import type { GroupId, PanelTree, SplitDirection } from "../types";
|
|
5
|
+
export type PathSegment = "a" | "b";
|
|
6
|
+
export type NodePath = PathSegment[];
|
|
7
|
+
export declare const isGroup: (node: PanelTree) => node is Extract<PanelTree, {
|
|
8
|
+
type: "group";
|
|
9
|
+
}>;
|
|
10
|
+
export declare const collectGroupsInOrder: (node: PanelTree, acc?: GroupId[]) => GroupId[];
|
|
11
|
+
export declare const getAtPath: (root: PanelTree, path: NodePath) => PanelTree;
|
|
12
|
+
export declare const setAtPath: (root: PanelTree, path: NodePath, value: PanelTree) => PanelTree;
|
|
13
|
+
/**
|
|
14
|
+
* Find split parent of a given group leaf. This function explicitly returns the path to the split node
|
|
15
|
+
* and which side ("a" | "b") the target group occupies under that split. If there is no split parent
|
|
16
|
+
* (i.e., the root is the leaf group), splitPath and side are null.
|
|
17
|
+
*/
|
|
18
|
+
export declare const findLeafParent: (node: PanelTree, groupId: GroupId, path?: NodePath) => {
|
|
19
|
+
splitPath: NodePath | null;
|
|
20
|
+
side: PathSegment | null;
|
|
21
|
+
} | null;
|
|
22
|
+
/**
|
|
23
|
+
* Split the leaf group referenced by groupId into a split node, keeping the original group on side 'a'
|
|
24
|
+
* and inserting a new group on side 'b'. Returns the new tree and the new group's id.
|
|
25
|
+
* This function does not touch registries; only pure tree transformation.
|
|
26
|
+
*/
|
|
27
|
+
export declare const splitLeaf: (root: PanelTree, groupId: GroupId, direction: SplitDirection, createGroupId: () => GroupId) => {
|
|
28
|
+
tree: PanelTree;
|
|
29
|
+
newGroupId: GroupId;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Remove the leaf group by collapsing it with its sibling. Returns the new tree and the survivor group id
|
|
33
|
+
* (if any). If there is no split parent (single group), returns the original tree.
|
|
34
|
+
*/
|
|
35
|
+
export declare const closeLeaf: (root: PanelTree, groupId: GroupId) => {
|
|
36
|
+
tree: PanelTree;
|
|
37
|
+
survivorGroupId: GroupId | null;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Set split ratio at a specific split node path. Clamps ratio into (0.05 .. 0.95) to avoid zero-size panes.
|
|
41
|
+
*/
|
|
42
|
+
export declare const setSplitRatio: (root: PanelTree, splitPath: NodePath, ratio: number) => PanelTree;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Types for VSCode-like panel system (tabs, groups, splits).
|
|
3
|
+
*/
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
export type PanelId = string;
|
|
6
|
+
export type GroupId = string;
|
|
7
|
+
export type SplitDirection = "vertical" | "horizontal";
|
|
8
|
+
type RowsColsSplitLimits = {
|
|
9
|
+
/**
|
|
10
|
+
* Maximum number of rows (horizontal panes stacked vertically). Mirrors the HTML `rows` attribute.
|
|
11
|
+
* Value of 1 disables horizontal splits.
|
|
12
|
+
*/
|
|
13
|
+
rows?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Maximum number of columns (vertical panes stacked horizontally). Mirrors the HTML `cols` attribute.
|
|
16
|
+
* Value of 1 disables vertical splits.
|
|
17
|
+
*/
|
|
18
|
+
cols?: number;
|
|
19
|
+
};
|
|
20
|
+
type LegacySplitLimits = {
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use `rows` instead.
|
|
23
|
+
*/
|
|
24
|
+
maxHorizontal?: number;
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated Use `cols` instead.
|
|
27
|
+
*/
|
|
28
|
+
maxVertical?: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Controls how many times panes may be split. Accepts a single number (applied to both rows/cols)
|
|
32
|
+
* or an object that mirrors HTML's `rows`/`cols` attributes for clarity.
|
|
33
|
+
*/
|
|
34
|
+
export type PanelSplitLimits = number | RowsColsSplitLimits | LegacySplitLimits;
|
|
35
|
+
export type TabDefinition = {
|
|
36
|
+
id: PanelId;
|
|
37
|
+
title: string;
|
|
38
|
+
/** Render function to avoid remount on move */
|
|
39
|
+
render: () => React.ReactNode;
|
|
40
|
+
};
|
|
41
|
+
export type GroupModel = {
|
|
42
|
+
id: GroupId;
|
|
43
|
+
/** Ordered panel ids belonging to this group */
|
|
44
|
+
tabIds: PanelId[];
|
|
45
|
+
/** Deprecated: view adapter may synthesize from registry; kept for backward-compat */
|
|
46
|
+
tabs: TabDefinition[];
|
|
47
|
+
activeTabId: PanelId | null;
|
|
48
|
+
};
|
|
49
|
+
export type SplitNode = {
|
|
50
|
+
type: "group";
|
|
51
|
+
groupId: GroupId;
|
|
52
|
+
} | {
|
|
53
|
+
type: "split";
|
|
54
|
+
direction: SplitDirection;
|
|
55
|
+
ratio: number;
|
|
56
|
+
a: SplitNode;
|
|
57
|
+
b: SplitNode;
|
|
58
|
+
};
|
|
59
|
+
export type PanelTree = SplitNode;
|
|
60
|
+
export type PanelSystemState = {
|
|
61
|
+
tree: PanelTree;
|
|
62
|
+
/** Single source registry of panels (id -> definition) */
|
|
63
|
+
panels: Record<PanelId, TabDefinition>;
|
|
64
|
+
groups: Record<GroupId, GroupModel>;
|
|
65
|
+
/** Keeps stable order for focusing groups with Cmd+1..9 */
|
|
66
|
+
groupOrder: GroupId[];
|
|
67
|
+
/** Currently focused group */
|
|
68
|
+
focusedGroupId: GroupId | null;
|
|
69
|
+
};
|
|
70
|
+
export type DropZone = "center" | "left" | "right" | "top" | "bottom";
|
|
71
|
+
export type DraggingTab = {
|
|
72
|
+
tabId: PanelId;
|
|
73
|
+
fromGroupId: GroupId;
|
|
74
|
+
};
|
|
75
|
+
export type PanelCommands = {
|
|
76
|
+
splitFocused: (direction: SplitDirection) => void;
|
|
77
|
+
focusGroupIndex: (index1Based: number) => void;
|
|
78
|
+
focusNextGroup: () => void;
|
|
79
|
+
focusPrevGroup: () => void;
|
|
80
|
+
closeFocusedGroup: () => void;
|
|
81
|
+
};
|
|
82
|
+
export type TabBarRenderProps = {
|
|
83
|
+
group: GroupModel;
|
|
84
|
+
onClickTab: (tabId: string) => void;
|
|
85
|
+
onStartDrag?: (tabId: string, groupId: string, e: React.PointerEvent) => void;
|
|
86
|
+
rootRef?: React.Ref<HTMLDivElement>;
|
|
87
|
+
/** Optional: request to add a tab to this group (UI triggers only; state lives in context) */
|
|
88
|
+
onAddTab?: (groupId: GroupId) => void;
|
|
89
|
+
/** Optional: request to close a tab (UI triggers only; state lives in context) */
|
|
90
|
+
onCloseTab?: (groupId: GroupId, tabId: PanelId) => void;
|
|
91
|
+
/** Enable double-click on empty tabbar area to add a new tab (default: false) */
|
|
92
|
+
doubleClickToAdd?: boolean;
|
|
93
|
+
};
|
|
94
|
+
export type PanelGroupRenderProps = {
|
|
95
|
+
group: GroupModel;
|
|
96
|
+
tabbar: React.ReactNode;
|
|
97
|
+
content: React.ReactNode;
|
|
98
|
+
onContentPointerDown?: (e: React.PointerEvent<HTMLDivElement>) => void;
|
|
99
|
+
groupRef?: React.Ref<HTMLDivElement>;
|
|
100
|
+
contentRef?: React.Ref<HTMLDivElement>;
|
|
101
|
+
};
|
|
102
|
+
export type PanelSystemProps = {
|
|
103
|
+
/** Initial tree and groups. Component is uncontrolled by default. */
|
|
104
|
+
initialState: PanelSystemState;
|
|
105
|
+
/** Explicit group id factory. Required by no-magic policy. */
|
|
106
|
+
createGroupId: () => GroupId;
|
|
107
|
+
/** Explicit panel id factory for newly-created tabs. Required by no-magic policy when adding tabs via UI. */
|
|
108
|
+
createPanelId?: () => PanelId;
|
|
109
|
+
/** Adapter layout mode: explicit selection required by no-magic policy. */
|
|
110
|
+
layoutMode: "absolute" | "grid";
|
|
111
|
+
/** When layoutMode==='grid', whether grid track resize is interactive (single source if false). */
|
|
112
|
+
gridTracksInteractive?: boolean;
|
|
113
|
+
/** Drag activation threshold in px (explicit, no-magic). */
|
|
114
|
+
dragThresholdPx: number;
|
|
115
|
+
/** View component for a group (no operation injection at PanelSystem layer). */
|
|
116
|
+
view?: React.ComponentType<{
|
|
117
|
+
groupId: GroupId;
|
|
118
|
+
}>;
|
|
119
|
+
/** Optional component for empty content in a group (when no active tab). */
|
|
120
|
+
emptyContentComponent?: React.ComponentType;
|
|
121
|
+
/** Optional controlled state */
|
|
122
|
+
state?: PanelSystemState;
|
|
123
|
+
onStateChange?: (next: PanelSystemState) => void;
|
|
124
|
+
/** ClassName/style passthrough */
|
|
125
|
+
className?: string;
|
|
126
|
+
style?: React.CSSProperties;
|
|
127
|
+
/** Pluggable UI components */
|
|
128
|
+
tabBarComponent?: React.ComponentType<TabBarRenderProps>;
|
|
129
|
+
panelGroupComponent?: React.ComponentType<PanelGroupRenderProps>;
|
|
130
|
+
/** Optional limits controlling split directions. */
|
|
131
|
+
splitLimits?: PanelSplitLimits;
|
|
132
|
+
/** Enable double-click on empty tabbar area to add a new tab (default: false) */
|
|
133
|
+
doubleClickToAdd?: boolean;
|
|
134
|
+
};
|
|
135
|
+
export {};
|