react-three-game 0.0.93 → 0.0.94

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.
@@ -9,8 +9,8 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
- import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
+ import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
14
14
  import { Euler, Matrix4, } from "three";
15
15
  import { useStore } from "zustand";
16
16
  import { useClickValid } from "./useClickValid";
@@ -20,8 +20,8 @@ import { builtinComponents } from "./components";
20
20
  import { loadModel, loadSound, loadTexture } from "../dragdrop";
21
21
  import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties, useInstanceCheck } from "./InstanceProvider";
22
22
  import { composeTransform, decompose } from "./utils";
23
- import { createPrefabStore, PrefabStoreProvider, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
24
- import { AssetRuntimeContext, CurrentNodeScope } from "./assetRuntime";
23
+ import { createPrefabStore, PrefabStoreProvider, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
24
+ import { AssetRuntimeContext, NodeScope } from "./assetRuntime";
25
25
  import { gameEvents } from "./GameEvents";
26
26
  import { sound as soundManager } from "../../helpers/SoundManager";
27
27
  builtinComponents.forEach(registerComponent);
@@ -50,8 +50,20 @@ function getNodeMetadataProps(node) {
50
50
  userData: Object.assign(Object.assign({ prefabNodeId: node.id }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(node)),
51
51
  };
52
52
  }
53
- export const PrefabRootInternal = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onEditNodeClick, basePath = "" }, ref) => {
54
- var _a;
53
+ export var PrefabEditorMode;
54
+ (function (PrefabEditorMode) {
55
+ PrefabEditorMode["Edit"] = "edit";
56
+ PrefabEditorMode["Play"] = "play";
57
+ })(PrefabEditorMode || (PrefabEditorMode = {}));
58
+ export const SceneContext = createContext(null);
59
+ export function useScene() {
60
+ const scene = useContext(SceneContext);
61
+ if (!scene) {
62
+ throw new Error("useScene must be used within a PrefabRoot or PrefabEditor scene provider");
63
+ }
64
+ return scene;
65
+ }
66
+ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onEditNodeClick, basePath = "", children }, ref) => {
55
67
  const [models, setModels] = useState({});
56
68
  const [textures, setTextures] = useState({});
57
69
  const [sounds, setSounds] = useState({});
@@ -64,31 +76,33 @@ export const PrefabRootInternal = forwardRef(({ editMode, data, store, selectedI
64
76
  const failedSounds = useRef(new Set());
65
77
  const objectRefs = useRef({});
66
78
  const nodeHandles = useRef(new Map());
67
- const parentStore = useOptionalPrefabStoreApi();
68
79
  const [ownedStore] = useState(() => {
80
+ if (store)
81
+ return null;
69
82
  if (data)
70
83
  return createPrefabStore(data);
71
- if (store || parentStore)
72
- return null;
73
84
  throw new Error("PrefabRoot requires either a `data` or `store` prop");
74
85
  });
75
- const resolvedStore = (_a = store !== null && store !== void 0 ? store : parentStore) !== null && _a !== void 0 ? _a : ownedStore;
86
+ const resolvedStore = store !== null && store !== void 0 ? store : ownedStore;
76
87
  const usesOwnedStore = resolvedStore === ownedStore;
77
- const shouldProvideStoreContext = !parentStore || parentStore !== resolvedStore;
78
88
  const rootId = useStore(resolvedStore, state => state.rootId);
79
89
  const assetManifestKey = useStore(resolvedStore, state => state.assetManifestKey);
80
90
  const availableModels = useMemo(() => (Object.assign(Object.assign({}, models), injectedModels)), [models, injectedModels]);
81
91
  const availableTextures = useMemo(() => (Object.assign(Object.assign({}, textures), injectedTextures)), [textures, injectedTextures]);
82
92
  const availableSounds = useMemo(() => (Object.assign(Object.assign({}, sounds), injectedSounds)), [sounds, injectedSounds]);
83
- const getNodeObject = useCallback((id) => {
93
+ const getObject = useCallback((id) => {
84
94
  var _a;
85
95
  return (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : null;
86
96
  }, []);
87
- const getNodeHandle = useCallback((id, kind) => {
97
+ const getHandle = useCallback((id, kind) => {
88
98
  var _a, _b;
89
99
  return (_b = (_a = nodeHandles.current.get(id)) === null || _a === void 0 ? void 0 : _a.get(kind)) !== null && _b !== void 0 ? _b : null;
90
100
  }, []);
91
- const registerNodeHandle = useCallback((id, kind, handle) => {
101
+ const getNode = useCallback((nodeId) => {
102
+ var _a;
103
+ return (_a = resolvedStore.getState().nodesById[nodeId]) !== null && _a !== void 0 ? _a : null;
104
+ }, [resolvedStore]);
105
+ const registerHandle = useCallback((id, kind, handle) => {
92
106
  const current = nodeHandles.current.get(id);
93
107
  if (handle == null) {
94
108
  if (!current)
@@ -105,20 +119,33 @@ export const PrefabRootInternal = forwardRef(({ editMode, data, store, selectedI
105
119
  }
106
120
  nodeHandles.current.set(id, new Map([[kind, handle]]));
107
121
  }, []);
108
- useImperativeHandle(ref, () => ({
122
+ const sceneValue = useMemo(() => ({
109
123
  get root() {
110
124
  var _a;
111
125
  return (_a = objectRefs.current[rootId]) !== null && _a !== void 0 ? _a : null;
112
126
  },
113
- getNodeObject,
114
- getNodeHandle,
127
+ mode: editMode ? PrefabEditorMode.Edit : PrefabEditorMode.Play,
128
+ get: getNode,
129
+ getObject,
130
+ getHandle,
131
+ add: (node, parentId) => {
132
+ const state = resolvedStore.getState();
133
+ state.addChild(parentId !== null && parentId !== void 0 ? parentId : state.rootId, node);
134
+ return node;
135
+ },
136
+ update: (id, fn) => resolvedStore.getState().updateNode(id, fn),
137
+ remove: (id) => resolvedStore.getState().deleteNode(id),
138
+ duplicate: (id) => resolvedStore.getState().duplicateNode(id),
139
+ move: (draggedId, targetId, position) => resolvedStore.getState().moveNode(draggedId, targetId, position),
140
+ replace: (prefab) => resolvedStore.getState().replacePrefab(prefab),
115
141
  addModel: (path, model) => setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model }))),
116
142
  addTexture: (path, texture) => setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture }))),
117
143
  addSound: (path, sound) => {
118
144
  soundManager.setBuffer(path, sound);
119
145
  setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
120
146
  },
121
- }), [getNodeHandle, getNodeObject, rootId]);
147
+ }), [editMode, getHandle, getNode, getObject, resolvedStore, rootId]);
148
+ useImperativeHandle(ref, () => sceneValue, [sceneValue]);
122
149
  const registerRef = useCallback((id, obj) => {
123
150
  objectRefs.current[id] = obj;
124
151
  }, []);
@@ -153,31 +180,27 @@ export const PrefabRootInternal = forwardRef(({ editMode, data, store, selectedI
153
180
  return;
154
181
  loading.current.add(file);
155
182
  void loader(resolveAssetPath(basePath, file)).then(result => {
183
+ loading.current.delete(file);
156
184
  if (!result.success) {
157
185
  console.warn(`Failed to load asset: ${file}`, result.error);
158
- loading.current.delete(file);
159
186
  failed.add(file);
160
187
  }
161
188
  });
162
189
  };
163
- modelsToLoad.forEach(file => loadAsset(file, models, injectedModels, failedModels.current, (path) => loadModel(path).then(result => {
164
- const model = result.model;
165
- if (result.success && model) {
166
- setModels(m => (Object.assign(Object.assign({}, m), { [file]: model })));
167
- }
190
+ modelsToLoad.forEach(file => loadAsset(file, models, injectedModels, failedModels.current, path => loadModel(path).then(result => {
191
+ if (result.success && result.model)
192
+ setModels(m => (Object.assign(Object.assign({}, m), { [file]: result.model })));
168
193
  return result;
169
194
  })));
170
- texturesToLoad.forEach(file => loadAsset(file, textures, injectedTextures, failedTextures.current, (path) => loadTexture(path).then(result => {
171
- if (result.success && result.texture) {
195
+ texturesToLoad.forEach(file => loadAsset(file, textures, injectedTextures, failedTextures.current, path => loadTexture(path).then(result => {
196
+ if (result.success && result.texture)
172
197
  setTextures(t => (Object.assign(Object.assign({}, t), { [file]: result.texture })));
173
- }
174
198
  return result;
175
199
  })));
176
- soundsToLoad.forEach(file => loadAsset(file, sounds, injectedSounds, failedSounds.current, (path) => loadSound(path).then(result => {
200
+ soundsToLoad.forEach(file => loadAsset(file, sounds, injectedSounds, failedSounds.current, path => loadSound(path).then(result => {
177
201
  if (result.success && result.sound) {
178
202
  soundManager.setBuffer(file, result.sound);
179
- setSounds(current => (Object.assign(Object.assign({}, current), { [file]: result.sound })));
180
- loading.current.delete(file);
203
+ setSounds(s => (Object.assign(Object.assign({}, s), { [file]: result.sound })));
181
204
  }
182
205
  return result;
183
206
  })));
@@ -185,14 +208,14 @@ export const PrefabRootInternal = forwardRef(({ editMode, data, store, selectedI
185
208
  syncAssets();
186
209
  }, [resolvedStore, assetManifestKey, basePath, injectedModels, injectedSounds, injectedTextures, models, sounds, textures]);
187
210
  const assetRuntime = useMemo(() => ({
188
- registerNodeHandle,
189
- getNodeHandle,
190
- getNodeObject,
211
+ registerHandle,
212
+ getHandle,
213
+ getObject,
191
214
  getModel: (path) => { var _a; return (_a = availableModels[path]) !== null && _a !== void 0 ? _a : null; },
192
215
  getTexture: (path) => { var _a; return (_a = availableTextures[path]) !== null && _a !== void 0 ? _a : null; },
193
216
  getSound: (path) => { var _a; return (_a = availableSounds[path]) !== null && _a !== void 0 ? _a : null; },
194
217
  getAssetRevision: () => `${Object.keys(availableTextures).sort().join('|')}::${Object.keys(availableModels).sort().join('|')}`,
195
- }), [registerNodeHandle, getNodeHandle, getNodeObject, availableModels, availableTextures, availableSounds]);
218
+ }), [registerHandle, getHandle, getObject, availableModels, availableTextures, availableSounds]);
196
219
  const handleNodeClick = useCallback((event, nodeId, fallbackObject) => {
197
220
  const node = resolvedStore.getState().nodesById[nodeId];
198
221
  if (!node)
@@ -201,14 +224,10 @@ export const PrefabRootInternal = forwardRef(({ editMode, data, store, selectedI
201
224
  emitNodePointerEvent(clickEventName, event, nodeId, node, fallbackObject);
202
225
  onClick === null || onClick === void 0 ? void 0 : onClick(event, node);
203
226
  }, [onClick, resolvedStore]);
204
- const content = (_jsx(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: registerRef, children: _jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY }) }));
205
- const runtimeContent = _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content });
206
- if (!shouldProvideStoreContext) {
207
- return runtimeContent;
208
- }
227
+ const content = (_jsxs(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: registerRef, children: [_jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY }), children] }));
228
+ const runtimeContent = (_jsx(SceneContext.Provider, { value: sceneValue, children: _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }) }));
209
229
  return _jsx(PrefabStoreProvider, { store: resolvedStore, children: runtimeContent });
210
230
  });
211
- export const PrefabRoot = PrefabRootInternal;
212
231
  function StoreRootNode(props) {
213
232
  const rootId = usePrefabRootId();
214
233
  return _jsx(GameObjectRenderer, Object.assign({}, props, { nodeId: rootId }));
@@ -379,7 +398,7 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick,
379
398
  const inner = renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes);
380
399
  const editAnchor = editMode ? (_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) })) : null;
381
400
  const standardNode = (_jsxs("group", Object.assign({ ref: handleGroupRef }, groupProps, { visible: nodeVisible }, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
382
- return (_jsx(CurrentNodeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: standardNode }));
401
+ return (_jsx(NodeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: standardNode }));
383
402
  }
384
403
  function ChildNodes(_a) {
385
404
  var { childIds, parentMatrix } = _a, props = __rest(_a, ["childIds", "parentMatrix"]);
@@ -1,36 +1,30 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { Object3D, Texture } from "three";
3
3
  export interface AssetRuntime {
4
- registerNodeHandle: (id: string, kind: string, handle: unknown) => void;
5
- getNodeHandle: <T = unknown>(id: string, kind: string) => T | null;
4
+ registerHandle: (id: string, kind: string, handle: unknown) => void;
5
+ getHandle: <T = unknown>(id: string, kind: string) => T | null;
6
6
  getModel: (path: string) => Object3D | null;
7
7
  getTexture: (path: string) => Texture | null;
8
8
  getSound: (path: string) => AudioBuffer | null;
9
9
  getAssetRevision: () => string;
10
- getNodeObject: (id: string) => Object3D | null;
10
+ getObject: (id: string) => Object3D | null;
11
11
  }
12
- export interface AssetRuntimeContextValue extends AssetRuntime {
13
- }
14
- export interface CurrentNodeRuntime {
12
+ export interface NodeApi {
15
13
  nodeId: string;
16
14
  editMode?: boolean;
17
15
  isSelected?: boolean;
18
- getCurrentNodeObject: <T extends Object3D = Object3D>() => T | null;
19
- getCurrentNodeHandle: <T = unknown>(kind: string) => T | null;
16
+ getObject: <T extends Object3D = Object3D>() => T | null;
17
+ getHandle: <T = unknown>(kind: string) => T | null;
20
18
  }
21
19
  export interface LiveRef<T> {
22
20
  readonly current: T | null;
23
21
  }
24
- export type LiveObjectRef<T extends Object3D = Object3D> = LiveRef<T>;
25
- export type LiveHandleRef<T = unknown> = LiveRef<T>;
26
- export type CurrentNodeObjectRef<T extends Object3D = Object3D> = LiveObjectRef<T>;
27
- export type CurrentNodeHandleRef<T = unknown> = LiveHandleRef<T>;
28
- export declare const AssetRuntimeContext: import("react").Context<AssetRuntimeContextValue | null>;
22
+ export declare const AssetRuntimeContext: import("react").Context<AssetRuntime | null>;
29
23
  export declare function useAssetRuntime(): AssetRuntime;
30
- export declare function useCurrentNode(): CurrentNodeRuntime;
31
- export declare function useCurrentNodeObject<T extends Object3D = Object3D>(): LiveRef<T>;
32
- export declare function useCurrentNodeHandle<T = unknown>(kind: string): LiveRef<T>;
33
- export declare function CurrentNodeScope({ nodeId, editMode, isSelected, children, }: {
24
+ export declare function useNode(): NodeApi;
25
+ export declare function useNodeObject<T extends Object3D = Object3D>(): LiveRef<T>;
26
+ export declare function useNodeHandle<T = unknown>(kind: string): LiveRef<T>;
27
+ export declare function NodeScope({ nodeId, editMode, isSelected, children, }: {
34
28
  nodeId: string;
35
29
  editMode?: boolean;
36
30
  isSelected?: boolean;
@@ -1,37 +1,37 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo } from "react";
3
3
  export const AssetRuntimeContext = createContext(null);
4
- const CurrentNodeRuntimeContext = createContext(null);
4
+ const NodeContext = createContext(null);
5
5
  export function useAssetRuntime() {
6
6
  const ctx = useContext(AssetRuntimeContext);
7
7
  if (!ctx)
8
8
  throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
9
9
  return ctx;
10
10
  }
11
- export function useCurrentNode() {
12
- const ctx = useContext(CurrentNodeRuntimeContext);
11
+ export function useNode() {
12
+ const ctx = useContext(NodeContext);
13
13
  if (!ctx)
14
- throw new Error("useCurrentNode must be used inside a component View rendered by <PrefabRoot>");
14
+ throw new Error("useNode must be used inside a component View rendered by <PrefabRoot>");
15
15
  return ctx;
16
16
  }
17
- export function useCurrentNodeObject() {
18
- const { getCurrentNodeObject } = useCurrentNode();
19
- return useMemo(() => ({ get current() { return getCurrentNodeObject(); } }), [getCurrentNodeObject]);
17
+ export function useNodeObject() {
18
+ const { getObject } = useNode();
19
+ return useMemo(() => ({ get current() { return getObject(); } }), [getObject]);
20
20
  }
21
- export function useCurrentNodeHandle(kind) {
22
- const { getCurrentNodeHandle } = useCurrentNode();
23
- return useMemo(() => ({ get current() { return getCurrentNodeHandle(kind); } }), [getCurrentNodeHandle, kind]);
21
+ export function useNodeHandle(kind) {
22
+ const { getHandle } = useNode();
23
+ return useMemo(() => ({ get current() { return getHandle(kind); } }), [getHandle, kind]);
24
24
  }
25
- export function CurrentNodeScope({ nodeId, editMode, isSelected, children, }) {
25
+ export function NodeScope({ nodeId, editMode, isSelected, children, }) {
26
26
  const asset = useContext(AssetRuntimeContext);
27
27
  if (!asset)
28
- throw new Error("CurrentNodeScope must be used inside <PrefabRoot>");
28
+ throw new Error("NodeScope must be used inside <PrefabRoot>");
29
29
  const value = useMemo(() => ({
30
30
  nodeId,
31
31
  editMode,
32
32
  isSelected,
33
- getCurrentNodeObject: () => asset.getNodeObject(nodeId),
34
- getCurrentNodeHandle: (kind) => asset.getNodeHandle(nodeId, kind),
33
+ getObject: () => asset.getObject(nodeId),
34
+ getHandle: (kind) => asset.getHandle(nodeId, kind),
35
35
  }), [asset, editMode, isSelected, nodeId]);
36
- return _jsx(CurrentNodeRuntimeContext.Provider, { value: value, children: children });
36
+ return _jsx(NodeContext.Provider, { value: value, children: children });
37
37
  }
@@ -3,7 +3,7 @@ import { OrthographicCamera, PerspectiveCamera, useHelper } from '@react-three/d
3
3
  import { useRef } from 'react';
4
4
  import { CameraHelper } from 'three';
5
5
  import { useFrame, useThree } from '@react-three/fiber';
6
- import { useCurrentNode } from '../assetRuntime';
6
+ import { useNode } from '../assetRuntime';
7
7
  import { FieldGroup, NumberField, SelectField } from './Input';
8
8
  const CAMERA_PROJECTION_OPTIONS = [
9
9
  { value: 'perspective', label: 'Perspective' },
@@ -25,7 +25,7 @@ function CameraComponentEditor({ component, onUpdate }) {
25
25
  }
26
26
  function CameraComponentView({ properties, children }) {
27
27
  var _a;
28
- const { editMode, isSelected } = useCurrentNode();
28
+ const { editMode, isSelected } = useNode();
29
29
  const { size } = useThree();
30
30
  const merged = Object.assign(Object.assign({}, cameraDefaults), properties);
31
31
  const projection = (_a = merged.projection) !== null && _a !== void 0 ? _a : cameraDefaults.projection;
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
3
3
  import { useRef, useEffect, useState } from "react";
4
4
  import { useFrame } from "@react-three/fiber";
5
5
  import { CameraHelper } from "three";
6
- import { useCurrentNode } from "../assetRuntime";
6
+ import { useNode } from "../assetRuntime";
7
7
  import { BooleanField, ColorField, NumberField, NumberInput, Vector3Input } from "./Input";
8
8
  import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
9
9
  import { colors } from "../styles";
@@ -102,7 +102,7 @@ function DirectionalLightComponentEditor({ component, onUpdate }) {
102
102
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs(LightSection, { title: "Light", children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(Vector3Input, { label: "Target Offset", value: values.targetOffset, onChange: targetOffset => onUpdate({ targetOffset }), snap: 0.5 })] }), _jsxs(LightSection, { title: "Shadow", children: [_jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: false }), values.castShadow ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "shadowAutoUpdate", label: "Auto Update", values: values, onChange: onUpdate, fallback: true }), _jsx(NumberField, { name: "shadowMapSize", label: "Map Size", values: values, onChange: onUpdate, min: 128, step: 128, fallback: 512 }), _jsx(ShadowBiasField, { name: "shadowBias", label: "Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(ShadowBiasField, { name: "shadowNormalBias", label: "Normal Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(NumberField, { name: "shadowCameraNear", label: "Near", values: values, onChange: onUpdate, min: 0.001, step: 0.1, fallback: 0.5 }), _jsx(NumberField, { name: "shadowCameraFar", label: "Far", values: values, onChange: onUpdate, min: 0.1, step: 1, fallback: 500 }), _jsx(ShadowFrustumField, { values: values, onChange: onUpdate })] })) : null] })] }));
103
103
  }
104
104
  function DirectionalLightView({ properties, children }) {
105
- const { editMode, isSelected } = useCurrentNode();
105
+ const { editMode, isSelected } = useNode();
106
106
  const merged = mergeWithDefaults(directionalLightDefaults, properties);
107
107
  const color = merged.color;
108
108
  const intensity = merged.intensity;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { colors, ui } from '../styles';
4
- import { useOptionalPrefabStoreApi } from '../prefabStore';
4
+ import { usePrefabStoreApi } from '../prefabStore';
5
5
  // ============================================================================
6
6
  // Shared Styles (derived from shared color tokens)
7
7
  // ============================================================================
@@ -278,14 +278,10 @@ export function ColorInput({ label, value, onChange }) {
278
278
  export function StringInput({ label, value, onChange, placeholder }) {
279
279
  return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "text", style: styles.input, value: value, onChange: e => onChange(e.target.value), placeholder: placeholder })] }));
280
280
  }
281
- function useOptionalPrefabSnapshot() {
282
- const store = useOptionalPrefabStoreApi();
283
- const [state, setState] = useState(() => { var _a; return (_a = store === null || store === void 0 ? void 0 : store.getState()) !== null && _a !== void 0 ? _a : null; });
281
+ function usePrefabSnapshot() {
282
+ const store = usePrefabStoreApi();
283
+ const [state, setState] = useState(() => store.getState());
284
284
  useEffect(() => {
285
- if (!store) {
286
- setState(null);
287
- return;
288
- }
289
285
  setState(store.getState());
290
286
  return store.subscribe(nextState => setState(nextState));
291
287
  }, [store]);
@@ -323,7 +319,7 @@ function SearchSuggestionList({ query, options, onSelect, emptyMessage, }) {
323
319
  }, children: [_jsx("span", { style: { fontSize: 11, fontWeight: 500 }, children: option.label }), option.description ? (_jsx("span", { style: { fontSize: 10, color: colors.textMuted, fontFamily: 'monospace' }, children: option.description })) : null] }, option.value))) })] }));
