react-three-game 0.0.90 → 0.0.92

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.
@@ -12,6 +12,34 @@ const styles = {
12
12
  textLight: { color: '#f9fafb' },
13
13
  iconLarge: { fontSize: 20 }
14
14
  };
15
+ const assetViewerColors = {
16
+ panelBg: '#111827',
17
+ controlBg: '#1f2937',
18
+ text: '#f9fafb',
19
+ border: 'rgba(255,255,255,0.12)',
20
+ accentBorder: 'rgba(34, 211, 238, 0.3)',
21
+ };
22
+ const assetPickerPopupBaseStyle = {
23
+ background: assetViewerColors.panelBg,
24
+ border: `1px solid ${assetViewerColors.border}`,
25
+ borderRadius: 0,
26
+ boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
27
+ };
28
+ const assetPickerButtonBaseStyle = {
29
+ backgroundColor: assetViewerColors.controlBg,
30
+ color: 'inherit',
31
+ fontSize: 10,
32
+ cursor: 'pointer',
33
+ border: `1px solid ${assetViewerColors.border}`,
34
+ borderRadius: 0,
35
+ };
36
+ const assetPickerWideButtonStyle = Object.assign(Object.assign({}, assetPickerButtonBaseStyle), { width: '100%', padding: '6px 8px' });
37
+ const assetPickerSmallButtonStyle = Object.assign(Object.assign({}, assetPickerButtonBaseStyle), { padding: '4px 8px' });
38
+ const assetPickerEmptyPreviewStyle = {
39
+ backgroundColor: assetViewerColors.controlBg,
40
+ border: `1px dashed ${assetViewerColors.border}`,
41
+ borderRadius: 0,
42
+ };
15
43
  function getItemsInPath(files, currentPath) {
16
44
  // Remove the leading category folder (e.g., /textures/, /models/, /sounds/)
17
45
  const filesWithoutCategory = files.map(file => {
@@ -39,8 +67,8 @@ function FolderTile({ name, onClick }) {
39
67
  return (_jsxs("div", { onClick: onClick, style: {
40
68
  maxWidth: 60,
41
69
  aspectRatio: '1 / 1',
42
- backgroundColor: '#1f2937', /* gray-800 */
43
- color: '#f9fafb',
70
+ backgroundColor: assetViewerColors.controlBg,
71
+ color: assetViewerColors.text,
44
72
  cursor: 'pointer',
45
73
  display: 'flex',
46
74
  flexDirection: 'column',
@@ -73,7 +101,7 @@ function AssetListViewer({ files, selected, onSelect, renderCard }) {
73
101
  const pathParts = currentPath.split('/').filter(Boolean);
74
102
  pathParts.pop();
75
103
  setCurrentPath(pathParts.join('/'));
76
- }, style: { marginBottom: 4, padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 12, cursor: 'pointer', border: 'none' }, children: "\u2190 Back" })), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }, children: [folders.map((folder) => (_jsx(FolderTile, { name: folder, onClick: () => setCurrentPath(currentPath ? `${currentPath}/${folder}` : folder) }, folder))), filesInCurrentPath.map((file) => (_jsx("div", { children: renderCard(file, onSelect) }, file)))] })] }));
104
+ }, style: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginBottom: 4, fontSize: 12, border: 'none' }), children: "\u2190 Back" })), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }, children: [folders.map((folder) => (_jsx(FolderTile, { name: folder, onClick: () => setCurrentPath(currentPath ? `${currentPath}/${folder}` : folder) }, folder))), filesInCurrentPath.map((file) => (_jsx("div", { children: renderCard(file, onSelect) }, file)))] })] }));
77
105
  }
78
106
  export function TextureListViewer({ files, selected, onSelect, basePath = "" }) {
79
107
  return (_jsxs("div", { style: { position: 'relative', width: '100%', height: '100%' }, children: [_jsx("div", { style: { width: '100%', height: '100%', overflowY: 'auto', overflowX: 'hidden', paddingRight: 4 }, children: _jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(TextureCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }) }), _jsx(SharedCanvas, {})] }));
@@ -172,8 +200,8 @@ function AssetPicker({ value, onChange, basePath, manifestFolder, preview, rende
172
200
  const fitsLeft = preferredLeft >= 8;
173
201
  const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
174
202
  const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
175
- setResolvedPopupStyle(Object.assign({ position: 'fixed', left,
176
- top, padding: 12, width: PICKER_POPUP_WIDTH, height: PICKER_POPUP_HEIGHT, overflow: 'hidden', zIndex: 1000, boxShadow: '0 4px 16px rgba(0,0,0,0.6)', background: '#111827', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 6 }, popupStyle));
203
+ setResolvedPopupStyle(Object.assign(Object.assign({ position: 'fixed', left,
204
+ top, padding: 12, width: PICKER_POPUP_WIDTH, height: PICKER_POPUP_HEIGHT, overflow: 'hidden', zIndex: 1000 }, assetPickerPopupBaseStyle), popupStyle));
177
205
  };
178
206
  updatePosition();
179
207
  window.addEventListener('resize', updatePosition);
@@ -194,23 +222,23 @@ function AssetPicker({ value, onChange, basePath, manifestFolder, preview, rende
194
222
  }) }), document.body)] }));
195
223
  }
196
224
  export function TexturePicker({ value, onChange, basePath = "" }) {
197
- return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "textures", rootStyle: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, changeButtonStyle: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3, marginTop: 4 }, clearButtonStyle: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3, marginTop: 4, marginLeft: 4 }, preview: _jsx(SingleTextureViewer, { file: value, basePath: basePath }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(TextureListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
225
+ return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "textures", rootStyle: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, changeButtonStyle: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginTop: 4 }), clearButtonStyle: Object.assign(Object.assign({}, assetPickerSmallButtonStyle), { marginTop: 4, marginLeft: 4 }), preview: _jsx(SingleTextureViewer, { file: value, basePath: basePath }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(TextureListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
198
226
  }
199
227
  export function ModelPicker({ value, onChange, basePath = "", pickerKey }) {
200
- return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "models", rootStyle: { maxHeight: 160, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)' }, clearButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)' }, popupStyle: { background: 'rgba(0,0,0,0.9)', border: '1px solid rgba(34, 211, 238, 0.3)' }, preview: _jsx("div", { style: { flex: '0 0 auto' }, children: _jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(ModelListViewer, { files: files, selected: selectedValue ? `/${selectedValue}` : undefined, onSelect: (file) => onSelect(file.startsWith('/') ? file.slice(1) : file), basePath: currentBasePath }, pickerKey)) }));
228
+ return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "models", rootStyle: { maxHeight: 160, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: Object.assign(Object.assign({}, assetPickerWideButtonStyle), { border: `1px solid ${assetViewerColors.accentBorder}` }), clearButtonStyle: Object.assign(Object.assign({}, assetPickerWideButtonStyle), { border: `1px solid ${assetViewerColors.accentBorder}` }), popupStyle: { background: 'rgba(0,0,0,0.9)', border: `1px solid ${assetViewerColors.accentBorder}` }, preview: _jsx("div", { style: { flex: '0 0 auto' }, children: _jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(ModelListViewer, { files: files, selected: selectedValue ? `/${selectedValue}` : undefined, onSelect: (file) => onSelect(file.startsWith('/') ? file.slice(1) : file), basePath: currentBasePath }, pickerKey)) }));
201
229
  }
