react-three-game 0.0.67 → 0.0.69

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.
Files changed (35) hide show
  1. package/README.md +109 -304
  2. package/dist/index.d.ts +15 -8
  3. package/dist/index.js +11 -8
  4. package/dist/shared/GameCanvas.d.ts +1 -2
  5. package/dist/tools/prefabeditor/EditorContext.d.ts +2 -2
  6. package/dist/tools/prefabeditor/EditorTree.d.ts +6 -6
  7. package/dist/tools/prefabeditor/EditorTree.js +92 -142
  8. package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +4 -11
  9. package/dist/tools/prefabeditor/EditorTreeMenus.js +16 -25
  10. package/dist/tools/prefabeditor/EditorUI.d.ts +5 -5
  11. package/dist/tools/prefabeditor/EditorUI.js +14 -11
  12. package/dist/tools/prefabeditor/GameEvents.d.ts +0 -30
  13. package/dist/tools/prefabeditor/GameEvents.js +0 -7
  14. package/dist/tools/prefabeditor/PrefabEditor.d.ts +12 -13
  15. package/dist/tools/prefabeditor/PrefabEditor.js +168 -138
  16. package/dist/tools/prefabeditor/PrefabRoot.d.ts +8 -5
  17. package/dist/tools/prefabeditor/PrefabRoot.js +141 -123
  18. package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -3
  19. package/dist/tools/prefabeditor/components/CameraComponent.js +2 -2
  20. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
  21. package/dist/tools/prefabeditor/components/ModelComponent.js +0 -1
  22. package/dist/tools/prefabeditor/components/SpotLightComponent.js +2 -2
  23. package/dist/tools/prefabeditor/components/TextComponent.js +2 -3
  24. package/dist/tools/prefabeditor/components/TransformComponent.js +9 -14
  25. package/dist/tools/prefabeditor/prefabStore.d.ts +42 -0
  26. package/dist/tools/prefabeditor/prefabStore.js +347 -0
  27. package/dist/tools/prefabeditor/sceneApi.d.ts +44 -0
  28. package/dist/tools/prefabeditor/sceneApi.js +161 -0
  29. package/dist/tools/prefabeditor/styles.d.ts +2 -1
  30. package/dist/tools/prefabeditor/styles.js +2 -12
  31. package/dist/tools/prefabeditor/utils.d.ts +15 -36
  32. package/dist/tools/prefabeditor/utils.js +36 -162
  33. package/package.json +4 -3
  34. package/dist/tools/prefabeditor/EventSystem.d.ts +0 -7
  35. package/dist/tools/prefabeditor/EventSystem.js +0 -23