324
320
  }
325
321
  export function NodeInput({ label, value, onChange, placeholder, includeRoot = true, }) {
326
- const prefabState = useOptionalPrefabSnapshot();
322
+ const prefabState = usePrefabSnapshot();
327
323
  const [query, setQuery] = useState('');
328
324
  const options = useMemo(() => {
329
325
  var _a;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ModelPicker } from '../../assetviewer/page';
3
- import { useContext, useMemo } from 'react';
3
+ import { useMemo } from 'react';
4
4
  import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput, StringField } from './Input';
5
5
  import { useAssetRuntime } from '../assetRuntime';
6
- import { EditorContext } from '../PrefabEditor';
6
+ import { useEditorContext } from '../PrefabEditor';
7
7
  import { getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
8
8
  import { colors, ui } from '../styles';
9
9
  const AXIS_OPTIONS = [
@@ -39,9 +39,7 @@ function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
39
39
  } }));
40
40
  }
41
41
  function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
42
- var _a;
43
- const editorContext = useContext(EditorContext);
44
- const positionSnap = (_a = editorContext === null || editorContext === void 0 ? void 0 : editorContext.positionSnap) !== null && _a !== void 0 ? _a : 0.5;
42
+ const { positionSnap } = useEditorContext();
45
43
  const repeatAxes = getRepeatAxesFromModelProperties(component.properties);