202
230
  export function SoundPicker({ value, onChange, basePath = "" }) {
203
- return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "sound", rootStyle: { maxHeight: 76, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3 }, clearButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3 }, preview: _jsx("div", { style: { flex: '0 0 auto', minWidth: 84 }, children: value ? _jsx(SingleSoundViewer, { file: value, basePath: basePath }) : _jsx("div", { style: { width: 84, height: 60, backgroundColor: '#1f2937', border: '1px dashed rgba(255,255,255,0.12)', borderRadius: 4 } }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(SoundListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
231
+ return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "sound", rootStyle: { maxHeight: 76, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: assetPickerWideButtonStyle, clearButtonStyle: assetPickerWideButtonStyle, preview: _jsx("div", { style: { flex: '0 0 auto', minWidth: 84 }, children: value ? _jsx(SingleSoundViewer, { file: value, basePath: basePath }) : _jsx("div", { style: Object.assign({ width: 84, height: 60 }, assetPickerEmptyPreviewStyle) }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(SoundListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
204
232
  }
205
233
  // Single Asset Viewer Components - display only one selected asset
206
234
  export function SingleTextureViewer({ file, basePath = "" }) {
207
235
  if (!file)
208
- return _jsx("div", { style: { width: 60, aspectRatio: '1 / 1', backgroundColor: '#1f2937', border: '1px dashed rgba(255,255,255,0.12)' } });
236
+ return _jsx("div", { style: Object.assign({ width: 60, aspectRatio: '1 / 1' }, assetPickerEmptyPreviewStyle) });
209
237
  return (_jsxs(_Fragment, { children: [_jsx(TextureCard, { file: file, basePath: basePath, onSelect: () => { } }), _jsx(SharedCanvas, {})] }));
210
238
  }
211
239
  export function SingleModelViewer({ file, basePath = "" }) {
212
240
  if (!file)
213
- return _jsx("div", { style: { width: 112, aspectRatio: '1 / 1', backgroundColor: '#1f2937', border: '1px dashed rgba(255,255,255,0.12)' } });
241
+ return _jsx("div", { style: Object.assign({ width: 112, aspectRatio: '1 / 1' }, assetPickerEmptyPreviewStyle) });
214
242
  return (_jsxs(_Fragment, { children: [_jsx(ModelCard, { file: file, basePath: basePath, onSelect: () => { }, size: 112 }), _jsx(SharedCanvas, {})] }));
215
243
  }
216
244
  export function SingleSoundViewer({ file, basePath = "" }) {
@@ -43,18 +43,18 @@ function NodeInspector({ node, updateNode, deleteNode, basePath }) {
43
43
  const available = allKeys.filter(k => !hasComponent(node, k));
44
44
  const [preferredAddType, setAddType] = useState(available[0] || "");
45
45
  const addType = available.includes(preferredAddType) ? preferredAddType : (available[0] || "");
46
- return _jsxs("div", { style: inspector.content, children: [_jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: "flex", marginBottom: 8, alignItems: 'center', gap: 8 }, children: [_jsx("div", { style: { fontSize: 10, color: colors.textDim, wordBreak: 'break-all', border: `1px solid ${colors.border}`, padding: '2px 6px', borderRadius: 3, flex: 1, fontFamily: 'monospace' }, children: node.id }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), base.btnDanger), title: "Delete Node", onClick: deleteNode, children: "\u274C" })] }), _jsx("input", { style: base.input, value: (_a = node.name) !== null && _a !== void 0 ? _a : "", placeholder: 'Node name', onChange: e => updateNode(n => (Object.assign(Object.assign({}, n), { name: e.target.value }))) })] }), _jsxs("div", { style: base.section, children: [_jsx("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }, children: _jsx("div", { style: base.label, children: "Components" }) }), node.components && Object.entries(node.components).map(([key, comp]) => {
46
+ return _jsxs("div", { style: inspector.content, children: [_jsxs("div", { style: base.section, children: [_jsxs("div", { style: { display: "flex", marginBottom: 4, alignItems: 'center', gap: 4 }, children: [_jsx("div", { style: { fontSize: 10, color: colors.textDim, wordBreak: 'break-all', background: colors.bgLight, padding: '2px 4px', flex: 1, fontFamily: 'monospace', minHeight: 18, boxSizing: 'border-box' }, children: node.id }), _jsx("button", { style: Object.assign(Object.assign(Object.assign({}, base.btn), base.btnDanger), { minWidth: 22, padding: '2px 4px' }), title: "Delete Node", onClick: deleteNode, children: "\u2715" })] }), _jsx("input", { style: base.input, value: (_a = node.name) !== null && _a !== void 0 ? _a : "", placeholder: 'Node name', onChange: e => updateNode(n => (Object.assign(Object.assign({}, n), { name: e.target.value }))) })] }), _jsxs("div", { style: base.section, children: [_jsx("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: _jsx("div", { style: base.label, children: "Components" }) }), node.components && Object.entries(node.components).map(([key, comp]) => {
47
47
  if (!comp)
48
48
  return null;
49
49
  const def = ALL_COMPONENTS[comp.type];
50
50
  if (!def)
51
51
  return _jsxs("div", { style: { color: colors.danger, fontSize: 11 }, children: ["Unknown: ", comp.type] }, key);
52
- return (_jsxs("div", { style: componentCard.container, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 500 }, children: key }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px' }), title: "Remove Component", onClick: () => updateNode(n => {
52
+ return (_jsxs("div", { style: componentCard.container, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 3 }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 500 }, children: key }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 4px', minWidth: 20 }), title: "Remove Component", onClick: () => updateNode(n => {
53
53
  var _a;
54
54
  const _b = (_a = n.components) !== null && _a !== void 0 ? _a : {}, _c = key, _ = _b[_c], rest = __rest(_b, [typeof _c === "symbol" ? _c : _c + ""]);
55
55
  return Object.assign(Object.assign({}, n), { components: rest });
56
56
  }), children: "\u2715" })] }), def.Editor && (_jsx(def.Editor, { component: comp, node: node, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath }))] }, key));
57
- })] }), available.length > 0 && (_jsx("div", { children: _jsxs("div", { style: base.row, children: [_jsx("select", { style: Object.assign(Object.assign({}, base.input), { flex: 1 }), value: addType, onChange: e => setAddType(e.target.value), children: available.map(k => _jsx("option", { value: k, children: k }, k)) }), _jsx("button", { style: base.btn, disabled: !addType, onClick: () => {
57
+ })] }), available.length > 0 && (_jsx("div", { children: _jsxs("div", { style: base.row, children: [_jsx("select", { style: Object.assign(Object.assign({}, base.input), { flex: 1, background: colors.bgInput, border: `1px solid ${colors.border}`, minHeight: 22 }), value: addType, onChange: e => setAddType(e.target.value), children: available.map(k => _jsx("option", { value: k, children: k }, k)) }), _jsx("button", { style: base.btn, disabled: !addType, onClick: () => {
58
58
  if (!addType)
59
59
  return;
60
60
  const def = ALL_COMPONENTS[addType];
@@ -8,9 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
11
- import { MapControls, TransformControls } from "@react-three/drei";
11
+ import { MapControls, TransformControls, useHelper } from "@react-three/drei";
12
12
  import GameCanvas from "../../shared/GameCanvas";
13
- import { useCallback, useEffect, useReducer, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
13
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
14
+ import { BoxHelper } from "three";
14
15
  import { findComponentEntry } from "./types";
15
16
  import PrefabRoot from "./PrefabRoot";
16
17
  import { Physics } from "@react-three/rapier";
@@ -31,6 +32,12 @@ function isObjectAttachedToRoot(root, object) {
31
32
  }
32
33
  return false;
33
34
  }
35
+ function SelectionHelper({ object }) {
36
+ const objectRef = useRef(null);
37
+ objectRef.current = object;
38
+ useHelper(object ? objectRef : null, BoxHelper, "cyan");
39
+ return null;
40
+ }
34
41
  export var PrefabEditorMode;
35
42
  (function (PrefabEditorMode) {
36
43
  PrefabEditorMode["Edit"] = "edit";
@@ -52,6 +59,7 @@ const DEFAULT_PREFAB = {
52
59
  root: createNode('Root', {}, { id: 'root' })
53
60
  };
54
61
  const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode: initialMode = PrefabEditorMode.Edit, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
62
+ var _a, _b;
55
63
  const [mode, setMode] = useState(initialMode);
56
64
  const [selectedId, setSelectedId] = useState(null);
57
65
  const [transformMode, setTransformMode] = useState("translate");
@@ -64,21 +72,17 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
64
72
  const [historyIndex, setHistoryIndex] = useState(0);
65
73
  const changeOriginRef = useRef(null);
66
74
  const historyIndexRef = useRef(0);
67
- const [, bumpSelectedObjectVersion] = useReducer((value) => value + 1, 0);
68
75
  const prefabRootRef = useRef(null);
69
76
  const canvasRef = useRef(null);
70
77
  const controlsRef = useRef(null);
78
+ const transformControlsRef = useRef(null);
79
+ const transformProxyRef = useRef(null);
71
80
  const onChangeRef = useRef(onChange);
72
81
  const isEditMode = mode === PrefabEditorMode.Edit;
73
82
  const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
74
83
  const getRootObject = useCallback(() => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : null; }, []);
75
84
  const getObject = useCallback((nodeId) => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(nodeId)) !== null && _b !== void 0 ? _b : null; }, []);
76
85
  const getRigidBody = useCallback((nodeId) => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getRigidBody(nodeId)) !== null && _b !== void 0 ? _b : null; }, []);
77
- const handleObjectRefChange = useCallback((nodeId) => {
78
- if (nodeId !== selectedId)
79
- return;
80
- bumpSelectedObjectVersion();
81
- }, [selectedId]);
82
86
  onChangeRef.current = onChange;
83
87
  const setSelection = useCallback((nodeId) => {
84
88
  const nextNode = nodeId ? prefabStore.getState().nodesById[nodeId] : null;
@@ -104,7 +108,9 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
104
108
  updateMode(initialMode);
105
109
  }, [initialMode, updateMode]);
106
110
  const loadPrefab = useCallback((prefab, options) => {
111
+ var _a;
107
112
  changeOriginRef.current = (options === null || options === void 0 ? void 0 : options.notifyChange) === false ? "replace-silent" : "replace";
113
+ (_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
108
114
  prefabStore.getState().replacePrefab(prefab);
109
115
  if (options === null || options === void 0 ? void 0 : options.resetHistory) {
110
116
  setSelectedId(null);
@@ -171,10 +177,23 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
171
177
  });
172
178
  return () => unsubscribe();
173
179
  }, [prefabStore, selectedId]);
180
+ const selectedNode = selectedId ? (_a = prefabStore.getState().nodesById[selectedId]) !== null && _a !== void 0 ? _a : null : null;
174
181
  const selectedObject = selectedId ? getObject(selectedId) : null;
175
- const transformObject = isObjectAttachedToRoot(getRootObject(), selectedObject)
176
- ? selectedObject
182
+ const selectedHasPhysics = Object.values((_b = selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.components) !== null && _b !== void 0 ? _b : {}).some(component => (component === null || component === void 0 ? void 0 : component.type) === "Physics");
183
+ const transformObject = isEditMode && (selectedHasPhysics ? transformProxyRef.current : selectedObject)
184
+ && isObjectAttachedToRoot(getRootObject(), selectedObject)
185
+ ? (selectedHasPhysics ? transformProxyRef.current : selectedObject)
177
186
  : null;
187
+ useLayoutEffect(() => {
188
+ if (!isEditMode || !selectedHasPhysics || !selectedObject || !transformProxyRef.current) {
189
+ return;
190
+ }
191
+ selectedObject.updateMatrixWorld(true);
192
+ transformProxyRef.current.matrixAutoUpdate = true;
193
+ selectedObject.matrixWorld.decompose(transformProxyRef.current.position, transformProxyRef.current.quaternion, transformProxyRef.current.scale);
194
+ transformProxyRef.current.updateMatrix();
195
+ transformProxyRef.current.updateMatrixWorld(true);
196
+ }, [isEditMode, selectedHasPhysics, selectedId, selectedObject]);
178
197
  const addNode = useCallback((node, options) => {
179
198
  var _a;
180
199
  const { addChild, rootId } = prefabStore.getState();
@@ -202,7 +221,9 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
202
221
  return node;
203
222
  }, [addNode]);
204
223
  const applyHistory = (index) => {
224
+ var _a;
205
225
  changeOriginRef.current = "history";
226
+ (_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
206
227
  prefabStore.getState().replacePrefab(history[index]);
207
228
  historyIndexRef.current = index;
208
229
  setHistoryIndex(index);
@@ -274,7 +295,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
274
295
  const handleTransformChange = () => {
275
296
  if (!selectedId)
276
297
  return;
277
- const object = getObject(selectedId);
298
+ const object = selectedHasPhysics ? transformProxyRef.current : getObject(selectedId);
278
299
  if (!object)
279
300
  return;
280
301
  const parentWorld = computeParentWorldMatrix(prefabStore.getState(), selectedId);
@@ -341,7 +362,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
341
362
  addModel,
342
363
  addTexture
343
364
  }), [addModel, addNode, addTexture, clearSelection, getObject, getPrefab, getRigidBody, getRootObject, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, prefabStore]);
344
- const content = (_jsxs(_Fragment, { children: [isEditMode ? _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }) : null, _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, onObjectRefChange: handleObjectRefChange, basePath: basePath }), children] }));
365
+ const content = (_jsxs(_Fragment, { children: [isEditMode ? _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }) : null, _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, basePath: basePath }), children] }));
345
366
  const handleCanvasCreated = useCallback((state) => {
346
367
  var _a;
347
368
  canvasRef.current = state.gl.domElement;
@@ -370,7 +391,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
370
391
  }
371
392
  (_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
372
393
  }
373
- : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [physics ? (_jsx(Physics, { colliders: false, debug: isEditMode, paused: isEditMode, children: content })) : content, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { object: transformObject, mode: transformMode, space: "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) });
394
+ : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [physics ? (_jsx(Physics, { colliders: false, debug: isEditMode, paused: isEditMode, children: content })) : content, _jsx("group", { ref: transformProxyRef, visible: false }), isEditMode ? _jsx(SelectionHelper, { object: transformObject }) : null, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { ref: transformControlsRef, object: transformObject, mode: transformMode, space: "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) });
374
395
  });
375
396
  PrefabEditor.displayName = "PrefabEditor";
376
397
  export default PrefabEditor;
@@ -18,7 +18,6 @@ export interface PrefabRootProps {
18
18
  selectedId?: string | null;
19
19
  onSelect?: (id: string | null) => void;
20
20
  onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
21
- onObjectRefChange?: (id: string, object: Object3D | null) => void;
22
21
  basePath?: string;
23
22
  }
24
23
  export declare const PrefabRoot: import("react").ForwardRefExoticComponent<PrefabRootProps & import("react").RefAttributes<PrefabRootRef>>;
@@ -10,9 +10,8 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
- import { useHelper } from "@react-three/drei";
14
13
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
15
- import { BoxHelper, Euler, Matrix4, } from "three";
14
+ import { Euler, Matrix4, } from "three";
16
15
  import { useStore } from "zustand";
17
16
  import { useClickValid } from "./useClickValid";
18
17
  import { findComponent, getNodeUserData } from "./types";
@@ -69,7 +68,7 @@ function getNodeMetadataProps(node) {
69
68
  userData: Object.assign(Object.assign({ prefabNodeId: node.id }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(node)),
70
69
  };
71
70
  }
72
- export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onObjectRefChange, basePath = "" }, ref) => {
71
+ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, basePath = "" }, ref) => {
73
72
  var _a;
74
73
  const [models, setModels] = useState({});
75
74
  const [textures, setTextures] = useState({});
@@ -120,8 +119,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
120
119
  }), [getObject]);