@@ -0,0 +1,42 @@
1
+ import { type ReactNode } from "react";
2
+ import { type StoreApi } from "zustand/vanilla";
3
+ import { GameObject, Prefab } from "./types";
4
+ export type PrefabNodeRecord = Omit<GameObject, "children">;
5
+ type PrefabAssetRefCounts = Record<string, number>;
6
+ type PrefabDocumentSnapshot = {
7
+ prefabId?: string;
8
+ prefabName?: string;
9
+ rootId: string;
10
+ nodesById: Record<string, PrefabNodeRecord>;
11
+ childIdsById: Record<string, string[]>;
12
+ parentIdById: Record<string, string | null>;
13
+ revision: number;
14
+ assetManifestKey: string;
15
+ assetRefCounts: PrefabAssetRefCounts;
16
+ };
17
+ export interface PrefabStoreState extends PrefabDocumentSnapshot {
18
+ replacePrefab: (prefab: Prefab) => void;
19
+ updateNode: (id: string, update: (node: PrefabNodeRecord) => PrefabNodeRecord) => void;
20
+ updateNodes: (updates: Array<{
21
+ id: string;
22
+ update: (node: PrefabNodeRecord) => PrefabNodeRecord;
23
+ }>) => void;
24
+ addChild: (parentId: string, node: GameObject) => void;
25
+ deleteNode: (id: string) => void;
26
+ duplicateNode: (id: string) => string | null;
27
+ toggleNodeFlag: (id: string, key: "disabled" | "locked") => void;
28
+ moveNode: (draggedId: string, targetId: string, position: "before" | "inside") => void;
29
+ }
30
+ export type PrefabStoreApi = StoreApi<PrefabStoreState>;
31
+ export declare function PrefabStoreProvider({ store, children, }: {
32
+ store: PrefabStoreApi;
33
+ children: ReactNode;
34
+ }): import("react").FunctionComponentElement<import("react").ProviderProps<PrefabStoreApi | null>>;
35
+ export declare function usePrefabStoreApi(): PrefabStoreApi;
36
+ export declare function usePrefabStore<T>(selector: (state: PrefabStoreState) => T): T;
37
+ export declare function usePrefabRootId(): string;
38
+ export declare function usePrefabNode(nodeId: string | null | undefined): PrefabNodeRecord | null;
39
+ export declare function usePrefabChildIds(nodeId: string | null | undefined): string[];
40
+ export declare function createPrefabStore(prefab: Prefab): PrefabStoreApi;
41
+ export declare function prefabStoreToPrefab(state: Pick<PrefabDocumentSnapshot, "prefabId" | "prefabName" | "rootId" | "nodesById" | "childIdsById">): Prefab;
42
+ export {};
@@ -0,0 +1,347 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { createContext, createElement, useContext } from "react";
13
+ import { subscribeWithSelector } from "zustand/middleware";
14
+ import { useStore } from "zustand";
15
+ import { createStore } from "zustand/vanilla";
16
+ const PrefabStoreContext = createContext(null);
17
+ const EMPTY_CHILD_IDS = [];
18
+ export function PrefabStoreProvider({ store, children, }) {
19
+ return createElement(PrefabStoreContext.Provider, { value: store }, children);
20
+ }
21
+ export function usePrefabStoreApi() {
22
+ const store = useContext(PrefabStoreContext);
23
+ if (!store) {
24
+ throw new Error("usePrefabStoreApi must be used within PrefabStoreProvider");
25
+ }
26
+ return store;
27
+ }
28
+ export function usePrefabStore(selector) {
29
+ return useStore(usePrefabStoreApi(), selector);
30
+ }
31
+ export function usePrefabRootId() {
32
+ return usePrefabStore(state => state.rootId);
33
+ }
34
+ export function usePrefabNode(nodeId) {
35
+ return usePrefabStore(state => { var _a; return nodeId ? (_a = state.nodesById[nodeId]) !== null && _a !== void 0 ? _a : null : null; });
36
+ }
37
+ export function usePrefabChildIds(nodeId) {
38
+ return usePrefabStore(state => { var _a; return nodeId ? (_a = state.childIdsById[nodeId]) !== null && _a !== void 0 ? _a : EMPTY_CHILD_IDS : EMPTY_CHILD_IDS; });
39
+ }
40
+ export function createPrefabStore(prefab) {
41
+ return createStore()(subscribeWithSelector((set, get) => (Object.assign(Object.assign({}, createDocumentState(prefab)), { replacePrefab: (nextPrefab) => {
42
+ set(createDocumentState(nextPrefab, get().revision + 1));
43
+ }, updateNode: (id, update) => {
44
+ const state = get();
45
+ const node = state.nodesById[id];
46
+ if (!node)
47
+ return;
48
+ const nextNode = update(node);
49
+ if (nextNode === node)
50
+ return;
51
+ const nextAssetRefCounts = updateAssetRefsForNodeChange(state.assetRefCounts, node, nextNode);
52
+ set(createMutationPatch(state, {
53
+ nodesById: Object.assign(Object.assign({}, state.nodesById), { [id]: nextNode }),
54
+ }, nextAssetRefCounts));
55
+ }, updateNodes: (updates) => {
56
+ if (updates.length === 0)
57
+ return;
58
+ const state = get();
59
+ let nextNodesById = null;
60
+ let nextAssetRefCounts = state.assetRefCounts;
61
+ for (const { id, update } of updates) {
62
+ const currentNode = (nextNodesById !== null && nextNodesById !== void 0 ? nextNodesById : state.nodesById)[id];
63
+ if (!currentNode)
64
+ continue;
65
+ const nextNode = update(currentNode);
66
+ if (nextNode === currentNode)
67
+ continue;
68
+ nextNodesById !== null && nextNodesById !== void 0 ? nextNodesById : (nextNodesById = Object.assign({}, state.nodesById));
69
+ nextNodesById[id] = nextNode;
70
+ nextAssetRefCounts = updateAssetRefsForNodeChange(nextAssetRefCounts, currentNode, nextNode);
71
+ }
72
+ if (!nextNodesById)
73
+ return;
74
+ set(createMutationPatch(state, { nodesById: nextNodesById }, nextAssetRefCounts));
75
+ }, addChild: (parentId, node) => {
76
+ var _a;
77
+ const state = get();
78
+ if (!state.nodesById[parentId])
79
+ return;
80
+ const nextNodesById = Object.assign({}, state.nodesById);
81
+ const nextChildIdsById = Object.assign({}, state.childIdsById);
82
+ const nextParentIdById = Object.assign({}, state.parentIdById);
83
+ const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
84
+ insertSubtree(node, parentId, nextNodesById, nextChildIdsById, nextParentIdById);
85
+ nextChildIdsById[parentId] = [...((_a = nextChildIdsById[parentId]) !== null && _a !== void 0 ? _a : []), node.id];
86
+ addAssetRefs(nextAssetRefCounts, collectSubtreeAssetRefs(node));
87
+ set(createMutationPatch(state, {
88
+ nodesById: nextNodesById,
89
+ childIdsById: nextChildIdsById,
90
+ parentIdById: nextParentIdById,
91
+ }, nextAssetRefCounts));
92
+ }, deleteNode: (id) => {
93
+ var _a;
94
+ const state = get();
95
+ if (id === state.rootId || !state.nodesById[id])
96
+ return;
97
+ const parentId = state.parentIdById[id];
98
+ if (!parentId)
99
+ return;
100
+ const idsToDelete = collectSubtreeIds(id, state.childIdsById);
101
+ const nextNodesById = Object.assign({}, state.nodesById);
102
+ const nextChildIdsById = Object.assign({}, state.childIdsById);
103
+ const nextParentIdById = Object.assign({}, state.parentIdById);
104
+ const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
105
+ removeAssetRefs(nextAssetRefCounts, collectAssetRefsForIds(idsToDelete, state.nodesById));
106
+ idsToDelete.forEach(nodeId => {
107
+ delete nextNodesById[nodeId];
108
+ delete nextChildIdsById[nodeId];
109
+ delete nextParentIdById[nodeId];
110
+ });
111
+ nextChildIdsById[parentId] = ((_a = nextChildIdsById[parentId]) !== null && _a !== void 0 ? _a : []).filter(childId => childId !== id);
112
+ set(createMutationPatch(state, {
113
+ nodesById: nextNodesById,
114
+ childIdsById: nextChildIdsById,
115
+ parentIdById: nextParentIdById,
116
+ }, nextAssetRefCounts));
117
+ }, duplicateNode: (id) => {
118
+ var _a;
119
+ const state = get();
120
+ if (id === state.rootId || !state.nodesById[id])
121
+ return null;
122
+ const parentId = state.parentIdById[id];
123
+ if (!parentId)
124
+ return null;
125
+ const nextNodesById = Object.assign({}, state.nodesById);
126
+ const nextChildIdsById = Object.assign({}, state.childIdsById);
127
+ const nextParentIdById = Object.assign({}, state.parentIdById);
128
+ const duplicatedRootId = cloneSubtreeIntoMaps(id, parentId, state, nextNodesById, nextChildIdsById, nextParentIdById);
129
+ if (!duplicatedRootId)
130
+ return null;
131
+ const siblings = [...((_a = nextChildIdsById[parentId]) !== null && _a !== void 0 ? _a : [])];
132
+ const currentIndex = siblings.findIndex(childId => childId === id);
133
+ if (currentIndex === -1) {
134
+ siblings.push(duplicatedRootId);
135
+ }
136
+ else {
137
+ siblings.splice(currentIndex + 1, 0, duplicatedRootId);
138
+ }
139
+ nextChildIdsById[parentId] = siblings;
140
+ const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
141
+ addAssetRefs(nextAssetRefCounts, collectAssetRefsForIds(collectSubtreeIds(id, state.childIdsById), state.nodesById));
142
+ set(createMutationPatch(state, {
143
+ nodesById: nextNodesById,
144
+ childIdsById: nextChildIdsById,
145
+ parentIdById: nextParentIdById,
146
+ }, nextAssetRefCounts));
147
+ return duplicatedRootId;
148
+ }, toggleNodeFlag: (id, key) => {
149
+ const state = get();
150
+ const node = state.nodesById[id];
151
+ if (!node)
152
+ return;
153
+ const nextNode = Object.assign(Object.assign({}, node), { [key]: !node[key] });
154
+ set(createMutationPatch(state, {
155
+ nodesById: Object.assign(Object.assign({}, state.nodesById), { [id]: nextNode }),
156
+ }));
157
+ }, moveNode: (draggedId, targetId, position) => {
158
+ var _a, _b, _c;
159
+ const state = get();
160
+ if (draggedId === state.rootId || draggedId === targetId)
161
+ return;
162
+ if (!state.nodesById[draggedId] || !state.nodesById[targetId])
163
+ return;
164
+ if (isDescendant(targetId, draggedId, state.parentIdById))
165
+ return;
166
+ const currentParentId = state.parentIdById[draggedId];
167
+ if (!currentParentId)
168
+ return;
169
+ const destinationParentId = position === "inside"
170
+ ? targetId
171
+ : state.parentIdById[targetId];
172
+ if (!destinationParentId)
173
+ return;
174
+ if (destinationParentId === draggedId || isDescendant(destinationParentId, draggedId, state.parentIdById))
175
+ return;
176
+ const nextChildIdsById = Object.assign({}, state.childIdsById);
177
+ const nextParentIdById = Object.assign(Object.assign({}, state.parentIdById), { [draggedId]: destinationParentId });
178
+ const sourceChildren = [...((_a = nextChildIdsById[currentParentId]) !== null && _a !== void 0 ? _a : [])].filter(childId => childId !== draggedId);
179
+ nextChildIdsById[currentParentId] = sourceChildren;
180
+ if (position === "inside") {
181
+ nextChildIdsById[destinationParentId] = [...((_b = nextChildIdsById[destinationParentId]) !== null && _b !== void 0 ? _b : []), draggedId];
182
+ }
183
+ else {
184
+ const destinationChildren = destinationParentId === currentParentId
185
+ ? [...sourceChildren]
186
+ : [...((_c = nextChildIdsById[destinationParentId]) !== null && _c !== void 0 ? _c : [])];
187
+ const targetIndex = destinationChildren.findIndex(childId => childId === targetId);
188
+ if (targetIndex === -1)
189
+ return;
190
+ destinationChildren.splice(targetIndex, 0, draggedId);
191
+ nextChildIdsById[destinationParentId] = destinationChildren;
192
+ }
193
+ set(createMutationPatch(state, {
194
+ childIdsById: nextChildIdsById,
195
+ parentIdById: nextParentIdById,
196
+ }));
197
+ } }))));
198
+ }
199
+ export function prefabStoreToPrefab(state) {
200
+ return {
201
+ id: state.prefabId,
202
+ name: state.prefabName,
203
+ root: denormalizeNode(state.rootId, state.nodesById, state.childIdsById),
204
+ };
205
+ }
206
+ function createDocumentState(prefab, revision = 0) {
207
+ const nodesById = {};
208
+ const childIdsById = {};
209
+ const parentIdById = {};
210
+ insertSubtree(prefab.root, null, nodesById, childIdsById, parentIdById);
211
+ const assetRefCounts = createAssetRefCounts(nodesById);
212
+ return {
213
+ prefabId: prefab.id,
214
+ prefabName: prefab.name,
215
+ rootId: prefab.root.id,
216
+ nodesById,
217
+ childIdsById,
218
+ parentIdById,
219
+ revision,
220
+ assetManifestKey: getAssetManifestKey(assetRefCounts),
221
+ assetRefCounts,
222
+ };
223
+ }
224
+ function createMutationPatch(state, patch, nextAssetRefCounts = state.assetRefCounts) {
225
+ const assetRefsChanged = nextAssetRefCounts !== state.assetRefCounts;
226
+ return Object.assign(Object.assign(Object.assign({}, patch), { revision: state.revision + 1 }), (assetRefsChanged ? {
227
+ assetRefCounts: nextAssetRefCounts,
228
+ assetManifestKey: getAssetManifestKey(nextAssetRefCounts),
229
+ } : null));
230
+ }
231
+ function denormalizeNode(id, nodesById, childIdsById) {
232
+ var _a;
233
+ const node = nodesById[id];
234
+ return Object.assign(Object.assign({}, node), { children: ((_a = childIdsById[id]) !== null && _a !== void 0 ? _a : []).map(childId => denormalizeNode(childId, nodesById, childIdsById)) });
235
+ }
236
+ function collectSubtreeIds(id, childIdsById) {
237
+ var _a;
238
+ const ids = [id];
239
+ for (const childId of (_a = childIdsById[id]) !== null && _a !== void 0 ? _a : []) {
240
+ ids.push(...collectSubtreeIds(childId, childIdsById));
241
+ }
242
+ return ids;
243
+ }
244
+ function insertSubtree(node, parentId, nodesById, childIdsById, parentIdById) {
245
+ var _a;
246
+ const { children } = node, nodeRecord = __rest(node, ["children"]);
247
+ nodesById[node.id] = nodeRecord;
248
+ childIdsById[node.id] = (_a = children === null || children === void 0 ? void 0 : children.map(child => child.id)) !== null && _a !== void 0 ? _a : [];
249
+ parentIdById[node.id] = parentId;
250
+ children === null || children === void 0 ? void 0 : children.forEach(child => insertSubtree(child, node.id, nodesById, childIdsById, parentIdById));
251
+ }
252
+ function cloneSubtreeIntoMaps(id, parentId, source, nodesById, childIdsById, parentIdById) {
253
+ var _a, _b;
254
+ const originalNode = source.nodesById[id];
255
+ if (!originalNode)
256
+ return null;
257
+ const clonedId = crypto.randomUUID();
258
+ const clonedNode = Object.assign(Object.assign({}, originalNode), { id: clonedId, name: `${(_a = originalNode.name) !== null && _a !== void 0 ? _a : originalNode.id} Copy` });
259
+ nodesById[clonedId] = clonedNode;
260
+ parentIdById[clonedId] = parentId;
261
+ const clonedChildIds = ((_b = source.childIdsById[id]) !== null && _b !== void 0 ? _b : [])
262
+ .map(childId => cloneSubtreeIntoMaps(childId, clonedId, source, nodesById, childIdsById, parentIdById))
263
+ .filter((childId) => Boolean(childId));
264
+ childIdsById[clonedId] = clonedChildIds;
265
+ return clonedId;
266
+ }
267
+ function isDescendant(id, potentialAncestorId, parentIdById) {
268
+ let currentId = id;
269
+ while (currentId) {
270
+ if (currentId === potentialAncestorId)
271
+ return true;
272
+ currentId = parentIdById[currentId];
273
+ }
274
+ return false;
275
+ }
276
+ function createAssetRefCounts(nodesById) {
277
+ const assetRefCounts = {};
278
+ Object.values(nodesById).forEach(node => addAssetRefs(assetRefCounts, getAssetRefs(node)));
279
+ return assetRefCounts;
280
+ }
281
+ function updateAssetRefsForNodeChange(assetRefCounts, currentNode, nextNode) {
282
+ const currentRefs = getAssetRefs(currentNode);
283
+ const nextRefs = getAssetRefs(nextNode);
284
+ if (sameStringArrays(currentRefs, nextRefs)) {
285
+ return assetRefCounts;
286
+ }
287
+ const nextAssetRefCounts = Object.assign({}, assetRefCounts);
288
+ removeAssetRefs(nextAssetRefCounts, currentRefs);
289
+ addAssetRefs(nextAssetRefCounts, nextRefs);
290
+ return nextAssetRefCounts;
291
+ }
292
+ function collectSubtreeAssetRefs(node) {
293
+ var _a;
294
+ const refs = getAssetRefs(node);
295
+ (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(child => refs.push(...collectSubtreeAssetRefs(child)));
296
+ return refs;
297
+ }
298
+ function collectAssetRefsForIds(ids, nodesById) {
299
+ return ids.flatMap(id => getAssetRefs(nodesById[id]));
300
+ }
301
+ function getAssetRefs(node) {
302
+ var _a;
303
+ const refs = [];
304
+ Object.values((_a = node === null || node === void 0 ? void 0 : node.components) !== null && _a !== void 0 ? _a : {}).forEach(component => {
305
+ var _a, _b, _c, _d;
306
+ if (!(component === null || component === void 0 ? void 0 : component.type))
307
+ return;
308
+ if (component.type === "Model" && ((_a = component.properties) === null || _a === void 0 ? void 0 : _a.filename)) {
309
+ refs.push(`model:${component.properties.filename}`);
310
+ }
311
+ if (component.type === "Material") {
312
+ if ((_b = component.properties) === null || _b === void 0 ? void 0 : _b.texture)
313
+ refs.push(`texture:${component.properties.texture}`);
314
+ if ((_c = component.properties) === null || _c === void 0 ? void 0 : _c.normalMapTexture)
315
+ refs.push(`texture:${component.properties.normalMapTexture}`);
316
+ }
317
+ if (component.type === "SpotLight" && ((_d = component.properties) === null || _d === void 0 ? void 0 : _d.map)) {
318
+ refs.push(`texture:${component.properties.map}`);
319
+ }
320
+ });
321
+ return refs.sort();
322
+ }
323
+ function addAssetRefs(assetRefCounts, refs) {
324
+ refs.forEach(ref => {
325
+ var _a;
326
+ assetRefCounts[ref] = ((_a = assetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) + 1;
327
+ });
328
+ }
329
+ function removeAssetRefs(assetRefCounts, refs) {
330
+ refs.forEach(ref => {
331
+ var _a;
332
+ const nextCount = ((_a = assetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) - 1;
333
+ if (nextCount > 0) {
334
+ assetRefCounts[ref] = nextCount;
335
+ return;
336
+ }
337
+ delete assetRefCounts[ref];
338
+ });
339
+ }
340
+ function getAssetManifestKey(assetRefCounts) {
341
+ return Object.keys(assetRefCounts).sort().join("|");
342
+ }
343
+ function sameStringArrays(left, right) {
344
+ if (left.length !== right.length)
345
+ return false;
346
+ return left.every((value, index) => value === right[index]);
347
+ }
@@ -0,0 +1,44 @@
1
+ import type { GameObject } from "./types";
2
+ export interface SpawnOptions {
3
+ name?: string;
4
+ parentId?: string;
5
+ select?: boolean;
6
+ }
7
+ export type EntityData = Omit<GameObject, "children">;
8
+ export type PropertyPath = string | Array<string | number>;
9
+ export interface EntityComponent<TProperties = Record<string, any>> {
10
+ readonly key: string;
11
+ readonly type: string;
12
+ get: <TValue = unknown>(path?: PropertyPath) => TValue | undefined;
13
+ set: (path: PropertyPath, value: unknown) => void;
14
+ update: (update: (properties: TProperties) => TProperties) => void;
15
+ }
16
+ export interface Entity {
17
+ readonly id: string;
18
+ set: (data: EntityData) => void;
19
+ update: (update: (node: EntityData) => EntityData) => void;
20
+ getComponent: <TProperties = Record<string, any>>(name: string) => EntityComponent<TProperties> | null;
21
+ }
22
+ export type EntityUpdate = (node: EntityData) => EntityData;
23
+ export type SceneUpdates = Record<string, EntityUpdate>;
24
+ export interface Scene {
25
+ readonly rootId: string;
26
+ find: (id: string) => Entity | null;
27
+ get: (id: string) => Entity;
28
+ update: {
29
+ (id: string, update: EntityUpdate): void;
30
+ (updates: SceneUpdates): void;
31
+ };
32
+ add: (node: GameObject, options?: SpawnOptions) => Entity;
33
+ remove: (id: string) => void;
34
+ }
35
+ interface SceneAdapter {
36
+ getRootId: () => string;
37
+ getNode: (id: string) => EntityData | null;
38
+ updateNode: (id: string, update: (node: EntityData) => EntityData) => void;
39
+ updateNodes: (updates: Record<string, (node: EntityData) => EntityData>) => void;
40
+ addNode: (node: GameObject, options?: SpawnOptions) => string;
41
+ removeNode: (id: string) => void;
42
+ }
43
+ export declare function createScene(adapter: SceneAdapter): Scene;
44
+ export {};
@@ -0,0 +1,161 @@
1
+ function missingNode(id) {
2
+ throw new Error(`Scene node not found: ${id}`);
3
+ }
4
+ function normalizePath(path) {
5
+ if (path === undefined) {
6
+ return [];
7
+ }
8
+ if (Array.isArray(path)) {
9
+ return path;
10
+ }
11
+ return path.split(".").filter(Boolean);
12
+ }
13
+ function getValueAtPath(value, path) {
14
+ const segments = normalizePath(path);
15
+ let current = value;
16
+ for (const segment of segments) {
17
+ if (current == null || typeof current !== "object") {
18
+ return undefined;
19
+ }
20
+ current = current[segment];
21
+ }
22
+ return current;
23
+ }
24
+ function setValueAtPath(value, path, nextValue) {
25
+ const segments = normalizePath(path);
26
+ if (segments.length === 0) {
27
+ return nextValue;
28
+ }
29
+ const cloneBranch = (current, index) => {
30
+ const segment = segments[index];
31
+ const source = current == null ? undefined : current;
32
+ const container = Array.isArray(source)
33
+ ? [...source]
34
+ : source && typeof source === "object"
35
+ ? Object.assign({}, source) : typeof segment === "number"
36
+ ? []
37
+ : {};
38
+ if (index === segments.length - 1) {
39
+ container[segment] = nextValue;
40
+ return container;
41
+ }
42
+ const child = source && typeof source === "object"
43
+ ? source[segment]
44
+ : undefined;
45
+ container[segment] = cloneBranch(child, index + 1);
46
+ return container;
47
+ };
48
+ return cloneBranch(value, 0);
49
+ }
50
+ function findComponentEntry(node, name) {
51
+ if (!node.components) {
52
+ return null;
53
+ }
54
+ const direct = node.components[name];
55
+ if (direct) {
56
+ return [name, direct];
57
+ }
58
+ const normalizedName = name.toLowerCase();
59
+ for (const [key, component] of Object.entries(node.components)) {
60
+ if (!component) {
61
+ continue;
62
+ }
63
+ if (key.toLowerCase() === normalizedName || component.type.toLowerCase() === normalizedName) {
64
+ return [key, component];
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ export function createScene(adapter) {
70
+ const findNode = (id) => adapter.getNode(id) ? createNode(id) : null;
71
+ const getNode = (id) => { var _a; return (_a = findNode(id)) !== null && _a !== void 0 ? _a : missingNode(id); };
72
+ function createComponent(entityId, componentKey, componentType) {
73
+ return {
74
+ key: componentKey,
75
+ type: componentType,
76
+ get(path) {
77
+ var _a;
78
+ const node = adapter.getNode(entityId);
79
+ const component = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
80
+ if (!component) {
81
+ return undefined;
82
+ }
83
+ return getValueAtPath(component.properties, path);
84
+ },
85
+ set(path, value) {
86
+ adapter.updateNode(entityId, node => {
87
+ var _a;
88
+ const component = (_a = node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
89
+ if (!component) {
90
+ return node;
91
+ }
92
+ return Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { [componentKey]: Object.assign(Object.assign({}, component), { properties: setValueAtPath(component.properties, path, value) }) }) });
93
+ });
94
+ },
95
+ update(update) {
96
+ adapter.updateNode(entityId, node => {
97
+ var _a;
98
+ const component = (_a = node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
99
+ if (!component) {
100
+ return node;
101
+ }
102
+ const nextProperties = update(component.properties);
103
+ if (nextProperties === component.properties) {
104
+ return node;
105
+ }
106
+ return Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { [componentKey]: Object.assign(Object.assign({}, component), { properties: nextProperties }) }) });
107
+ });
108
+ },
109
+ };
110
+ }
111
+ function createNode(id) {
112
+ return {
113
+ id,
114
+ set(data) {
115
+ adapter.updateNode(id, () => data);
116
+ },
117
+ update(update) {
118
+ adapter.updateNode(id, update);
119
+ },
120
+ getComponent(name) {
121
+ const node = adapter.getNode(id);
122
+ if (!node) {
123
+ return null;
124
+ }
125
+ const entry = findComponentEntry(node, name);
126
+ if (!entry) {
127
+ return null;
128
+ }
129
+ const [componentKey, component] = entry;
130
+ return createComponent(id, componentKey, component.type);
131
+ },
132
+ };
133
+ }
134
+ function update(idOrUpdates, mutate) {
135
+ if (typeof idOrUpdates === "string") {
136
+ if (!mutate) {
137
+ return;
138
+ }
139
+ adapter.updateNode(idOrUpdates, mutate);
140
+ return;
141
+ }
142
+ if (Object.keys(idOrUpdates).length === 0) {
143
+ return;
144
+ }
145
+ adapter.updateNodes(idOrUpdates);
146
+ }
147
+ return {
148
+ get rootId() {
149
+ return adapter.getRootId();
150
+ },
151
+ find: findNode,
152
+ get: getNode,
153
+ update,
154
+ add(node, options) {
155
+ return createNode(adapter.addNode(node, options));
156
+ },
157
+ remove(id) {
158
+ adapter.removeNode(id);
159
+ },
160
+ };
161
+ }
@@ -897,6 +897,8 @@ export declare const inspector: {
897
897
  maxHeight: string;
898
898
  overflowY: "auto";
899
899
  overflowX: "hidden";
900
+ scrollbarWidth: "thin";
901
+ scrollbarColor: string;
900
902
  boxSizing: "border-box";
901
903
  display: string;
902
904
  flexDirection: "column";
@@ -1813,7 +1815,6 @@ export declare const toolbar: {
1813
1815
  cursor: string;
1814
1816
  };
1815
1817
  };
1816
- export declare const scrollbarCSS: string;
1817
1818
  export declare const componentCard: {
1818
1819
  container: React.CSSProperties;
1819
1820
  };
@@ -98,6 +98,8 @@ export const inspector = {
98
98
  maxHeight: '80vh',
99
99
  overflowY: 'auto',
100
100
  overflowX: 'hidden',
101
+ scrollbarWidth: 'thin',
102
+ scrollbarColor: `${colors.bgLight} transparent`,
101
103
  boxSizing: 'border-box',
102
104
  display: 'flex',
103
105
  flexDirection: 'column',
@@ -182,18 +184,6 @@ export const toolbar = {
182
184
  cursor: 'not-allowed',
183
185
  },
184
186
  };
185
- // Shared scrollbar CSS (inject via <style> tag since CSS can't be bundled)
186
- export const scrollbarCSS = `
187
- .prefab-scroll::-webkit-scrollbar,
188
- .tree-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
189
- .prefab-scroll::-webkit-scrollbar-track,
190
- .tree-scroll::-webkit-scrollbar-track { background: transparent; }
191
- .prefab-scroll::-webkit-scrollbar-thumb,
192
- .tree-scroll::-webkit-scrollbar-thumb { background: ${colors.border}; border-radius: 3px; }
193
- .prefab-scroll::-webkit-scrollbar-thumb:hover,
194
- .tree-scroll::-webkit-scrollbar-thumb:hover { background: #555; }
195
- .prefab-scroll { scrollbar-width: thin; scrollbar-color: ${colors.border} transparent; }
196
- `;
197
187
  // Reusable component card style for inspector sections
198
188
  export const componentCard = {
199
189
  container: {