react-headless-dock-layout 0.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/.vscode/settings.json +7 -0
- package/biome.json +34 -0
- package/index.html +12 -0
- package/package.json +43 -0
- package/src/App.tsx +119 -0
- package/src/global.css +5 -0
- package/src/index.ts +3 -0
- package/src/index.tsx +8 -0
- package/src/internal/EventEmitter.ts +17 -0
- package/src/internal/LayoutManager/LayoutManager.test.ts +948 -0
- package/src/internal/LayoutManager/LayoutManager.ts +515 -0
- package/src/internal/LayoutManager/LayoutTree.test.ts +341 -0
- package/src/internal/LayoutManager/LayoutTree.ts +82 -0
- package/src/internal/LayoutManager/calculateLayoutRects.test.ts +211 -0
- package/src/internal/LayoutManager/calculateLayoutRects.ts +88 -0
- package/src/internal/LayoutManager/calculateMinSize.test.ts +77 -0
- package/src/internal/LayoutManager/calculateMinSize.ts +40 -0
- package/src/internal/LayoutManager/findClosestDirection.test.ts +95 -0
- package/src/internal/LayoutManager/findClosestDirection.ts +15 -0
- package/src/internal/LayoutManager/types.ts +20 -0
- package/src/internal/assertNever.ts +3 -0
- package/src/internal/clamp.tsx +3 -0
- package/src/internal/findParentNode.ts +30 -0
- package/src/internal/invariant.tsx +6 -0
- package/src/strategies.ts +76 -0
- package/src/types.ts +31 -0
- package/src/useDockLayout.ts +249 -0
- package/tsconfig.json +7 -0
- package/tsup.config.ts +9 -0
package/biome.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"includes": ["**", "!!**/dist"]
|
|
10
|
+
},
|
|
11
|
+
"formatter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"indentStyle": "space"
|
|
14
|
+
},
|
|
15
|
+
"linter": {
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"rules": {
|
|
18
|
+
"recommended": true
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"javascript": {
|
|
22
|
+
"formatter": {
|
|
23
|
+
"quoteStyle": "double"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"assist": {
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"actions": {
|
|
29
|
+
"source": {
|
|
30
|
+
"organizeImports": "on"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>React Headless Dock Layout</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="dist/bundle.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-headless-dock-layout",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight, headless dock layout library for React.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"require": {
|
|
13
|
+
"types": "./dist/index.d.cts",
|
|
14
|
+
"default": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"react",
|
|
20
|
+
"headless",
|
|
21
|
+
"dock layout"
|
|
22
|
+
],
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@biomejs/biome": "2.3.10",
|
|
26
|
+
"@total-typescript/tsconfig": "^1.0.4",
|
|
27
|
+
"@types/react": "^19.2.2",
|
|
28
|
+
"@types/react-dom": "^19.2.2",
|
|
29
|
+
"react": "^19.2.3",
|
|
30
|
+
"react-dom": "^19.2.3",
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"typescript": "^5.9.3",
|
|
33
|
+
"vitest": "^4.0.14"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": ">=17.0.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "vitest",
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"type-check": "tsc --noEmit"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { LayoutNode } from ".";
|
|
2
|
+
import { assertNever } from "./internal/assertNever";
|
|
3
|
+
import { useDockLayout } from "./useDockLayout";
|
|
4
|
+
|
|
5
|
+
const INITIAL_LAYOUT: LayoutNode | null = null;
|
|
6
|
+
|
|
7
|
+
function loadInitialLayout() {
|
|
8
|
+
const savedLayout = localStorage.getItem("layout");
|
|
9
|
+
if (savedLayout === null) {
|
|
10
|
+
return INITIAL_LAYOUT;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(savedLayout);
|
|
14
|
+
return parsed.root;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error(error);
|
|
17
|
+
return INITIAL_LAYOUT;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function App() {
|
|
22
|
+
const {
|
|
23
|
+
layoutManager,
|
|
24
|
+
containerRef,
|
|
25
|
+
layoutRects,
|
|
26
|
+
draggingRect,
|
|
27
|
+
getRectProps,
|
|
28
|
+
getDropZoneProps,
|
|
29
|
+
getDragHandleProps,
|
|
30
|
+
} = useDockLayout<HTMLDivElement>(loadInitialLayout());
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<div style={{ height: "10vh", border: "1px solid white" }}>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
onClick={() => {
|
|
38
|
+
layoutManager.addPanel();
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
Add panel
|
|
42
|
+
</button>
|
|
43
|
+
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
onClick={() => {
|
|
47
|
+
localStorage.setItem("layout", layoutManager.serialize());
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
Save to Local Storage
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
<div
|
|
54
|
+
ref={containerRef}
|
|
55
|
+
style={{
|
|
56
|
+
height: "90vh",
|
|
57
|
+
position: "relative",
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
{layoutRects.map((rect) => {
|
|
61
|
+
if (rect.type === "split") {
|
|
62
|
+
const { style, ...props } = getRectProps(rect);
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
key={rect.id}
|
|
66
|
+
style={{ ...style, backgroundColor: "white", fontSize: 20 }}
|
|
67
|
+
{...props}
|
|
68
|
+
></div>
|
|
69
|
+
);
|
|
70
|
+
} else if (rect.type === "panel") {
|
|
71
|
+
const { style, ...props } = getRectProps(rect);
|
|
72
|
+
const dropZoneProps = getDropZoneProps(rect);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
key={rect.id}
|
|
77
|
+
style={{
|
|
78
|
+
...style,
|
|
79
|
+
fontSize: 20,
|
|
80
|
+
display: "grid",
|
|
81
|
+
placeItems: "center",
|
|
82
|
+
opacity: draggingRect?.id === rect.id ? 0.5 : 1,
|
|
83
|
+
}}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
{dropZoneProps !== null && (
|
|
87
|
+
<div
|
|
88
|
+
style={{
|
|
89
|
+
...dropZoneProps.style,
|
|
90
|
+
backgroundColor: "#3182f6",
|
|
91
|
+
opacity: 0.5,
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
<button type="button" {...getDragHandleProps(rect)}>
|
|
97
|
+
Drag Handle
|
|
98
|
+
</button>
|
|
99
|
+
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
onClick={() => {
|
|
103
|
+
layoutManager.removePanel(rect.id);
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
Close Panel
|
|
107
|
+
</button>
|
|
108
|
+
|
|
109
|
+
{rect.id}
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
} else {
|
|
113
|
+
assertNever(rect);
|
|
114
|
+
}
|
|
115
|
+
})}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
package/src/global.css
ADDED
package/src/index.ts
ADDED
package/src/index.tsx
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class EventEmitter {
|
|
2
|
+
private _listeners = new Set<() => void>();
|
|
3
|
+
|
|
4
|
+
subscribe(listener: () => void) {
|
|
5
|
+
this._listeners.add(listener);
|
|
6
|
+
|
|
7
|
+
return () => {
|
|
8
|
+
this._listeners.delete(listener);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
emit() {
|
|
13
|
+
this._listeners.forEach((listener) => {
|
|
14
|
+
listener();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|