121
120
  const registerRef = useCallback((id, obj) => {
122
121
  objectRefs.current[id] = obj;
123
- onObjectRefChange === null || onObjectRefChange === void 0 ? void 0 : onObjectRefChange(id, obj);
124
- }, [onObjectRefChange]);
122
+ }, []);
125
123
  const registerRigidBodyRef = useCallback((id, rb) => {
126
124
  rigidBodyRefs.current.set(id, rb);
127
125
  }, []);
@@ -293,18 +291,10 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
293
291
  const clickEventName = getNodeClickEventName(gameObject);
294
292
  const metadataProps = getNodeMetadataProps(gameObject);
295
293
  const groupRef = useRef(null);
296
- const helperRef = useRef(null);
297
294
  const handleGroupRef = useCallback((object) => {
298
295
  groupRef.current = object;
299
296
  registerRef(nodeId, object);
300
297
  }, [nodeId, registerRef]);
301
- const handleHelperRef = useCallback((object) => {
302
- helperRef.current = object;
303
- }, []);
304
- const handleEditGroupRef = useCallback((object) => {
305
- handleGroupRef(object);
306
- handleHelperRef(object);
307
- }, [handleGroupRef, handleHelperRef]);
308
298
  const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
309
299
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
310
300
  onClick === null || onClick === void 0 ? void 0 : onClick(event, gameObject);