46
44
  return (_jsxs(FieldGroup, { children: [_jsx(ModelPicker, { value: component.properties.filename, onChange: (filename) => onUpdate({ filename }), basePath: basePath, pickerKey: node === null || node === void 0 ? void 0 : node.id }), _jsx(BooleanField, { name: "instanced", label: "Instanced", values: component.properties, onChange: onUpdate, fallback: false }), !component.properties.instanced ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "emitClickEvent", label: "Emit Click Event", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitClickEvent ? (_jsx(StringField, { name: "clickEventName", label: "Click Event Name", values: component.properties, onChange: onUpdate, placeholder: "node:click" })) : null] })) : null, component.properties.instanced && (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "repeat", label: "Repeat", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.repeat && (_jsx(RepeatAxisEditor, { axes: repeatAxes, onChange: (nextAxes) => onUpdate({ repeatAxes: nextAxes }), positionSnap: positionSnap }))] }))] }));
47
45
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useRef } from 'react';
3
3
  import { useHelper } from '@react-three/drei';
4
4
  import { PointLightHelper } from 'three';
5
- import { useCurrentNode } from '../assetRuntime';
5
+ import { useNode } from '../assetRuntime';
6
6
  import { BooleanField, ColorField, NumberField } from './Input';
7
7
  import { LightSection, ShadowBiasField, mergeWithDefaults } from './lightUtils';
