react-three-game 0.0.14 → 0.0.16
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 +50 -66
- package/dist/helpers/index.d.ts +35 -0
- package/dist/helpers/index.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/shared/GameCanvas.js +13 -13
- package/dist/tools/dragdrop/DragDropLoader.js +1 -0
- package/dist/tools/prefabeditor/EditorTree.js +116 -4
- package/dist/tools/prefabeditor/EditorUI.js +160 -6
- package/dist/tools/prefabeditor/PrefabEditor.js +56 -4
- package/dist/tools/prefabeditor/PrefabRoot.js +1 -10
- package/dist/tools/prefabeditor/types.d.ts +4 -4
- package/dist/tools/prefabeditor/types.js +1 -0
- package/package.json +4 -1
- package/src/helpers/index.ts +95 -0
- package/src/index.ts +3 -0
- package/src/shared/GameCanvas.tsx +5 -2
- package/src/tools/dragdrop/DragDropLoader.tsx +1 -0
- package/src/tools/prefabeditor/EditorTree.tsx +154 -16
- package/src/tools/prefabeditor/EditorUI.tsx +198 -51
- package/src/tools/prefabeditor/PrefabEditor.tsx +67 -7
- package/src/tools/prefabeditor/PrefabRoot.tsx +2 -13
- package/src/tools/prefabeditor/types.ts +5 -5
package/README.md
CHANGED
|
@@ -17,45 +17,26 @@ npm i react-three-game @react-three/fiber three
|
|
|
17
17
|
Scenes are JSON prefabs. Components are registered modules. Hierarchy is declarative.
|
|
18
18
|
|
|
19
19
|
```jsx
|
|
20
|
-
<PrefabRoot data={{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}} />
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Tailwind CSS Support
|
|
34
|
-
|
|
35
|
-
This library uses Tailwind CSS for styling its editor components. To ensure styles are correctly applied in your application, you need to configure Tailwind to scan the library's source files.
|
|
36
|
-
|
|
37
|
-
### Tailwind v4
|
|
38
|
-
|
|
39
|
-
Add the library path to your CSS entry point using the `@source` directive:
|
|
40
|
-
|
|
41
|
-
```css
|
|
42
|
-
@import "tailwindcss";
|
|
43
|
-
@source "../../node_modules/react-three-game/dist/**/*.{js,ts,jsx,tsx}";
|
|
20
|
+
<PrefabRoot data={{
|
|
21
|
+
root: {
|
|
22
|
+
id: "cube",
|
|
23
|
+
components: {
|
|
24
|
+
transform: { type: "Transform", properties: { position: [0, 1, 0] } },
|
|
25
|
+
geometry: { type: "Geometry", properties: { geometryType: "box" } },
|
|
26
|
+
material: { type: "Material", properties: { color: "green" } },
|
|
27
|
+
physics: { type: "Physics", properties: { type: "dynamic" } }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}} />
|
|
44
31
|
```
|
|
45
32
|
|
|
46
|
-
|
|
33
|
+
## Styling
|
|
47
34
|
|
|
48
|
-
|
|
35
|
+
The prefab editor UI ships with **inline styles** (no Tailwind / CSS framework required). That means you can install and render it without any additional build-time CSS configuration.
|
|
49
36
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// ...
|
|
54
|
-
"./node_modules/react-three-game/dist/**/*.{js,ts,jsx,tsx}",
|
|
55
|
-
],
|
|
56
|
-
// ...
|
|
57
|
-
}
|
|
58
|
-
```
|
|
37
|
+
If you want to fully restyle the editor, you can:
|
|
38
|
+
- Wrap `PrefabEditor` in your own layout and override positioning.
|
|
39
|
+
- Fork/compose the editor UI components (they’re plain React components).
|
|
59
40
|
|
|
60
41
|
## Quick Start
|
|
61
42
|
|
|
@@ -64,41 +45,44 @@ npm install react-three-game @react-three/fiber @react-three/rapier three
|
|
|
64
45
|
```
|
|
65
46
|
|
|
66
47
|
```jsx
|
|
48
|
+
import { Physics } from '@react-three/rapier';
|
|
67
49
|
import { GameCanvas, PrefabRoot } from 'react-three-game';
|
|
68
50
|
|
|
69
51
|
export default function App() {
|
|
70
52
|
return (
|
|
71
53
|
<GameCanvas>
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
54
|
+
<Physics>
|
|
55
|
+
<ambientLight intensity={0.8} />
|
|
56
|
+
<PrefabRoot
|
|
57
|
+
data={{
|
|
58
|
+
id: "scene",
|
|
59
|
+
name: "scene",
|
|
60
|
+
root: {
|
|
61
|
+
id: "root",
|
|
62
|
+
children: [
|
|
63
|
+
{
|
|
64
|
+
id: "ground",
|
|
65
|
+
components: {
|
|
66
|
+
transform: { type: "Transform", properties: { position: [0, 0, 0], rotation: [-1.57, 0, 0] } },
|
|
67
|
+
geometry: { type: "Geometry", properties: { geometryType: "plane", args: [50, 50] } },
|
|
68
|
+
material: { type: "Material", properties: { color: "green" } },
|
|
69
|
+
physics: { type: "Physics", properties: { type: "fixed" } }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "player",
|
|
74
|
+
components: {
|
|
75
|
+
transform: { type: "Transform", properties: { position: [0, 2, 0] } },
|
|
76
|
+
geometry: { type: "Geometry", properties: { geometryType: "sphere" } },
|
|
77
|
+
material: { type: "Material", properties: { color: "#ff6b6b" } },
|
|
78
|
+
physics: { type: "Physics", properties: { type: "dynamic" } }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]
|
|
97
82
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}} />
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
</Physics>
|
|
102
86
|
</GameCanvas>
|
|
103
87
|
);
|
|
104
88
|
}
|
|
@@ -109,8 +93,8 @@ export default function App() {
|
|
|
109
93
|
```typescript
|
|
110
94
|
interface GameObject {
|
|
111
95
|
id: string;
|
|
112
|
-
|
|
113
|
-
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
hidden?: boolean;
|
|
114
98
|
components: {
|
|
115
99
|
transform?: TransformComponent;
|
|
116
100
|
geometry?: GeometryComponent;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { GameObject } from "../tools/prefabeditor/types";
|
|
2
|
+
export type Vec3 = [number, number, number];
|
|
3
|
+
export interface GroundOptions {
|
|
4
|
+
/** GameObject id. Defaults to "ground". */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Plane size. Defaults to 50. */
|
|
7
|
+
size?: number;
|
|
8
|
+
/** Transform overrides. */
|
|
9
|
+
position?: Vec3;
|
|
10
|
+
rotation?: Vec3;
|
|
11
|
+
scale?: Vec3;
|
|
12
|
+
/** Material overrides. */
|
|
13
|
+
color?: string;
|
|
14
|
+
texture?: string;
|
|
15
|
+
/** When true, set repeat wrapping. Defaults to true if texture is provided. */
|
|
16
|
+
repeat?: boolean;
|
|
17
|
+
/** Texture repeat counts when repeat=true. Defaults to [25,25]. */
|
|
18
|
+
repeatCount?: [number, number];
|
|
19
|
+
/** Physics body type. Defaults to "fixed". */
|
|
20
|
+
physicsType?: "fixed" | "dynamic" | "kinematic";
|
|
21
|
+
/** Set true to hide the node. */
|
|
22
|
+
hidden?: boolean;
|
|
23
|
+
/** Set true to disable the node. */
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a ready-to-use plane ground GameObject.
|
|
28
|
+
*
|
|
29
|
+
* Designed to reduce prefab boilerplate:
|
|
30
|
+
* - Transform (rotated to lie flat)
|
|
31
|
+
* - Geometry (plane)
|
|
32
|
+
* - Material (optional texture + repeat)
|
|
33
|
+
* - Physics (fixed by default)
|
|
34
|
+
*/
|
|
35
|
+
export declare function ground(options?: GroundOptions): GameObject;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a ready-to-use plane ground GameObject.
|
|
3
|
+
*
|
|
4
|
+
* Designed to reduce prefab boilerplate:
|
|
5
|
+
* - Transform (rotated to lie flat)
|
|
6
|
+
* - Geometry (plane)
|
|
7
|
+
* - Material (optional texture + repeat)
|
|
8
|
+
* - Physics (fixed by default)
|
|
9
|
+
*/
|
|
10
|
+
export function ground(options = {}) {
|
|
11
|
+
const { id = "ground", size = 50, position = [0, 0, 0], rotation = [-Math.PI / 2, 0, 0], scale = [1, 1, 1], color = "white", texture, repeat = texture ? true : false, repeatCount = [25, 25], physicsType = "fixed", hidden = false, disabled = false, } = options;
|
|
12
|
+
return {
|
|
13
|
+
id,
|
|
14
|
+
disabled,
|
|
15
|
+
hidden,
|
|
16
|
+
components: {
|
|
17
|
+
transform: {
|
|
18
|
+
type: "Transform",
|
|
19
|
+
properties: {
|
|
20
|
+
position,
|
|
21
|
+
rotation,
|
|
22
|
+
scale,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
geometry: {
|
|
26
|
+
type: "Geometry",
|
|
27
|
+
properties: {
|
|
28
|
+
geometryType: "plane",
|
|
29
|
+
args: [size, size],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
material: {
|
|
33
|
+
type: "Material",
|
|
34
|
+
properties: Object.assign(Object.assign({ color }, (texture ? { texture } : {})), (repeat ? { repeat: true, repeatCount } : {})),
|
|
35
|
+
},
|
|
36
|
+
physics: {
|
|
37
|
+
type: "Physics",
|
|
38
|
+
properties: {
|
|
39
|
+
type: physicsType,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
|
3
3
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
4
4
|
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
5
5
|
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
6
|
+
export * from './helpers';
|
|
6
7
|
export type { Prefab, GameObject } from './tools/prefabeditor/types';
|
package/dist/index.js
CHANGED
|
@@ -4,3 +4,5 @@ export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
|
4
4
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
5
5
|
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
6
6
|
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
7
|
+
// Helpers
|
|
8
|
+
export * from './helpers';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -18,7 +19,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
18
19
|
}
|
|
19
20
|
return t;
|
|
20
21
|
};
|
|
21
|
-
import { jsx as _jsx,
|
|
22
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
22
23
|
import { Canvas, extend } from "@react-three/fiber";
|
|
23
24
|
import { WebGPURenderer, MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial, PCFShadowMap } from "three/webgpu";
|
|
24
25
|
import { Suspense, useState } from "react";
|
|
@@ -33,16 +34,15 @@ extend({
|
|
|
33
34
|
export default function GameCanvas(_a) {
|
|
34
35
|
var { loader = false, children } = _a, props = __rest(_a, ["loader", "children"]);
|
|
35
36
|
const [frameloop, setFrameloop] = useState("never");
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}, children: _jsx(Suspense, { children: children }) }), loader ? _jsx(Loader, {}) : null] });
|
|
37
|
+
return _jsx(_Fragment, { children: _jsxs(Canvas, { style: { touchAction: 'none' }, shadows: { type: PCFShadowMap, }, frameloop: frameloop, gl: (_a) => __awaiter(this, [_a], void 0, function* ({ canvas }) {
|
|
38
|
+
const renderer = new WebGPURenderer(Object.assign({ canvas: canvas,
|
|
39
|
+
// @ts-expect-error futuristic
|
|
40
|
+
shadowMap: true, antialias: true }, props));
|
|
41
|
+
yield renderer.init().then(() => {
|
|
42
|
+
setFrameloop("always");
|
|
43
|
+
});
|
|
44
|
+
return renderer;
|
|
45
|
+
}), camera: {
|
|
46
|
+
position: [0, 1, 5],
|
|
47
|
+
}, children: [_jsx(Suspense, { children: children }), loader ? _jsx(Loader, {}) : null] }) });
|
|
48
48
|
}
|
|
@@ -6,6 +6,94 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
6
6
|
const [draggedId, setDraggedId] = useState(null);
|
|
7
7
|
const [collapsedIds, setCollapsedIds] = useState(new Set());
|
|
8
8
|
const [isTreeCollapsed, setIsTreeCollapsed] = useState(false);
|
|
9
|
+
const styles = {
|
|
10
|
+
panel: {
|
|
11
|
+
background: "rgba(0,0,0,0.55)",
|
|
12
|
+
color: "rgba(255,255,255,0.9)",
|
|
13
|
+
border: "1px solid rgba(255,255,255,0.12)",
|
|
14
|
+
borderRadius: 6,
|
|
15
|
+
overflow: "hidden",
|
|
16
|
+
maxHeight: "85vh",
|
|
17
|
+
display: "flex",
|
|
18
|
+
flexDirection: "column",
|
|
19
|
+
backdropFilter: "blur(6px)",
|
|
20
|
+
WebkitBackdropFilter: "blur(6px)",
|
|
21
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
|
|
22
|
+
fontSize: 11,
|
|
23
|
+
lineHeight: 1.2,
|
|
24
|
+
userSelect: "none",
|
|
25
|
+
WebkitUserSelect: "none",
|
|
26
|
+
},
|
|
27
|
+
panelHeader: {
|
|
28
|
+
padding: "4px 6px",
|
|
29
|
+
borderBottom: "1px solid rgba(255,255,255,0.10)",
|
|
30
|
+
display: "flex",
|
|
31
|
+
gap: 8,
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
justifyContent: "space-between",
|
|
34
|
+
cursor: "pointer",
|
|
35
|
+
background: "rgba(255,255,255,0.05)",
|
|
36
|
+
textTransform: "uppercase",
|
|
37
|
+
letterSpacing: "0.08em",
|
|
38
|
+
fontSize: 10,
|
|
39
|
+
color: "rgba(255,255,255,0.7)",
|
|
40
|
+
},
|
|
41
|
+
scroll: {
|
|
42
|
+
overflowY: "auto",
|
|
43
|
+
},
|
|
44
|
+
row: {
|
|
45
|
+
display: "flex",
|
|
46
|
+
alignItems: "center",
|
|
47
|
+
padding: "2px 6px",
|
|
48
|
+
borderBottom: "1px solid rgba(255,255,255,0.07)",
|
|
49
|
+
cursor: "pointer",
|
|
50
|
+
whiteSpace: "nowrap",
|
|
51
|
+
},
|
|
52
|
+
rowSelected: {
|
|
53
|
+
background: "rgba(255,255,255,0.10)",
|
|
54
|
+
},
|
|
55
|
+
chevron: {
|
|
56
|
+
width: 12,
|
|
57
|
+
textAlign: "center",
|
|
58
|
+
opacity: 0.55,
|
|
59
|
+
fontSize: 10,
|
|
60
|
+
marginRight: 4,
|
|
61
|
+
cursor: "pointer",
|
|
62
|
+
},
|
|
63
|
+
idText: {
|
|
64
|
+
fontSize: 11,
|
|
65
|
+
overflow: "hidden",
|
|
66
|
+
textOverflow: "ellipsis",
|
|
67
|
+
},
|
|
68
|
+
contextMenu: {
|
|
69
|
+
position: "fixed",
|
|
70
|
+
zIndex: 50,
|
|
71
|
+
minWidth: 120,
|
|
72
|
+
background: "rgba(0,0,0,0.82)",
|
|
73
|
+
border: "1px solid rgba(255,255,255,0.16)",
|
|
74
|
+
borderRadius: 6,
|
|
75
|
+
overflow: "hidden",
|
|
76
|
+
boxShadow: "0 12px 32px rgba(0,0,0,0.45)",
|
|
77
|
+
backdropFilter: "blur(6px)",
|
|
78
|
+
WebkitBackdropFilter: "blur(6px)",
|
|
79
|
+
},
|
|
80
|
+
menuItem: {
|
|
81
|
+
width: "100%",
|
|
82
|
+
textAlign: "left",
|
|
83
|
+
padding: "6px 8px",
|
|
84
|
+
background: "transparent",
|
|
85
|
+
border: "none",
|
|
86
|
+
color: "rgba(255,255,255,0.9)",
|
|
87
|
+
font: "inherit",
|
|
88
|
+
cursor: "pointer",
|
|
89
|
+
},
|
|
90
|
+
menuItemDanger: {
|
|
91
|
+
color: "rgba(255,120,120,0.95)",
|
|
92
|
+
},
|
|
93
|
+
menuDivider: {
|
|
94
|
+
borderTop: "1px solid rgba(255,255,255,0.10)",
|
|
95
|
+
}
|
|
96
|
+
};
|
|
9
97
|
if (!prefabData || !setPrefabData)
|
|
10
98
|
return null;
|
|
11
99
|
const handleContextMenu = (e, nodeId) => {
|
|
@@ -30,8 +118,6 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
30
118
|
var _a;
|
|
31
119
|
const newNode = {
|
|
32
120
|
id: crypto.randomUUID(),
|
|
33
|
-
enabled: true,
|
|
34
|
-
visible: true,
|
|
35
121
|
components: {
|
|
36
122
|
transform: {
|
|
37
123
|
type: "Transform",
|
|
@@ -134,9 +220,35 @@ export default function EditorTree({ prefabData, setPrefabData, selectedId, setS
|
|
|
134
220
|
const isSelected = node.id === selectedId;
|
|
135
221
|
const isCollapsed = collapsedIds.has(node.id);
|
|
136
222
|
const hasChildren = node.children && node.children.length > 0;
|
|
137
|
-
return (_jsxs("div", {
|
|
223
|
+
return (_jsxs("div", { children: [_jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, styles.row), (isSelected ? styles.rowSelected : null)), { paddingLeft: `${depth * 10 + 6}px` }), onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => handleContextMenu(e, node.id), draggable: node.id !== prefabData.root.id, onDragStart: (e) => handleDragStart(e, node.id), onDragOver: (e) => handleDragOver(e, node.id), onDrop: (e) => handleDrop(e, node.id), onPointerEnter: (e) => {
|
|
224
|
+
if (!isSelected)
|
|
225
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.06)";
|
|
226
|
+
}, onPointerLeave: (e) => {
|
|
227
|
+
if (!isSelected)
|
|
228
|
+
e.currentTarget.style.background = "transparent";
|
|
229
|
+
}, children: [_jsx("span", { style: Object.assign(Object.assign({}, styles.chevron), { visibility: hasChildren ? 'visible' : 'hidden' }), onClick: (e) => hasChildren && toggleCollapse(e, node.id), onPointerEnter: (e) => {
|
|
230
|
+
e.currentTarget.style.opacity = "0.9";
|
|
231
|
+
}, onPointerLeave: (e) => {
|
|
232
|
+
e.currentTarget.style.opacity = "0.55";
|
|
233
|
+
}, children: isCollapsed ? '▶' : '▼' }), _jsx("span", { style: styles.idText, children: node.id })] }), !isCollapsed && node.children && (_jsx("div", { children: node.children.map(child => renderNode(child, depth + 1)) }))] }, node.id));
|
|
138
234
|
};
|
|
139
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", {
|
|
235
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, styles.panel), { width: isTreeCollapsed ? 'auto' : '14rem' }), onClick: closeContextMenu, children: [_jsxs("div", { style: styles.panelHeader, onClick: (e) => { e.stopPropagation(); setIsTreeCollapsed(!isTreeCollapsed); }, onPointerEnter: (e) => {
|
|
236
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
237
|
+
}, onPointerLeave: (e) => {
|
|
238
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.05)";
|
|
239
|
+
}, children: [_jsx("span", { children: "Prefab Graph" }), _jsx("span", { style: { fontSize: 10, opacity: 0.8 }, children: isTreeCollapsed ? '▶' : '◀' })] }), !isTreeCollapsed && (_jsx("div", { style: Object.assign(Object.assign({}, styles.scroll), { padding: 2 }), children: renderNode(prefabData.root) }))] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, styles.contextMenu), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: closeContextMenu, children: [_jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuDivider), onClick: () => handleAddChild(contextMenu.nodeId), onPointerEnter: (e) => {
|
|
240
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
241
|
+
}, onPointerLeave: (e) => {
|
|
242
|
+
e.currentTarget.style.background = "transparent";
|
|
243
|
+
}, children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuDivider), onClick: () => handleDuplicate(contextMenu.nodeId), onPointerEnter: (e) => {
|
|
244
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
245
|
+
}, onPointerLeave: (e) => {
|
|
246
|
+
e.currentTarget.style.background = "transparent";
|
|
247
|
+
}, children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, styles.menuItem), styles.menuItemDanger), onClick: () => handleDelete(contextMenu.nodeId), onPointerEnter: (e) => {
|
|
248
|
+
e.currentTarget.style.background = "rgba(255,255,255,0.08)";
|
|
249
|
+
}, onPointerLeave: (e) => {
|
|
250
|
+
e.currentTarget.style.background = "transparent";
|
|
251
|
+
}, children: "Delete" })] }))] }))] }));
|
|
140
252
|
}
|
|
141
253
|
// --- Helpers ---
|
|
142
254
|
function findNode(root, id) {
|