@@ -318,7 +308,6 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
318
308
  },
319
309
  }
320
310
  : undefined;
321
- useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
322
311
  const world = parentMatrix.clone().multiply(compose(gameObject));
323
312
  const physics = findComponent(gameObject, "Physics");
324
313
  const ready = isNodeReady(gameObject, loadedModels);
@@ -336,8 +325,10 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
336
325
  const renderCtx = { loadedModels, editMode, registerRef };
337
326
  const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode });
338
327
  const inner = renderCompositionNode(gameObject, renderCtx, primaryClickHandlers, childNodes);
339
- const physicsInner = editMode ? _jsx("group", { visible: false, children: inner }) : inner;
340
- return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("group", Object.assign({ ref: handleEditGroupRef }, groupProps, editClickHandlers, { children: [_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }), inner] })), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: physicsInner }), physicsKey)) : null] })) : hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: _jsx("group", Object.assign({ ref: handleGroupRef }, metadataProps, { children: inner })) }), physicsKey)) : (_jsx("group", Object.assign({ ref: handleGroupRef }, groupProps, { children: inner }))) }));
328
+ const editAnchor = editMode ? (_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) })) : null;
329
+ const standardNode = (_jsxs("group", Object.assign({ ref: handleGroupRef }, groupProps, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
330
+ const physicsNode = hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: _jsxs("group", Object.assign({ ref: handleGroupRef }, metadataProps, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })) }), physicsKey)) : null;
331
+ return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: physicsNode !== null && physicsNode !== void 0 ? physicsNode : standardNode }));
341
332
  }
342
333
  function isRendererHandledComponent(componentType) {
343
334
  return componentType === "Transform"
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { BooleanField, FieldGroup, StringField } from "./Input";
3
+ import { base, ui } from "../styles";
3
4
  const DEFAULT_TRIANGLE_POSITIONS = [
4
5
  0, 0, 0,
5
6
  1, 0, 0,
@@ -37,7 +38,7 @@ function getIndexArray(indices) {
37
38
  return maxIndex > 65535 ? new Uint32Array(indices) : new Uint16Array(indices);
38
39
  }
39
40
  function BufferArrayField({ label, value, fallback, onChange, rows = 4, }) {
40
- return (_jsxs("label", { style: { display: 'grid', gap: 4 }, children: [_jsx("span", { style: { fontSize: '10px', color: '#888', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 500 }, children: label }), _jsx("textarea", { rows: rows, spellCheck: false, defaultValue: toAttributeText(value, fallback), onBlur: (event) => {
41
+ return (_jsxs("label", { style: { display: 'grid', gap: 4 }, children: [_jsx("span", { style: Object.assign(Object.assign({}, base.label), { textTransform: 'uppercase', letterSpacing: '0.05em' }), children: label }), _jsx("textarea", { rows: rows, spellCheck: false, defaultValue: toAttributeText(value, fallback), onBlur: (event) => {
41
42
  try {
42
43
  onChange(parseArrayInput(event.target.value));
43
44
  event.target.setCustomValidity('');
@@ -46,19 +47,7 @@ function BufferArrayField({ label, value, fallback, onChange, rows = 4, }) {
46
47
  event.target.setCustomValidity('Expected a JSON array of numbers');
47
48
  event.target.reportValidity();
48
49
  }
49
- }, style: {
50
- width: '100%',
51
- backgroundColor: '#171717',
52
- border: '1px solid #333',
53
- padding: '6px 8px',
54
- fontSize: '11px',
55
- color: '#eee',
56
- fontFamily: 'monospace',
57
- outline: 'none',
58
- borderRadius: 3,
59
- resize: 'vertical',
60
- boxSizing: 'border-box',
61
- } })] }));
50
+ }, style: Object.assign(Object.assign({}, ui.monoTextInput), { width: '100%', minHeight: rows * 18, padding: '4px 6px', outline: 'none', resize: 'vertical', boxSizing: 'border-box' }) })] }));
62
51
  }
63
52
  function BufferGeometryComponentEditor({ component, onUpdate, }) {
64
53
  var _a;
@@ -1,22 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { colors } from "../styles";
3
+ import { colors, ui } from "../styles";
4
4
  const RESERVED_USER_DATA_KEYS = new Set([
5
5
  'prefabNodeId',
6
6
  'prefabNodeName',
7
7
  ]);
8
- const inputStyle = {
9
- width: '100%',
10
- backgroundColor: colors.bgInput,
11
- border: `1px solid ${colors.border}`,
12
- padding: '6px 8px',
13
- fontSize: '11px',
14
- color: colors.text,
15
- fontFamily: 'monospace',
16
- outline: 'none',
17
- borderRadius: 3,
18
- boxSizing: 'border-box',
19
- };
8
+ const inputStyle = Object.assign(Object.assign({}, ui.monoTextInput), { width: '100%', padding: '4px 6px', fontSize: '11px', outline: 'none', boxSizing: 'border-box' });
20
9
  function isRecord(value) {
21
10
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
22
11
  }
@@ -43,7 +43,7 @@ const frustumInputStyle = {
43
43
  const centerLockButtonStyle = {
44
44
  width: 34,
45
45
  height: 34,
46
- borderRadius: 999,
46
+ borderRadius: 0,
47
47
  border: `1px solid ${colors.border}`,
48
48
  background: colors.bgInput,
49
49
  color: colors.textMuted,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
- import { colors } from '../styles';
3
+ import { colors, ui } from '../styles';
4
4
  import { useOptionalPrefabStoreApi } from '../prefabStore';
5
5
  // ============================================================================
6
6
  // Shared Styles (derived from shared color tokens)
@@ -10,13 +10,13 @@ const styles = {
10
10
  width: '80px',
11
11
  backgroundColor: colors.bgInput,
12
12
  border: `1px solid ${colors.border}`,
13
- padding: '3px 6px',
13
+ padding: '2px 4px',
14
14
  fontSize: '11px',
15
15
  color: colors.text,
16
16
  fontFamily: 'monospace',
17
17
  outline: 'none',
18
18
  textAlign: 'right',
19
- borderRadius: 3,
19
+ borderRadius: 0,
20
20
  },
21
21
  label: {
22
22
  display: 'block',
@@ -229,9 +229,9 @@ export function Vector3Input({ label, value, onChange, snap, labelExtra }) {
229
229
  gap: 4,
230
230
  backgroundColor: colors.bgInput,
231
231
  border: `1px solid ${colors.border}`,
232
- borderRadius: 3,
233
- padding: '4px 6px',
234
- minHeight: 28,
232
+ borderRadius: 0,
233
+ padding: '2px 4px',
234
+ minHeight: 22,
235
235
  cursor: 'ew-resize',
236
236
  }, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: [_jsx("span", { style: {
237
237
  fontSize: 11,
@@ -265,13 +265,13 @@ export function Vector3Input({ label, value, onChange, snap, labelExtra }) {
265
265
  // ============================================================================
266
266
  export function ColorInput({ label, value, onChange }) {
267
267
  return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 4, justifyContent: 'space-between' }, children: [_jsx("input", { type: "color", style: {
268
- height: 32,
268
+ height: 22,
269
269
  width: 48,
270
270
  backgroundColor: colors.bgInput,
271
271
  border: `1px solid ${colors.border}`,
272
- borderRadius: 3,
272
+ borderRadius: 0,
273
273
  cursor: 'pointer',
274
- padding: 2,
274
+ padding: 1,
275
275
  flexShrink: 0,
276
276
  }, value: value, onChange: e => onChange(e.target.value) }), _jsx("input", { type: "text", style: Object.assign({}, styles.input), value: value, onChange: e => onChange(e.target.value) })] })] }));
277
277
  }
@@ -304,20 +304,20 @@ function SearchSuggestionList({ query, options, onSelect, emptyMessage, }) {
304
304
  gap: 4,
305
305
  maxHeight: 160,
306
306
  overflowY: 'auto',
307
- border: `1px solid ${colors.border}`,
308
- borderRadius: 3,
307
+ border: 'none',
308
+ borderRadius: 0,
309
309
  background: colors.bgSurface,
310
- padding: 4,
310
+ padding: 2,
311
311
  }, children: filtered.length === 0 ? (_jsx("div", { style: { fontSize: 11, color: colors.textMuted, padding: '4px 6px' }, children: emptyMessage })) : filtered.map(option => (_jsxs("button", { type: "button", onClick: () => onSelect(option.value), style: {
312
312
  display: 'flex',
313
313
  flexDirection: 'column',
314
314
  alignItems: 'flex-start',
315
315
  gap: 2,
316
- border: `1px solid ${colors.border}`,
317
- borderRadius: 3,
318
- background: colors.bgInput,
316
+ border: 'none',
317
+ borderRadius: 0,
318
+ background: colors.bgSurface,
319
319
  color: colors.text,
320
- padding: '6px 8px',
320
+ padding: '4px 6px',
321
321
  cursor: 'pointer',
322
322
  textAlign: 'left',
323
323
  }, 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))) })] }));