8
8
  const pointLightDefaults = {
@@ -23,7 +23,7 @@ function PointLightComponentEditor({ component, onUpdate }) {
23
23
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs(LightSection, { title: "Light", children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(NumberField, { name: "distance", label: "Distance", values: values, onChange: onUpdate, min: 0, step: 1, fallback: 0 }), _jsx(NumberField, { name: "decay", label: "Decay", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 2 })] }), _jsxs(LightSection, { title: "Shadow", children: [_jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: false }), values.castShadow ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "shadowAutoUpdate", label: "Auto Update", values: values, onChange: onUpdate, fallback: true }), _jsx(NumberField, { name: "shadowMapSize", label: "Map Size", values: values, onChange: onUpdate, min: 128, step: 128, fallback: 512 }), _jsx(ShadowBiasField, { name: "shadowBias", label: "Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(ShadowBiasField, { name: "shadowNormalBias", label: "Normal Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(NumberField, { name: "shadowCameraNear", label: "Near", values: values, onChange: onUpdate, min: 0.001, step: 0.1, fallback: 0.5 }), _jsx(NumberField, { name: "shadowCameraFar", label: "Far", values: values, onChange: onUpdate, min: 0.1, step: 1, fallback: 500 })] })) : null] })] }));
24
24
  }
25
25
  function PointLightView({ properties, children }) {
26
- const { editMode, isSelected } = useCurrentNode();
26
+ const { editMode, isSelected } = useNode();
27
27
  const merged = mergeWithDefaults(pointLightDefaults, properties);
28
28
  const color = merged.color;
29
29
  const intensity = merged.intensity;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useRef } from 'react';