@@ -383,19 +383,7 @@ export function ListEditor({ label, items, renderItem, onAdd, addOptions = [], e
383
383
  setSelectedAddValue((_b = (_a = addOptions[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '');
384
384
  }
385
385
  }, [addOptions, hasAddSelector, selectedAddValue]);
386
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [hasAddSelector ? (_jsx("div", { style: { minWidth: 140 }, children: _jsx(SelectInput, { value: resolvedAddValue, onChange: setSelectedAddValue, options: canAdd ? addOptions : [{ value: '', label: 'All items added' }] }) })) : null, _jsx("button", { type: "button", onClick: () => onAdd(resolvedAddValue), disabled: !canAddItem, style: {
387
- width: 22,
388
- height: 22,
389
- borderRadius: 3,
390
- border: `1px solid ${canAddItem ? colors.accentBorder : colors.border}`,
391
- background: canAddItem ? colors.accentBg : colors.bgSurface,
392
- color: canAddItem ? colors.accent : colors.textMuted,
393
- cursor: canAddItem ? 'pointer' : 'not-allowed',
394
- fontSize: 14,
395
- lineHeight: 1,
396
- padding: 0,
397
- flexShrink: 0,
398
- }, title: canAddItem ? addButtonTitle : addDisabledTitle, children: "+" })] })] }), items.length === 0 ? (_jsx("div", { style: { fontSize: 11, color: colors.textMuted }, children: emptyMessage })) : null, items.map(renderItem)] }));
386
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [hasAddSelector ? (_jsx("div", { style: { minWidth: 140 }, children: _jsx(SelectInput, { value: resolvedAddValue, onChange: setSelectedAddValue, options: canAdd ? addOptions : [{ value: '', label: 'All items added' }] }) })) : null, _jsx("button", { type: "button", onClick: () => onAdd(resolvedAddValue), disabled: !canAddItem, style: Object.assign(Object.assign({}, ui.compactActionButton), { width: 22, minWidth: 22, height: 22, border: `1px solid ${canAddItem ? colors.accentBorder : colors.border}`, background: canAddItem ? colors.accentBg : colors.bgSurface, color: canAddItem ? colors.accent : colors.textMuted, cursor: canAddItem ? 'pointer' : 'not-allowed', fontSize: 14, lineHeight: 1 }), title: canAddItem ? addButtonTitle : addDisabledTitle, children: "+" })] })] }), items.length === 0 ? (_jsx("div", { style: { fontSize: 11, color: colors.textMuted }, children: emptyMessage })) : null, items.map(renderItem)] }));
399
387
  }
400
388
  export function NumberField({ name, label, values, onChange, fallback = 0, step, min, max, style, }) {
401
389
  var _a;
@@ -5,7 +5,7 @@ import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput,
5
5
  import { useAssetRuntime } from '../assetRuntime';
6
6
  import { EditorContext } from '../PrefabEditor';
7
7
  import { getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
8
- import { colors } from '../styles';
8
+ import { colors, ui } from '../styles';
9
9
  const AXIS_OPTIONS = [
10
10
  { value: 'x', label: 'X' },
11
11
  { value: 'y', label: 'Y' },
@@ -35,25 +35,7 @@ function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
35
35
  return (_jsx(ListEditor, { label: "Repeat Axes", items: axes, onAdd: addAxis, addOptions: availableAxisOptions, canAdd: availableAxisOptions.length > 0, emptyMessage: "No repeat axes added.", addButtonTitle: "Add repeat axis", addDisabledTitle: "All axes already in use", renderItem: (axisConfig, index) => {
36
36
  const usedByOthers = new Set(axes.filter((_, axisIndex) => axisIndex !== index).map(axis => axis.axis));
37
37
  const axisOptions = AXIS_OPTIONS.filter(option => option.value === axisConfig.axis || !usedByOthers.has(option.value));
38
- return (_jsxs("div", { style: {
39
- display: 'flex',
40
- flexDirection: 'column',
41
- gap: 6,
42
- padding: 8,
43
- border: `1px solid ${colors.border}`,
44
- borderRadius: 4,
45
- background: colors.bgSurface,
46
- }, children: [_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'end' }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SelectInput, { label: "Axis", value: axisConfig.axis, onChange: (axis) => updateAxis(index, { axis: axis }), options: axisOptions }) }), _jsx("button", { type: "button", onClick: () => removeAxis(index), style: {
47
- height: 24,
48
- width: 28,
49
- borderRadius: 3,
50
- border: `1px solid ${colors.border}`,
51
- background: colors.bgInput,
52
- color: colors.text,
53
- cursor: 'pointer',
54
- padding: 0,
55
- flexShrink: 0,
56
- }, title: "Remove repeat axis", children: "\u00D7" })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Count" }), _jsx(NumberInput, { value: axisConfig.count, onChange: (count) => updateAxis(index, { count: Math.max(1, Math.floor(count)) }), step: 1, min: 1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { children: [_jsx(Label, { children: "Offset" }), _jsx(NumberInput, { value: axisConfig.offset, onChange: (offset) => updateAxis(index, { offset: quantize(offset, positionSnap) }), step: positionSnap > 0 ? positionSnap : 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] })] }, `${axisConfig.axis}-${index}`));
38
+ return (_jsxs("div", { style: Object.assign(Object.assign({}, ui.secondaryPanel), { display: 'flex', flexDirection: 'column', gap: 6 }), children: [_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'end' }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SelectInput, { label: "Axis", value: axisConfig.axis, onChange: (axis) => updateAxis(index, { axis: axis }), options: axisOptions }) }), _jsx("button", { type: "button", onClick: () => removeAxis(index), style: Object.assign(Object.assign({}, ui.compactActionButton), { height: 24, background: colors.bgInput }), title: "Remove repeat axis", children: "\u00D7" })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Count" }), _jsx(NumberInput, { value: axisConfig.count, onChange: (count) => updateAxis(index, { count: Math.max(1, Math.floor(count)) }), step: 1, min: 1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { children: [_jsx(Label, { children: "Offset" }), _jsx(NumberInput, { value: axisConfig.offset, onChange: (offset) => updateAxis(index, { offset: quantize(offset, positionSnap) }), step: positionSnap > 0 ? positionSnap : 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] })] }, `${axisConfig.axis}-${index}`));
57
39
  } }));
58
40
  }
59
41
  function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
13
  import { BallCollider, CapsuleCollider, CuboidCollider, RigidBody, useRapier } from "@react-three/rapier";
14
- import { useCallback, useEffect, useRef } from 'react';
14
+ import { useCallback, useEffect, useRef, useState } from 'react';
15
15
  import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
16
16
  import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
17
17
  import { usePrefabNode, usePrefabStore } from "../prefabStore";
@@ -27,6 +27,7 @@ const colliderSizeFallback = [1, 1, 1];
27
27
  const colliderRadiusFallback = 0.5;
28
28
  const capsuleRadiusFallback = 0.35;
29
29
  const capsuleHalfHeightFallback = 0.45;
30
+ const EDIT_MODE_DEBUG_REFRESH_THROTTLE_MS = 120;
30
31
  function isManualColliderShape(value) {
31
32
  return value === 'cuboid' || value === 'ball' || value === 'capsule';
32
33
  }
@@ -90,10 +91,11 @@ function LockedAxisField({ label, name, values, onChange, }) {
90
91
  const isLocked = !enabledAxes[index];
91
92
  return (_jsx("button", { type: "button", onClick: () => toggleAxisLock(index), style: {
92
93
  flex: 1,
94
+ minHeight: 22,
93
95
  backgroundColor: isLocked ? colors.dangerBg : colors.bgInput,
94
96
  border: `1px solid ${isLocked ? colors.dangerBorder : colors.border}`,
95
- borderRadius: 3,
96
- padding: '6px 8px',
97
+ borderRadius: 0,
98
+ padding: '2px 6px',
97
99
  color: isLocked ? colors.danger : colors.textMuted,
98
100
  fontSize: '11px',
99
101
  fontFamily: 'monospace',
@@ -145,11 +147,12 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
145
147
  ? 'capsule'
146
148
  : resolvedManualColliderShape;
147
149
  const rigidBodyRef = useRef(null);
150
+ const [editRefreshVersion, setEditRefreshVersion] = useState(0);
151
+ const lastEditRefreshAtRef = useRef(0);
148
152
  const linearVelocityKey = linearVelocity.join(',');
149
153
  const angularVelocityKey = angularVelocity.join(',');
150
- const rbKey = editMode
151
- ? `${type || 'dynamic'}_${colliderType}_${resolvedManualColliderShape}_${colliderSize.join(',')}_${colliderRadius}_${capsuleRadius}_${capsuleHalfHeight}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
152
- : `${type || 'dynamic'}_${colliderType}_${resolvedManualColliderShape}_${colliderSize.join(',')}_${colliderRadius}_${capsuleRadius}_${capsuleHalfHeight}`;
154
+ const transformSignature = `${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}_${scale === null || scale === void 0 ? void 0 : scale.join(',')}`;
155
+ const rbKey = `${type || 'dynamic'}_${colliderType}_${resolvedManualColliderShape}_${colliderSize.join(',')}_${colliderRadius}_${capsuleRadius}_${capsuleHalfHeight}_${editMode ? editRefreshVersion : 0}`;
153
156
  const handleRigidBodyRef = useCallback((rigidBody) => {
154
157
  rigidBodyRef.current = rigidBody;
155
158
  if (!nodeId)
@@ -178,6 +181,23 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
178
181
  }
179
182
  }
180
183
  }, [activeCollisionTypes, rapier, type, colliders]);
184
+ useEffect(() => {
185
+ if (!editMode) {
186
+ return;
187
+ }
188
+ const now = Date.now();
189
+ const delay = Math.max(0, EDIT_MODE_DEBUG_REFRESH_THROTTLE_MS - (now - lastEditRefreshAtRef.current));
190
+ if (delay === 0) {
191
+ lastEditRefreshAtRef.current = now;
192
+ setEditRefreshVersion(version => version + 1);
193
+ return;
194
+ }
195
+ const timeoutId = setTimeout(() => {
196
+ lastEditRefreshAtRef.current = Date.now();
197
+ setEditRefreshVersion(version => version + 1);
198
+ }, delay);
199
+ return () => clearTimeout(timeoutId);
200
+ }, [editMode, transformSignature]);
181
201
  // Seed authored velocities when the body instance changes or the authored values change.
182
202
  useEffect(() => {
183
203
  if (!rigidBodyRef.current)
@@ -243,14 +263,15 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
243
263
  sensor,
244
264
  enabledTranslations,
245
265
  enabledRotations, name: nodeName, userData: Object.assign({ entityId: nodeId }, userData), onIntersectionEnter: emitSensorEnterEvent ? handleIntersectionEnter : undefined, onIntersectionExit: emitSensorExitEvent ? handleIntersectionExit : undefined, onCollisionEnter: emitCollisionEnterEvent ? handleCollisionEnter : undefined, onCollisionExit: emitCollisionExitEvent ? handleCollisionExit : undefined }, otherProps);
246
- return (_jsxs(RigidBody, Object.assign({}, rigidBodyProps, { children: [!usesAutomaticColliderSource ? renderManualCollider({
266
+ const rigidBodyContent = (_jsxs(_Fragment, { children: [!usesAutomaticColliderSource ? renderManualCollider({
247
267
  shape: manualColliderShapeToRender,
248
268
  sensor,
249
269
  colliderSize,
250
270
  colliderRadius,
251
271
  capsuleRadius,
252
272
  capsuleHalfHeight,
253
- }) : null, children] }), rbKey));
273
+ }) : null, children] }));
274
+ return (_jsx(RigidBody, Object.assign({}, rigidBodyProps, { children: rigidBodyContent }), rbKey));
254
275
  }
255
276
  const PhysicsComponent = {
256
277
  name: 'Physics',
@@ -5,7 +5,7 @@ import { SoundPicker } from '../../assetviewer/page';
5
5
  import { useAssetRuntime, useEntityRuntime } from '../assetRuntime';
6
6
  import { gameEvents } from '../GameEvents';
7
7
  import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
8
- import { colors } from '../styles';
8
+ import { colors, ui } from '../styles';
9
9
  import { AudioListener } from 'three';
10
10
  const CLIP_MODE_OPTIONS = [
11
11
  { value: 'single', label: 'Single Clip' },
@@ -116,25 +116,7 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
116
116
  type: 'select',
117
117
  options: CLIP_MODE_OPTIONS.map(option => ({ value: option.value, label: option.label })),
118
118
  },
119
- ], values: component.properties, onChange: onUpdate }), _jsx(ListEditor, { label: "Clips", items: clips, onAdd: addClip, emptyMessage: "No clips added.", addButtonTitle: "Add clip", addDisabledTitle: "Add clip", renderItem: (clip, index) => (_jsxs("div", { style: {
120
- display: 'flex',
121
- gap: 6,
122
- alignItems: 'end',
123
- padding: 8,
124
- border: `1px solid ${colors.border}`,
125
- borderRadius: 4,
126
- background: colors.bgSurface,
127
- }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SoundPicker, { value: clip || undefined, onChange: (nextPath) => updateClip(index, nextPath !== null && nextPath !== void 0 ? nextPath : ''), basePath: basePath }) }), _jsx("button", { type: "button", onClick: () => removeClip(index), style: {
128
- height: 24,
129
- width: 28,
130
- borderRadius: 3,
131
- border: `1px solid ${colors.border}`,
132
- background: colors.bgInput,
133
- color: colors.text,
134
- cursor: 'pointer',
135
- padding: 0,
136
- flexShrink: 0,
137
- }, title: "Remove clip", children: "\u00D7" })] }, `${clip}-${index}`)) }), _jsx(BooleanField, { name: "positional", label: "Positional", values: component.properties, onChange: onUpdate, fallback: false }), positional ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "refDistance", label: "Ref Distance", values: component.properties, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "maxDistance", label: "Max Distance", values: component.properties, onChange: onUpdate, fallback: 24, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "rolloffFactor", label: "Rolloff", values: component.properties, onChange: onUpdate, fallback: 1, min: 0, step: 0.1 }), _jsx(SelectField, { name: "distanceModel", label: "Distance Model", values: component.properties, onChange: onUpdate, fallback: "inverse", options: [
119
+ ], values: component.properties, onChange: onUpdate }), _jsx(ListEditor, { label: "Clips", items: clips, onAdd: addClip, emptyMessage: "No clips added.", addButtonTitle: "Add clip", addDisabledTitle: "Add clip", renderItem: (clip, index) => (_jsxs("div", { style: Object.assign(Object.assign({}, ui.secondaryPanel), { display: 'flex', gap: 6, alignItems: 'end' }), children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SoundPicker, { value: clip || undefined, onChange: (nextPath) => updateClip(index, nextPath !== null && nextPath !== void 0 ? nextPath : ''), basePath: basePath }) }), _jsx("button", { type: "button", onClick: () => removeClip(index), style: Object.assign(Object.assign({}, ui.compactActionButton), { height: 24, background: colors.bgInput }), title: "Remove clip", children: "\u00D7" })] }, `${clip}-${index}`)) }), _jsx(BooleanField, { name: "positional", label: "Positional", values: component.properties, onChange: onUpdate, fallback: false }), positional ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "refDistance", label: "Ref Distance", values: component.properties, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "maxDistance", label: "Max Distance", values: component.properties, onChange: onUpdate, fallback: 24, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "rolloffFactor", label: "Rolloff", values: component.properties, onChange: onUpdate, fallback: 1, min: 0, step: 0.1 }), _jsx(SelectField, { name: "distanceModel", label: "Distance Model", values: component.properties, onChange: onUpdate, fallback: "inverse", options: [
138
120
  { value: 'inverse', label: 'Inverse' },
139
121
  { value: 'linear', label: 'Linear' },
140
122
  { value: 'exponential', label: 'Exponential' },
@@ -3,15 +3,16 @@ import { Label, Vector3Input } from "./Input";
3
3
  import { useEditorContext } from "../PrefabEditor";
4
4
  import { colors } from "../styles";
5
5
  const buttonStyle = {
6
- padding: '4px 8px',
6
+ padding: '2px 6px',
7
7
  background: colors.bgSurface,
8
8
  color: colors.text,
9
9
  border: `1px solid ${colors.border}`,
10
- borderRadius: 3,
10
+ borderRadius: 0,
11
11
  cursor: 'pointer',
12
12
  font: 'inherit',
13
13
  fontSize: 11,
14
14
  flex: 1,
15
+ minHeight: 22,
15
16
  };
16
17
  function TransformModeSelector({ transformMode, setTransformMode }) {
17
18
  return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx(Label, { children: "Transform Mode" }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => {
@@ -29,7 +30,7 @@ const snapLockBtnStyle = {
29
30
  background: 'none',
30
31
  border: 'none',
31
32
  cursor: 'pointer',
32
- padding: '0 2px',
33
+ padding: 0,
33
34
  fontSize: 12,
34
35
  lineHeight: 1,
35
36
  color: colors.textMuted,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
- import { colors } from '../styles';
3
+ import { base, colors, ui } from '../styles';
4
4
  import { FieldGroup, FieldRow, NumberInput } from './Input';
5
5
  export function mergeWithDefaults(defaults, properties) {
6
6
  const merged = Object.assign({}, defaults);
@@ -15,15 +15,7 @@ export function mergeWithDefaults(defaults, properties) {
15
15
  return merged;
16
16
  }
17
17
  export function LightSection({ title, children }) {
18
- return (_jsxs("div", { style: {
19
- display: 'flex',
20
- flexDirection: 'column',
21
- gap: 8,
22
- padding: '8px 10px',
23
- border: `1px solid ${colors.border}`,
24
- borderRadius: 6,
25
- background: colors.bgSurface,
26
- }, children: [_jsx("div", { style: {
18
+ return (_jsxs("div", { style: Object.assign(Object.assign({}, ui.secondaryPanel), { display: 'flex', flexDirection: 'column', gap: 8, padding: 6 }), children: [_jsx("div", { style: {
27
19
  fontSize: 10,
28
20
  textTransform: 'uppercase',
29
21
  letterSpacing: '0.08em',
@@ -51,14 +43,5 @@ export function ShadowBiasField({ name, label, values, onChange, fallback = 0, }
51
43
  var _a;
52
44
  const value = (_a = values[name]) !== null && _a !== void 0 ? _a : fallback;
53
45
  const [step, setStep] = useState(() => getBiasStep(value));
54
- return (_jsx(FieldRow, { label: label, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx(NumberInput, { value: value, onChange: nextValue => onChange({ [name]: nextValue }), step: step, min: -0.1, max: 0.1, style: { width: 92 } }), _jsx("select", { value: step.toString(), onChange: event => setStep(Number(event.target.value)), style: {
55
- width: 78,
56
- backgroundColor: colors.bgInput,
57
- border: `1px solid ${colors.border}`,
58
- color: colors.text,
59
- borderRadius: 3,
60
- fontSize: 11,
61
- padding: '3px 6px',
62
- fontFamily: 'monospace',
63
- }, title: "Bias scrub step", children: shadowBiasSteps.map(option => (_jsx("option", { value: option, children: formatBiasStep(option) }, option))) })] }) }));
46
+ return (_jsx(FieldRow, { label: label, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx(NumberInput, { value: value, onChange: nextValue => onChange({ [name]: nextValue }), step: step, min: -0.1, max: 0.1, style: { width: 92 } }), _jsx("select", { value: step.toString(), onChange: event => setStep(Number(event.target.value)), style: Object.assign(Object.assign({}, base.input), { width: 78, fontSize: 11, fontFamily: 'monospace' }), title: "Bias scrub step", children: shadowBiasSteps.map(option => (_jsx("option", { value: option, children: formatBiasStep(option) }, option))) })] }) }));
64
47
  }
@@ -34,6 +34,11 @@ interface ToolbarStyles {
34
34
  interface ComponentCardStyles {
35
35
  container: Style;
36
36
  }
37
+ interface UtilityStyles {
38
+ secondaryPanel: Style;
39
+ compactActionButton: Style;
40
+ monoTextInput: Style;
41
+ }
37
42
  export declare const colors: {
38
43
  bg: string;
39
44
  bgSurface: string;
@@ -64,4 +69,5 @@ export declare const tree: TreeStyles;
64
69
  export declare const menu: MenuStyles;
65
70
  export declare const toolbar: ToolbarStyles;
66
71
  export declare const componentCard: ComponentCardStyles;
72
+ export declare const ui: UtilityStyles;
67
73
  export {};
@@ -1,24 +1,24 @@
1
1
  export const colors = {
2
- bg: '#1e1e1e',
3
- bgSurface: '#252526',
4
- bgLight: '#2d2d2d',
5
- bgHover: '#2a2d2e',
6
- bgInput: '#1a1a1a',
7
- border: '#3c3c3c',
8
- borderLight: '#333333',
9
- borderFaint: '#2a2a2a',
10
- text: '#cccccc',
11
- textMuted: '#999999',
12
- textDim: '#666666',
13
- accent: '#4c9eff',
14
- accentBg: 'rgba(76, 158, 255, 0.12)',
15
- accentBorder: 'rgba(76, 158, 255, 0.4)',
16
- danger: '#f44747',
17
- dangerBg: 'rgba(244, 71, 71, 0.12)',
18
- dangerBorder: 'rgba(244, 71, 71, 0.35)',
2
+ bg: '#f3f3f3',
3
+ bgSurface: '#d7d7d7',
4
+ bgLight: '#fafafa',
5
+ bgHover: '#e6e6e6',
6
+ bgInput: '#f5f5f5',
7
+ border: '#6f6f6f',
8
+ borderLight: '#9a9a9a',
9
+ borderFaint: '#b8b8b8',
10
+ text: '#2f2f2f',
11
+ textMuted: '#5f5f5f',
12
+ textDim: '#7f7f7f',
13
+ accent: '#1e6f89',
14
+ accentBg: '#a9dded',
15
+ accentBorder: '#5e5e5e',
16
+ danger: '#9c3232',
17
+ dangerBg: '#efcaca',
18
+ dangerBorder: '#6f6f6f',
19
19
  };
20
20
  export const fonts = {
21
- family: 'system-ui, -apple-system, sans-serif',
21
+ family: 'Tahoma, Verdana, sans-serif',
22
22
  size: 11,
23
23
  sizeSm: 10,
24
24
  };
@@ -28,44 +28,48 @@ export const base = {
28
28
  background: colors.bg,
29
29
  color: colors.text,
30
30
  border: `1px solid ${colors.border}`,
31
- borderRadius: 4,
32
31
  fontFamily: fonts.family,
33
32
  fontSize: fonts.size,
34
- boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
33
+ borderRadius: 0,
34
+ boxShadow: 'none',
35
35
  },
36
36
  header: {
37
- padding: '7px 10px',
37
+ padding: '3px 6px',
38
38
  display: 'flex',
39
39
  alignItems: 'center',
40
40
  justifyContent: 'space-between',
41
41
  cursor: 'pointer',
42
42
  background: colors.bgLight,
43
- borderBottom: `1px solid ${colors.borderLight}`,
43
+ borderBottom: `1px solid ${colors.border}`,
44
44
  fontSize: fonts.size,
45
- fontWeight: 600,
46
- textTransform: 'uppercase',
47
- letterSpacing: 0.8,
45
+ fontWeight: 400,
48
46
  color: colors.text,
47
+ minHeight: 22,
48
+ boxSizing: 'border-box',
49
49
  },
50
50
  input: {
51
51
  width: '100%',
52
52
  background: colors.bgInput,
53
53
  border: `1px solid ${colors.border}`,
54
- borderRadius: 3,
55
- padding: '5px 8px',
54
+ borderRadius: 0,
55
+ padding: '2px 4px',
56
56
  color: colors.text,
57
57
  fontSize: fonts.size,
58
58
  outline: 'none',
59
+ minHeight: 22,
60
+ boxSizing: 'border-box',
59
61
  },
60
62
  btn: {
61
- background: colors.bgLight,
63
+ background: colors.bgSurface,
62
64
  border: `1px solid ${colors.border}`,
63
- borderRadius: 3,
64
- padding: '4px 8px',
65
+ borderRadius: 0,
66
+ padding: '2px 6px',
65
67
  color: colors.text,
66
68
  fontSize: fonts.size,
67
69
  cursor: 'pointer',
68
70
  outline: 'none',
71
+ minHeight: 22,
72
+ boxSizing: 'border-box',
69
73
  },
70
74
  btnDanger: {
71
75
  background: colors.dangerBg,
@@ -75,25 +79,22 @@ export const base = {
75
79
  label: {
76
80
  fontSize: fonts.sizeSm,
77
81
  color: colors.textMuted,
78
- marginBottom: 4,
79
- textTransform: 'uppercase',
80
- letterSpacing: 0.5,
81
- fontWeight: 500,
82
+ marginBottom: 2,
83
+ fontWeight: 400,
82
84
  },
83
85
  row: {
84
86
  display: 'flex',
85
- gap: 6,
87
+ gap: 4,
86
88
  },
87
89
  section: {
88
- paddingBottom: 8,
89
- borderBottom: `1px solid ${colors.borderLight}`,
90
+ paddingBottom: 4,
90
91
  },
91
92
  };
92
93
  // Specific panel styles
93
94
  export const inspector = {
94
- panel: Object.assign(Object.assign({}, base.panel), { position: 'absolute', top: 8, right: 8, zIndex: 20, width: 260 }),
95
+ panel: Object.assign(Object.assign({}, base.panel), { position: 'absolute', top: 8, right: 8, zIndex: 20, width: 300 }),
95
96
  content: {
96
- padding: 8,
97
+ padding: 6,
97
98
  maxHeight: '80vh',
98
99
  overflowY: 'auto',
99
100
  overflowX: 'hidden',
@@ -102,37 +103,37 @@ export const inspector = {
102
103
  boxSizing: 'border-box',
103
104
  display: 'flex',
104
105
  flexDirection: 'column',
105
- gap: 8,
106
+ gap: 4,
106
107
  },
107
108
  };
108
109
  export const tree = {
109
110
  panel: Object.assign(Object.assign({}, base.panel), { maxHeight: '85vh', display: 'flex', flexDirection: 'column', userSelect: 'none' }),
110
111
  scroll: {
111
112
  overflowY: 'auto',
112
- padding: 4,
113
+ padding: 2,
113
114
  scrollbarWidth: 'thin',
114
115
  scrollbarColor: `${colors.bgLight} transparent`,
115
116
  },
116
117
  row: {
117
118
  display: 'flex',
118
119
  alignItems: 'center',
119
- padding: '3px 6px',
120
+ padding: '2px 4px',
120
121
  borderBottomWidth: 1,
121
122
  borderBottomStyle: 'solid',
122
123
  borderBottomColor: colors.borderFaint,
123
124
  cursor: 'pointer',
124
125
  whiteSpace: 'nowrap',
125
- borderRadius: 2,
126
126
  },
127
127
  selected: {
128
128
  background: colors.accentBg,
129
129
  borderBottomColor: colors.accentBorder,
130
+ boxShadow: 'none',
130
131
  },
131
132
  iconButton: {
132
133
  background: 'none',
133
134
  border: 'none',
134
135
  cursor: 'pointer',
135
- padding: '0 4px',
136
+ padding: '0 2px',
136
137
  fontSize: 14,
137
138
  opacity: 0.7,
138
139
  color: 'inherit',
@@ -146,15 +147,15 @@ export const menu = {
146
147
  width: 'max-content',
147
148
  maxWidth: 'min(240px, calc(100vw - 16px))',
148
149
  background: colors.bgSurface,
149
- border: `1px solid ${colors.border}`,
150
- borderRadius: 4,
150
+ border: 'none',
151
151
  overflow: 'hidden',
152
- boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
152
+ borderRadius: 0,
153
+ boxShadow: 'none',
153
154
  },
154
155
  item: {
155
156
  width: '100%',
156
157
  textAlign: 'left',
157
- padding: '7px 12px',
158
+ padding: '4px 8px',
158
159
  background: 'transparent',
159
160
  border: 'none',
160
161
  color: colors.text,
@@ -171,17 +172,17 @@ export const toolbar = {
171
172
  panel: {
172
173
  position: 'absolute',
173
174
  top: 8,
174
- left: '240px',
175
+ left: '232px',
175
176
  display: 'flex',
176
- gap: 6,
177
- padding: '4px 6px',
177
+ gap: 4,
178
+ padding: '2px 4px',
178
179
  background: colors.bg,
179
180
  border: `1px solid ${colors.border}`,
180
- borderRadius: 4,
181
181
  color: colors.text,
182
182
  fontFamily: fonts.family,
183
183
  fontSize: fonts.size,
184
- boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
184
+ borderRadius: 0,
185
+ boxShadow: 'none',
185
186
  },
186
187
  divider: {
187
188
  width: 1,
@@ -195,10 +196,22 @@ export const toolbar = {
195
196
  // Reusable component card style for inspector sections
196
197
  export const componentCard = {
197
198
  container: {
198
- marginBottom: 8,
199
- backgroundColor: colors.bgSurface,
200
- padding: 8,
201
- borderRadius: 4,
199
+ marginBottom: 4,
200
+ backgroundColor: colors.bg,
201
+ padding: 4,
202
+ border: `1px solid ${colors.border}`,
203
+ borderRadius: 0,
204
+ boxShadow: 'none',
205
+ },
206
+ };
207
+ export const ui = {
208
+ secondaryPanel: {
209
+ background: colors.bgSurface,
202
210
  border: `1px solid ${colors.border}`,
211
+ borderRadius: 0,
212
+ padding: 4,
213
+ boxSizing: 'border-box',
203
214
  },
215
+ compactActionButton: Object.assign(Object.assign({}, base.btn), { width: 28, minWidth: 28, padding: 0, flexShrink: 0 }),
216
+ monoTextInput: Object.assign(Object.assign({}, base.input), { fontFamily: 'monospace' }),
204
217
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.90",
3
+ "version": "0.0.92",
4
4
  "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",