3
3
  import { useThree } from '@react-three/fiber';
4
4
  import { SoundPicker } from '../../assetviewer/page';
5
- import { useAssetRuntime, useCurrentNode } from '../assetRuntime';
5
+ import { useAssetRuntime, useNode } from '../assetRuntime';
6
6
  import { gameEvents } from '../GameEvents';
7
7
  import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
8
8
  import { colors, ui } from '../styles';
@@ -124,7 +124,7 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
124
124
  }
125
125
  function SoundComponentView({ properties, children }) {
126
126
  const { getSound } = useAssetRuntime();
127
- const { editMode, nodeId } = useCurrentNode();
127
+ const { editMode, nodeId } = useNode();
128
128
  const { camera } = useThree();
129
129
  const { eventName, autoplay = false, positional = false, refDistance = 1, maxDistance = 24, rolloffFactor = 1, distanceModel = 'inverse' } = properties;
130
130
  const sequenceIndexRef = useRef(0);
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
3
3
  import { useRef, useEffect } from "react";
4
4
  import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
5
5
  import { SpotLightHelper } from "three";
6
- import { useAssetRuntime, useCurrentNode } from "../assetRuntime";
6
+ import { useAssetRuntime, useNode } from "../assetRuntime";
7
7
  import { useFrame } from "@react-three/fiber";
8
8
  import { TexturePicker } from "../../assetviewer/page";
9
9
  import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
@@ -31,7 +31,7 @@ function SpotLightComponentEditor({ component, onUpdate, basePath = "" }) {
31
31
  function SpotLightView({ properties, children }) {
32
32
  var _a;
33
33
  const { getTexture } = useAssetRuntime();
34
- const { editMode, isSelected } = useCurrentNode();
34
+ const { editMode, isSelected } = useNode();
35
35
  const merged = mergeWithDefaults(spotLightDefaults, properties);
36
36
  const color = merged.color;
37
37
  const intensity = merged.intensity;
@@ -8,7 +8,6 @@ export interface PrefabState {
8
8
  nodesById: Record<string, PrefabNodeRecord>;
9
9
  childIdsById: Record<string, string[]>;
10
10
  parentIdById: Record<string, string | null>;
11
- revision: number;
12
11
  assetManifestKey: string;
13
12
  assetRefCounts: PrefabAssetRefCounts;
14
13
  }
@@ -25,7 +24,7 @@ export declare function createEmptyNode(name?: string): GameObject;
25
24
  export declare function createEmptyPrefab(): Prefab;
26
25
  export declare function createModelNode(filename: string, name?: string): GameObject;
27
26
  export declare function createImageNode(texturePath: string, name?: string): GameObject;
28
- export declare function normalizePrefab(prefab: Prefab, revision?: number): PrefabState;
27
+ export declare function normalizePrefab(prefab: Prefab): PrefabState;
29
28
  export declare function createPrefabPatch(state: PrefabState, patch: Partial<PrefabState>, nextAssetRefCounts?: PrefabAssetRefCounts): Partial<PrefabState>;
30
29
  export declare function denormalizePrefab(state: Pick<PrefabState, 'prefabId' | 'prefabName' | 'rootId' | 'nodesById' | 'childIdsById'>): Prefab;
31
30
  export declare function collectSubtreeIds(id: string, childIdsById: Record<string, string[]>): string[];
@@ -132,7 +132,7 @@ export function createImageNode(texturePath, name) {
132
132
  },
133
133
  });
134
134
  }
135
- export function normalizePrefab(prefab, revision = 0) {
135
+ export function normalizePrefab(prefab) {
136
136
  const nodesById = {};
137
137
  const childIdsById = {};
138
138
  const parentIdById = {};
@@ -145,14 +145,13 @@ export function normalizePrefab(prefab, revision = 0) {
145
145
  nodesById,
146
146
  childIdsById,
147
147
  parentIdById,
148
- revision,
149
148
  assetManifestKey: getAssetManifestKey(assetRefCounts),
150
149
  assetRefCounts,
151
150
  };
152
151
  }
153
152
  export function createPrefabPatch(state, patch, nextAssetRefCounts = state.assetRefCounts) {
154
153
  const assetRefsChanged = nextAssetRefCounts !== state.assetRefCounts;
155
- return Object.assign(Object.assign(Object.assign({}, patch), { revision: state.revision + 1 }), (assetRefsChanged ? {
154
+ return Object.assign(Object.assign({}, patch), (assetRefsChanged ? {
156
155
  assetRefCounts: nextAssetRefCounts,
157
156
  assetManifestKey: getAssetManifestKey(nextAssetRefCounts),
158
157
  } : null));
@@ -5,14 +5,9 @@ import { denormalizePrefab, PrefabState, PrefabNodeRecord } from "./prefab";
5
5
  export interface PrefabStoreState extends PrefabState {
6
6
  replacePrefab: (prefab: Prefab) => void;
7
7
  updateNode: (id: string, update: (node: PrefabNodeRecord) => PrefabNodeRecord) => void;
8
- updateNodes: (updates: Array<{
9
- id: string;
10
- update: (node: PrefabNodeRecord) => PrefabNodeRecord;
11
- }>) => void;
12
8
  addChild: (parentId: string, node: GameObject) => void;
13
9
  deleteNode: (id: string) => void;
14
10
  duplicateNode: (id: string) => string | null;
15
- toggleNodeFlag: (id: string, key: "disabled" | "locked") => void;
16
11
  moveNode: (draggedId: string, targetId: string, position: "before" | "inside") => void;
17
12
  }
18
13
  export type PrefabStoreApi = StoreApi<PrefabStoreState>;
@@ -21,7 +16,6 @@ export declare function PrefabStoreProvider({ store, children, }: {
21
16
  children: ReactNode;
22
17
  }): import("react").FunctionComponentElement<import("react").ProviderProps<PrefabStoreApi | null>>;
23
18
  export declare function usePrefabStoreApi(): PrefabStoreApi;
24
- export declare function useOptionalPrefabStoreApi(): PrefabStoreApi | null;
25
19
  export declare function usePrefabStore<T>(selector: (state: PrefabStoreState) => T): T;
26
20
  export declare function usePrefabRootId(): string;
27
21
  export declare function usePrefabNode(nodeId: string | null | undefined): PrefabNodeRecord | null;
@@ -15,9 +15,6 @@ export function usePrefabStoreApi() {
15
15
  }
16
16
  return store;
17
17
  }
18
- export function useOptionalPrefabStoreApi() {
19
- return useContext(PrefabStoreContext);
20
- }
21
18
  export function usePrefabStore(selector) {
22
19
  return useStore(usePrefabStoreApi(), selector);
23
20
  }
@@ -32,7 +29,7 @@ export function usePrefabChildIds(nodeId) {
32
29
  }
33
30
  export function createPrefabStore(prefab) {
34
31
  return createStore()(subscribeWithSelector((set, get) => (Object.assign(Object.assign({}, normalizePrefab(prefab)), { replacePrefab: (nextPrefab) => {
35
- set(normalizePrefab(nextPrefab, get().revision + 1));
32
+ set(normalizePrefab(nextPrefab));
36
33
  }, updateNode: (id, update) => {
37
34
  const state = get();
38
35
  const node = state.nodesById[id];
@@ -45,26 +42,6 @@ export function createPrefabStore(prefab) {
45
42
  set(createPrefabPatch(state, {
46
43
  nodesById: Object.assign(Object.assign({}, state.nodesById), { [id]: nextNode }),
47
44
  }, nextAssetRefCounts));
48
- }, updateNodes: (updates) => {
49
- if (updates.length === 0)
50
- return;
51
- const state = get();
52
- let nextNodesById = null;
53
- let nextAssetRefCounts = state.assetRefCounts;
54
- for (const { id, update } of updates) {
55
- const currentNode = (nextNodesById !== null && nextNodesById !== void 0 ? nextNodesById : state.nodesById)[id];
56
- if (!currentNode)
57
- continue;
58
- const nextNode = update(currentNode);
59
- if (nextNode === currentNode)
60
- continue;
61
- nextNodesById !== null && nextNodesById !== void 0 ? nextNodesById : (nextNodesById = Object.assign({}, state.nodesById));
62
- nextNodesById[id] = nextNode;
63
- nextAssetRefCounts = updateAssetRefsForNodeChange(nextAssetRefCounts, currentNode, nextNode);
64
- }
65
- if (!nextNodesById)
66
- return;
67
- set(createPrefabPatch(state, { nodesById: nextNodesById }, nextAssetRefCounts));
68
45
  }, addChild: (parentId, node) => {
69
46
  var _a;
70
47
  const state = get();
@@ -152,15 +129,6 @@ export function createPrefabStore(prefab) {
152
129
  parentIdById: nextParentIdById,
153
130
  }, nextAssetRefCounts));
154
131
  return duplicatedRootId;
155
- }, toggleNodeFlag: (id, key) => {
156
- const state = get();
157
- const node = state.nodesById[id];
158
- if (!node)
159
- return;
160
- const nextNode = Object.assign(Object.assign({}, node), { [key]: !node[key] });
161
- set(createPrefabPatch(state, {
162
- nodesById: Object.assign(Object.assign({}, state.nodesById), { [id]: nextNode }),
163
- }));
164
132
  }, moveNode: (draggedId, targetId, position) => {
165
133
  var _a, _b, _c;
166
134
  const state = get();