react-three-game 0.0.55 → 0.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/shared/ContactShadow.d.ts +8 -0
  4. package/dist/shared/ContactShadow.js +32 -0
  5. package/dist/shared/GameCanvas.js +1 -3
  6. package/dist/tools/assetviewer/page.js +36 -15
  7. package/dist/tools/dragdrop/DragDropLoader.js +17 -40
  8. package/dist/tools/dragdrop/modelLoader.d.ts +5 -0
  9. package/dist/tools/dragdrop/modelLoader.js +39 -0
  10. package/dist/tools/prefabeditor/Dropdown.d.ts +15 -0
  11. package/dist/tools/prefabeditor/Dropdown.js +82 -0
  12. package/dist/tools/prefabeditor/EditorContext.d.ts +5 -0
  13. package/dist/tools/prefabeditor/EditorTree.js +139 -70
  14. package/dist/tools/prefabeditor/EditorUI.js +5 -10
  15. package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
  16. package/dist/tools/prefabeditor/PrefabEditor.js +70 -3
  17. package/dist/tools/prefabeditor/PrefabRoot.d.ts +3 -0
  18. package/dist/tools/prefabeditor/PrefabRoot.js +136 -35
  19. package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -7
  20. package/dist/tools/prefabeditor/components/CameraComponent.d.ts +3 -0
  21. package/dist/tools/prefabeditor/components/CameraComponent.js +25 -0
  22. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
  23. package/dist/tools/prefabeditor/components/EnvironmentComponent.d.ts +3 -0
  24. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +15 -0
  25. package/dist/tools/prefabeditor/components/GeometryComponent.js +46 -46
  26. package/dist/tools/prefabeditor/components/Input.d.ts +51 -1
  27. package/dist/tools/prefabeditor/components/Input.js +100 -47
  28. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +8 -2
  29. package/dist/tools/prefabeditor/components/MaterialComponent.js +129 -14
  30. package/dist/tools/prefabeditor/components/ModelComponent.js +44 -3
  31. package/dist/tools/prefabeditor/components/PhysicsComponent.js +16 -81
  32. package/dist/tools/prefabeditor/components/SpotLightComponent.js +6 -11
  33. package/dist/tools/prefabeditor/components/TextComponent.js +7 -53
  34. package/dist/tools/prefabeditor/components/TransformComponent.js +31 -19
  35. package/dist/tools/prefabeditor/components/index.js +5 -1
  36. package/dist/tools/prefabeditor/styles.d.ts +17 -4
  37. package/dist/tools/prefabeditor/styles.js +69 -32
  38. package/dist/tools/prefabeditor/utils.d.ts +8 -3
  39. package/dist/tools/prefabeditor/utils.js +92 -6
  40. package/package.json +1 -1
  41. package/react-three-game-skill/react-three-game/rules/LIGHTING.md +6 -0
  42. package/src/index.ts +7 -0
  43. package/src/shared/ContactShadow.tsx +74 -0
  44. package/src/shared/GameCanvas.tsx +0 -3
  45. package/src/tools/assetviewer/page.tsx +78 -46
  46. package/src/tools/dragdrop/DragDropLoader.tsx +7 -39
  47. package/src/tools/dragdrop/modelLoader.ts +36 -0
  48. package/src/tools/prefabeditor/Dropdown.tsx +112 -0
  49. package/src/tools/prefabeditor/EditorContext.tsx +5 -0
  50. package/src/tools/prefabeditor/EditorTree.tsx +237 -115
  51. package/src/tools/prefabeditor/EditorUI.tsx +6 -11
  52. package/src/tools/prefabeditor/PrefabEditor.tsx +77 -5
  53. package/src/tools/prefabeditor/PrefabRoot.tsx +228 -59
  54. package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +5 -11
  55. package/src/tools/prefabeditor/components/CameraComponent.tsx +80 -0
  56. package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +2 -2
  57. package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +47 -0
  58. package/src/tools/prefabeditor/components/GeometryComponent.tsx +69 -63
  59. package/src/tools/prefabeditor/components/Input.tsx +247 -53
  60. package/src/tools/prefabeditor/components/MaterialComponent.tsx +191 -20
  61. package/src/tools/prefabeditor/components/ModelComponent.tsx +52 -5
  62. package/src/tools/prefabeditor/components/PhysicsComponent.tsx +44 -85
  63. package/src/tools/prefabeditor/components/SpotLightComponent.tsx +14 -16
  64. package/src/tools/prefabeditor/components/TextComponent.tsx +58 -57
  65. package/src/tools/prefabeditor/components/TransformComponent.tsx +78 -20
  66. package/src/tools/prefabeditor/components/index.ts +5 -1
  67. package/src/tools/prefabeditor/styles.ts +71 -32
  68. package/src/tools/prefabeditor/utils.ts +96 -5
@@ -9,91 +9,26 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx } from "react/jsx-runtime";
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import { RigidBody, useRapier } from "@react-three/rapier";
14
14
  import { useRef, useEffect, useCallback } from 'react';
15
- import { FieldRenderer } from "./Input";
15
+ import { BooleanField, FieldGroup, NumberField, SelectField } from "./Input";
16
16
  import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
17
- const physicsFields = [
18
- {
19
- name: 'type',
20
- type: 'select',
21
- label: 'Type',
22
- options: [
23
- { value: 'dynamic', label: 'Dynamic' },
24
- { value: 'fixed', label: 'Fixed' },
25
- { value: 'kinematicPosition', label: 'Kinematic Position' },
26
- { value: 'kinematicVelocity', label: 'Kinematic Velocity' },
27
- ],
28
- },
29
- {
30
- name: 'colliders',
31
- type: 'select',
32
- label: 'Collider',
33
- options: [
34
- { value: 'hull', label: 'Hull (convex)' },
35
- { value: 'trimesh', label: 'Trimesh (exact)' },
36
- { value: 'cuboid', label: 'Cuboid (box)' },
37
- { value: 'ball', label: 'Ball (sphere)' },
38
- ],
39
- },
40
- {
41
- name: 'mass',
42
- type: 'number',
43
- label: 'Mass',
44
- },
45
- {
46
- name: 'restitution',
47
- type: 'number',
48
- label: 'Restitution (Bounciness)',
49
- min: 0,
50
- max: 1,
51
- step: 0.1,
52
- },
53
- {
54
- name: 'friction',
55
- type: 'number',
56
- label: 'Friction',
57
- min: 0,
58
- step: 0.1,
59
- },
60
- {
61
- name: 'linearDamping',
62
- type: 'number',
63
- label: 'Linear Damping',
64
- min: 0,
65
- step: 0.1,
66
- },
67
- {
68
- name: 'angularDamping',
69
- type: 'number',
70
- label: 'Angular Damping',
71
- min: 0,
72
- step: 0.1,
73
- },
74
- {
75
- name: 'gravityScale',
76
- type: 'number',
77
- label: 'Gravity Scale',
78
- step: 0.1,
79
- },
80
- {
81
- name: 'sensor',
82
- type: 'boolean',
83
- label: 'Sensor (Trigger Only)',
84
- },
85
- {
86
- name: 'activeCollisionTypes',
87
- type: 'select',
88
- label: 'Collision Detection',
89
- options: [
90
- { value: '', label: 'Default (Dynamic only)' },
91
- { value: 'all', label: 'All (includes kinematic & fixed)' },
92
- ],
93
- },
94
- ];
95
17
  function PhysicsComponentEditor({ component, onUpdate }) {
96
- return (_jsx(FieldRenderer, { fields: physicsFields, values: component.properties, onChange: onUpdate }));
18
+ return (_jsxs(FieldGroup, { children: [_jsx(SelectField, { name: "type", label: "Type", values: component.properties, onChange: onUpdate, options: [
19
+ { value: 'dynamic', label: 'Dynamic' },
20
+ { value: 'fixed', label: 'Fixed' },
21
+ { value: 'kinematicPosition', label: 'Kinematic Position' },
22
+ { value: 'kinematicVelocity', label: 'Kinematic Velocity' },
23
+ ] }), _jsx(SelectField, { name: "colliders", label: "Collider", values: component.properties, onChange: onUpdate, options: [
24
+ { value: 'hull', label: 'Hull (convex)' },
25
+ { value: 'trimesh', label: 'Trimesh (exact)' },
26
+ { value: 'cuboid', label: 'Cuboid (box)' },
27
+ { value: 'ball', label: 'Ball (sphere)' },
28
+ ] }), _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
29
+ { value: '', label: 'Default (Dynamic only)' },
30
+ { value: 'all', label: 'All (includes kinematic & fixed)' },
31
+ ] })] }));
97
32
  }
98
33
  function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId, registerRigidBodyRef }) {
99
34
  const { type, colliders, sensor, activeCollisionTypes } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes"]);
@@ -1,18 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useRef, useEffect } from "react";
3
- import { FieldRenderer } from "./Input";
4
- const spotLightFields = [
5
- { name: 'color', type: 'color', label: 'Color' },
6
- { name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
7
- { name: 'angle', type: 'number', label: 'Angle', step: 0.1, min: 0, max: Math.PI },
8
- { name: 'penumbra', type: 'number', label: 'Penumbra', step: 0.1, min: 0, max: 1 },
9
- { name: 'distance', type: 'number', label: 'Distance', step: 1, min: 0 },
10
- { name: 'castShadow', type: 'boolean', label: 'Cast Shadow' },
11
- ];
3
+ import { BooleanField, ColorField, FieldGroup, NumberField } from "./Input";
4
+ import { useHelper } from "@react-three/drei";
5
+ import { SpotLightHelper } from "three";
12
6
  function SpotLightComponentEditor({ component, onUpdate }) {
13
- return (_jsx(FieldRenderer, { fields: spotLightFields, values: component.properties, onChange: onUpdate }));
7
+ return (_jsxs(FieldGroup, { children: [_jsx(ColorField, { name: "color", label: "Color", values: component.properties, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: component.properties, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(NumberField, { name: "angle", label: "Angle", values: component.properties, onChange: onUpdate, min: 0, max: Math.PI, step: 0.05, fallback: Math.PI / 6 }), _jsx(NumberField, { name: "penumbra", label: "Penumbra", values: component.properties, onChange: onUpdate, min: 0, max: 1, step: 0.05, fallback: 0.5 }), _jsx(NumberField, { name: "distance", label: "Distance", values: component.properties, onChange: onUpdate, min: 0, step: 1, fallback: 100 }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: component.properties, onChange: onUpdate, fallback: true })] }));
14
8
  }
15
- function SpotLightView({ properties, editMode }) {
9
+ function SpotLightView({ properties, editMode, isSelected }) {
16
10
  var _a, _b, _c, _d, _e, _f;
17
11
  const color = (_a = properties.color) !== null && _a !== void 0 ? _a : '#ffffff';
18
12
  const intensity = (_b = properties.intensity) !== null && _b !== void 0 ? _b : 1.0;
@@ -22,6 +16,7 @@ function SpotLightView({ properties, editMode }) {
22
16
  const castShadow = (_f = properties.castShadow) !== null && _f !== void 0 ? _f : true;
23
17
  const spotLightRef = useRef(null);
24
18
  const targetRef = useRef(null);
19
+ useHelper(editMode && isSelected ? spotLightRef : null, SpotLightHelper, color);
25
20
  useEffect(() => {
26
21
  if (spotLightRef.current && targetRef.current) {
27
22
  spotLightRef.current.target = targetRef.current;
@@ -1,61 +1,15 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { FieldRenderer } from "./Input";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ColorField, FieldGroup, NumberField, SelectField, StringField } from "./Input";
3
3
  import { Text } from 'three-text/three/react';
4
4
  import { useRef, useState, useCallback } from 'react';
5
5
  // Initialize HarfBuzz path for font shaping
6
6
  Text.setHarfBuzzPath('/fonts/hb.wasm');
7
7
  function TextComponentEditor({ component, onUpdate, }) {
8
- const fields = [
9
- {
10
- name: 'text',
11
- type: 'string',
12
- label: 'Text',
13
- placeholder: 'Enter text...',
14
- },
15
- {
16
- name: 'color',
17
- type: 'color',
18
- label: 'Color',
19
- },
20
- {
21
- name: 'font',
22
- type: 'string',
23
- label: 'Font',
24
- placeholder: '/fonts/NotoSans-Regular.ttf',
25
- },
26
- {
27
- name: 'size',
28
- type: 'number',
29
- label: 'Size',
30
- min: 0.01,
31
- step: 0.1,
32
- },
33
- {
34
- name: 'depth',
35
- type: 'number',
36
- label: 'Depth',
37
- min: 0,
38
- step: 0.1,
39
- },
40
- {
41
- name: 'width',
42
- type: 'number',
43
- label: 'Width',
44
- min: 0,
45
- step: 0.5,
46
- },
47
- {
48
- name: 'align',
49
- type: 'select',
50
- label: 'Align',
51
- options: [
52
- { value: 'left', label: 'Left' },
53
- { value: 'center', label: 'Center' },
54
- { value: 'right', label: 'Right' },
55
- ],
56
- },
57
- ];
58
- return (_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: onUpdate }));
8
+ return (_jsxs(FieldGroup, { children: [_jsx(StringField, { name: "text", label: "Text", values: component.properties, onChange: onUpdate, placeholder: "Enter text..." }), _jsx(ColorField, { name: "color", label: "Color", values: component.properties, onChange: onUpdate }), _jsx(StringField, { name: "font", label: "Font", values: component.properties, onChange: onUpdate, placeholder: "/fonts/NotoSans-Regular.ttf" }), _jsx(NumberField, { name: "size", label: "Size", values: component.properties, onChange: onUpdate, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "depth", label: "Depth", values: component.properties, onChange: onUpdate, min: 0, step: 0.1 }), _jsx(NumberField, { name: "width", label: "Width", values: component.properties, onChange: onUpdate, min: 0, step: 0.5 }), _jsx(SelectField, { name: "align", label: "Align", values: component.properties, onChange: onUpdate, options: [
9
+ { value: 'left', label: 'Left' },
10
+ { value: 'center', label: 'Center' },
11
+ { value: 'right', label: 'Right' },
12
+ ] })] }));
59
13
  }
60
14
  function TextComponentView({ properties }) {
61
15
  const { text = '', font, size, depth, width, align, color } = properties;
@@ -1,42 +1,54 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { FieldRenderer, Label } from "./Input";
2
+ import { Label, Vector3Field, Vector3Input } from "./Input";
3
3
  import { useEditorContext } from "../EditorContext";
4
+ import { colors } from "../styles";
4
5
  const buttonStyle = {
5
- padding: '2px 6px',
6
- background: 'transparent',
7
- color: 'rgba(255,255,255,0.9)',
8
- border: '1px solid rgba(255,255,255,0.14)',
9
- borderRadius: 4,
6
+ padding: '4px 8px',
7
+ background: colors.bgSurface,
8
+ color: colors.text,
9
+ border: `1px solid ${colors.border}`,
10
+ borderRadius: 3,
10
11
  cursor: 'pointer',
11
12
  font: 'inherit',
13
+ fontSize: 11,
12
14
  flex: 1,
13
15
  };
14
16
  function TransformModeSelector({ transformMode, setTransformMode, snapResolution, setSnapResolution }) {
15
17
  return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsxs(Label, { children: ["Transform Mode ", snapResolution > 0 && `(Snap: ${snapResolution})`] }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => {
16
18
  const isActive = transformMode === mode;
17
- return (_jsx("button", { onClick: () => setTransformMode(mode), style: Object.assign(Object.assign({}, buttonStyle), { background: isActive ? 'rgba(255,255,255,0.10)' : 'transparent' }), onPointerEnter: (e) => {
19
+ return (_jsx("button", { onClick: () => setTransformMode(mode), style: Object.assign(Object.assign({}, buttonStyle), { background: isActive ? colors.accentBg : colors.bgSurface, borderColor: isActive ? colors.accentBorder : colors.border, color: isActive ? colors.accent : colors.text }), onPointerEnter: (e) => {
18
20
  if (!isActive)
19
- e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
21
+ e.currentTarget.style.background = colors.bgHover;
20
22
  }, onPointerLeave: (e) => {
21
23
  if (!isActive)
22
- e.currentTarget.style.background = 'transparent';
24
+ e.currentTarget.style.background = colors.bgSurface;
23
25
  }, children: mode }, mode));
24
- }) }), _jsx("div", { style: { marginTop: 6 }, children: _jsxs("button", { onClick: () => setSnapResolution(snapResolution > 0 ? 0 : 0.1), style: Object.assign(Object.assign({}, buttonStyle), { background: snapResolution > 0 ? 'rgba(255,255,255,0.10)' : 'transparent', width: '100%' }), onPointerEnter: (e) => {
26
+ }) }), _jsx("div", { style: { marginTop: 6 }, children: _jsxs("button", { onClick: () => setSnapResolution(snapResolution > 0 ? 0 : 0.1), style: Object.assign(Object.assign({}, buttonStyle), { background: snapResolution > 0 ? colors.accentBg : colors.bgSurface, borderColor: snapResolution > 0 ? colors.accentBorder : colors.border, color: snapResolution > 0 ? colors.accent : colors.text, width: '100%' }), onPointerEnter: (e) => {
25
27
  if (snapResolution === 0)
26
- e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
28
+ e.currentTarget.style.background = colors.bgHover;
27
29
  }, onPointerLeave: (e) => {
28
30
  if (snapResolution === 0)
29
- e.currentTarget.style.background = 'transparent';
31
+ e.currentTarget.style.background = colors.bgSurface;
30
32
  }, children: ["Snap: ", snapResolution > 0 ? `ON (${snapResolution})` : 'OFF'] }) })] }));
31
33
  }
34
+ const snapLockBtnStyle = {
35
+ background: 'none',
36
+ border: 'none',
37
+ cursor: 'pointer',
38
+ padding: '0 2px',
39
+ fontSize: 12,
40
+ lineHeight: 1,
41
+ color: colors.textMuted,
42
+ };
43
+ function SnapLockButton({ locked, onToggle, title }) {
44
+ return (_jsx("button", { style: snapLockBtnStyle, onClick: onToggle, title: title, children: locked ? '🔒' : '🔓' }));
45
+ }
32
46
  function TransformComponentEditor({ component, onUpdate }) {
33
- const { transformMode, setTransformMode, snapResolution, setSnapResolution } = useEditorContext();
34
- const fields = [
35
- { name: 'position', type: 'vector3', label: 'Position', snap: snapResolution },
36
- { name: 'rotation', type: 'vector3', label: 'Rotation', snap: snapResolution },
37
- { name: 'scale', type: 'vector3', label: 'Scale', snap: snapResolution },
38
- ];
39
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(TransformModeSelector, { transformMode: transformMode, setTransformMode: setTransformMode, snapResolution: snapResolution, setSnapResolution: setSnapResolution }), _jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: onUpdate })] }));
47
+ var _a, _b;
48
+ const { transformMode, setTransformMode, snapResolution, setSnapResolution, positionSnap, setPositionSnap, rotationSnap, setRotationSnap } = useEditorContext();
49
+ const positionSnapped = positionSnap > 0;
50
+ const rotationSnapped = rotationSnap > 0;
51
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [_jsx(TransformModeSelector, { transformMode: transformMode, setTransformMode: setTransformMode, snapResolution: snapResolution, setSnapResolution: setSnapResolution }), _jsx(Vector3Input, { label: "Position", value: (_a = component.properties.position) !== null && _a !== void 0 ? _a : [0, 0, 0], onChange: v => onUpdate({ position: v }), snap: positionSnap, labelExtra: _jsx(SnapLockButton, { locked: positionSnapped, onToggle: () => setPositionSnap(positionSnapped ? 0 : 0.5), title: positionSnapped ? `Snap ON (0.5) — click to disable` : `Snap OFF — click to enable (0.5)` }) }), _jsx(Vector3Input, { label: "Rotation", value: (_b = component.properties.rotation) !== null && _b !== void 0 ? _b : [0, 0, 0], onChange: v => onUpdate({ rotation: v }), snap: rotationSnap, labelExtra: _jsx(SnapLockButton, { locked: rotationSnapped, onToggle: () => setRotationSnap(rotationSnapped ? 0 : Math.PI / 4), title: rotationSnapped ? `Snap ON (π/4) — click to disable` : `Snap OFF — click to enable (π/4)` }) }), _jsx(Vector3Field, { name: "scale", label: "Scale", values: component.properties, onChange: onUpdate, fallback: [1, 1, 1] })] }));
40
52
  }
41
53
  const TransformComponent = {
42
54
  name: 'Transform',
@@ -7,6 +7,8 @@ import DirectionalLightComponent from './DirectionalLightComponent';
7
7
  import AmbientLightComponent from './AmbientLightComponent';
8
8
  import ModelComponent from './ModelComponent';
9
9
  import TextComponent from './TextComponent';
10
+ import EnvironmentComponent from './EnvironmentComponent';
11
+ import CameraComponent from './CameraComponent';
10
12
  export default [
11
13
  GeometryComponent,
12
14
  TransformComponent,
@@ -16,5 +18,7 @@ export default [
16
18
  DirectionalLightComponent,
17
19
  AmbientLightComponent,
18
20
  ModelComponent,
19
- TextComponent
21
+ TextComponent,
22
+ EnvironmentComponent,
23
+ CameraComponent,
20
24
  ];
@@ -1,12 +1,18 @@
1
1
  export declare const colors: {
2
2
  bg: string;
3
+ bgSurface: string;
3
4
  bgLight: string;
4
5
  bgHover: string;
6
+ bgInput: string;
5
7
  border: string;
6
8
  borderLight: string;
7
9
  borderFaint: string;
8
10
  text: string;
9
11
  textMuted: string;
12
+ textDim: string;
13
+ accent: string;
14
+ accentBg: string;
15
+ accentBorder: string;
10
16
  danger: string;
11
17
  dangerBg: string;
12
18
  dangerBorder: string;
@@ -890,6 +896,8 @@ export declare const inspector: {
890
896
  padding: number;
891
897
  maxHeight: string;
892
898
  overflowY: "auto";
899
+ overflowX: "hidden";
900
+ boxSizing: "border-box";
893
901
  display: string;
894
902
  flexDirection: "column";
895
903
  gap: number;
@@ -1759,19 +1767,21 @@ export declare const tree: {
1759
1767
  row: React.CSSProperties;
1760
1768
  selected: {
1761
1769
  background: string;
1770
+ borderBottomColor: string;
1762
1771
  };
1763
1772
  };
1764
1773
  export declare const menu: {
1765
1774
  container: {
1766
1775
  position: "fixed";
1767
1776
  zIndex: number;
1768
- minWidth: number;
1777
+ minWidth: string;
1778
+ width: string;
1779
+ maxWidth: string;
1769
1780
  background: string;
1770
1781
  border: string;
1771
1782
  borderRadius: number;
1772
1783
  overflow: string;
1773
1784
  boxShadow: string;
1774
- backdropFilter: string;
1775
1785
  };
1776
1786
  item: React.CSSProperties;
1777
1787
  danger: {
@@ -1783,7 +1793,6 @@ export declare const toolbar: {
1783
1793
  position: "absolute";
1784
1794
  top: number;
1785
1795
  left: string;
1786
- transform: string;
1787
1796
  display: string;
1788
1797
  gap: number;
1789
1798
  padding: string;
@@ -1793,7 +1802,7 @@ export declare const toolbar: {
1793
1802
  color: string;
1794
1803
  fontFamily: string;
1795
1804
  fontSize: number;
1796
- backdropFilter: string;
1805
+ boxShadow: string;
1797
1806
  };
1798
1807
  divider: {
1799
1808
  width: number;
@@ -1804,3 +1813,7 @@ export declare const toolbar: {
1804
1813
  cursor: string;
1805
1814
  };
1806
1815
  };
1816
+ export declare const scrollbarCSS: string;
1817
+ export declare const componentCard: {
1818
+ container: React.CSSProperties;
1819
+ };
@@ -1,19 +1,25 @@
1
1
  // Shared editor styles - single source of truth for all prefab editor UI
2
2
  export const colors = {
3
- bg: 'rgba(0,0,0,0.6)',
4
- bgLight: 'rgba(255,255,255,0.06)',
5
- bgHover: 'rgba(255,255,255,0.1)',
6
- border: 'rgba(255,255,255,0.15)',
7
- borderLight: 'rgba(255,255,255,0.1)',
8
- borderFaint: 'rgba(255,255,255,0.05)',
9
- text: '#fff',
10
- textMuted: 'rgba(255,255,255,0.7)',
11
- danger: '#ffaaaa',
12
- dangerBg: 'rgba(255,80,80,0.2)',
13
- dangerBorder: 'rgba(255,80,80,0.4)',
3
+ bg: '#1e1e1e',
4
+ bgSurface: '#252526',
5
+ bgLight: '#2d2d2d',
6
+ bgHover: '#2a2d2e',
7
+ bgInput: '#1a1a1a',
8
+ border: '#3c3c3c',
9
+ borderLight: '#333333',
10
+ borderFaint: '#2a2a2a',
11
+ text: '#cccccc',
12
+ textMuted: '#999999',
13
+ textDim: '#666666',
14
+ accent: '#4c9eff',
15
+ accentBg: 'rgba(76, 158, 255, 0.12)',
16
+ accentBorder: 'rgba(76, 158, 255, 0.4)',
17
+ danger: '#f44747',
18
+ dangerBg: 'rgba(244, 71, 71, 0.12)',
19
+ dangerBorder: 'rgba(244, 71, 71, 0.35)',
14
20
  };
15
21
  export const fonts = {
16
- family: 'system-ui, sans-serif',
22
+ family: 'system-ui, -apple-system, sans-serif',
17
23
  size: 11,
18
24
  sizeSm: 10,
19
25
  };
@@ -24,12 +30,12 @@ export const base = {
24
30
  color: colors.text,
25
31
  border: `1px solid ${colors.border}`,
26
32
  borderRadius: 4,
27
- backdropFilter: 'blur(8px)',
28
33
  fontFamily: fonts.family,
29
34
  fontSize: fonts.size,
35
+ boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
30
36
  },
31
37
  header: {
32
- padding: '6px 8px',
38
+ padding: '7px 10px',
33
39
  display: 'flex',
34
40
  alignItems: 'center',
35
41
  justifyContent: 'space-between',
@@ -37,22 +43,23 @@ export const base = {
37
43
  background: colors.bgLight,
38
44
  borderBottom: `1px solid ${colors.borderLight}`,
39
45
  fontSize: fonts.size,
40
- fontWeight: 500,
46
+ fontWeight: 600,
41
47
  textTransform: 'uppercase',
42
- letterSpacing: 0.5,
48
+ letterSpacing: 0.8,
49
+ color: colors.text,
43
50
  },
44
51
  input: {
45
52
  width: '100%',
46
- background: colors.bgHover,
53
+ background: colors.bgInput,
47
54
  border: `1px solid ${colors.border}`,
48
55
  borderRadius: 3,
49
- padding: '4px 6px',
56
+ padding: '5px 8px',
50
57
  color: colors.text,
51
58
  fontSize: fonts.size,
52
59
  outline: 'none',
53
60
  },
54
61
  btn: {
55
- background: colors.bgHover,
62
+ background: colors.bgLight,
56
63
  border: `1px solid ${colors.border}`,
57
64
  borderRadius: 3,
58
65
  padding: '4px 8px',
@@ -68,10 +75,11 @@ export const base = {
68
75
  },
69
76
  label: {
70
77
  fontSize: fonts.sizeSm,
71
- opacity: 0.7,
78
+ color: colors.textMuted,
72
79
  marginBottom: 4,
73
80
  textTransform: 'uppercase',
74
81
  letterSpacing: 0.5,
82
+ fontWeight: 500,
75
83
  },
76
84
  row: {
77
85
  display: 'flex',
@@ -89,6 +97,8 @@ export const inspector = {
89
97
  padding: 8,
90
98
  maxHeight: '80vh',
91
99
  overflowY: 'auto',
100
+ overflowX: 'hidden',
101
+ boxSizing: 'border-box',
92
102
  display: 'flex',
93
103
  flexDirection: 'column',
94
104
  gap: 8,
@@ -100,40 +110,46 @@ export const tree = {
100
110
  overflowY: 'auto',
101
111
  padding: 4,
102
112
  scrollbarWidth: 'thin',
103
- scrollbarColor: 'rgba(255,255,255,0.06) transparent',
113
+ scrollbarColor: `${colors.bgLight} transparent`,
104
114
  },
105
115
  row: {
106
116
  display: 'flex',
107
117
  alignItems: 'center',
108
118
  padding: '3px 6px',
109
- borderBottom: `1px solid ${colors.borderFaint}`,
119
+ borderBottomWidth: 1,
120
+ borderBottomStyle: 'solid',
121
+ borderBottomColor: colors.borderFaint,
110
122
  cursor: 'pointer',
111
123
  whiteSpace: 'nowrap',
124
+ borderRadius: 2,
112
125
  },
113
126
  selected: {
114
- background: 'rgba(255,255,255,0.12)',
127
+ background: colors.accentBg,
128
+ borderBottomColor: colors.accentBorder,
115
129
  },
116
130
  };
117
131
  export const menu = {
118
132
  container: {
119
133
  position: 'fixed',
120
134
  zIndex: 50,
121
- minWidth: 120,
122
- background: 'rgba(0,0,0,0.85)',
135
+ minWidth: 'auto',
136
+ width: 'max-content',
137
+ maxWidth: 'min(240px, calc(100vw - 16px))',
138
+ background: colors.bgSurface,
123
139
  border: `1px solid ${colors.border}`,
124
140
  borderRadius: 4,
125
141
  overflow: 'hidden',
126
- boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
127
- backdropFilter: 'blur(8px)',
142
+ boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
128
143
  },
129
144
  item: {
130
145
  width: '100%',
131
146
  textAlign: 'left',
132
- padding: '6px 8px',
147
+ padding: '7px 12px',
133
148
  background: 'transparent',
134
149
  border: 'none',
135
150
  color: colors.text,
136
151
  fontSize: fonts.size,
152
+ whiteSpace: 'nowrap',
137
153
  cursor: 'pointer',
138
154
  outline: 'none',
139
155
  },
@@ -145,8 +161,7 @@ export const toolbar = {
145
161
  panel: {
146
162
  position: 'absolute',
147
163
  top: 8,
148
- left: '50%',
149
- transform: 'translateX(-50%)',
164
+ left: '240px',
150
165
  display: 'flex',
151
166
  gap: 6,
152
167
  padding: '4px 6px',
@@ -156,14 +171,36 @@ export const toolbar = {
156
171
  color: colors.text,
157
172
  fontFamily: fonts.family,
158
173
  fontSize: fonts.size,
159
- backdropFilter: 'blur(8px)',
174
+ boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
160
175
  },
161
176
  divider: {
162
177
  width: 1,
163
- background: 'rgba(255,255,255,0.2)',
178
+ background: colors.borderLight,
164
179
  },
165
180
  disabled: {
166
181
  opacity: 0.4,
167
182
  cursor: 'not-allowed',
168
183
  },
169
184
  };
185
+ // Shared scrollbar CSS (inject via <style> tag since CSS can't be bundled)
186
+ export const scrollbarCSS = `
187
+ .prefab-scroll::-webkit-scrollbar,
188
+ .tree-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
189
+ .prefab-scroll::-webkit-scrollbar-track,
190
+ .tree-scroll::-webkit-scrollbar-track { background: transparent; }
191
+ .prefab-scroll::-webkit-scrollbar-thumb,
192
+ .tree-scroll::-webkit-scrollbar-thumb { background: ${colors.border}; border-radius: 3px; }
193
+ .prefab-scroll::-webkit-scrollbar-thumb:hover,
194
+ .tree-scroll::-webkit-scrollbar-thumb:hover { background: #555; }
195
+ .prefab-scroll { scrollbar-width: thin; scrollbar-color: ${colors.border} transparent; }
196
+ `;
197
+ // Reusable component card style for inspector sections
198
+ export const componentCard = {
199
+ container: {
200
+ marginBottom: 8,
201
+ backgroundColor: colors.bgSurface,
202
+ padding: 8,
203
+ borderRadius: 4,
204
+ border: `1px solid ${colors.border}`,
205
+ },
206
+ };
@@ -1,13 +1,13 @@
1
1
  import { GameObject, Prefab } from "./types";
2
- import { Object3D } from 'three';
2
+ import { Object3D, Vector3 } from 'three';
3
3
  export interface ExportGLBOptions {
4
4
  filename?: string;
5
5
  binary?: boolean;
6
6
  onComplete?: (result: ArrayBuffer | object) => void;
7
7
  onError?: (error: any) => void;
8
8
  }
9
- /** Save a prefab as JSON file */
10
- export declare function saveJson(data: Prefab, filename: string): void;
9
+ /** Save a prefab as JSON file, showing a Save As dialog when supported */
10
+ export declare function saveJson(data: Prefab, filename: string): Promise<void>;
11
11
  /** Load a prefab from JSON file */
12
12
  export declare function loadJson(): Promise<Prefab | undefined>;
13
13
  /**
@@ -23,6 +23,7 @@ export declare function exportGLB(sceneRoot: Object3D, options?: ExportGLBOption
23
23
  * @returns Promise that resolves with the GLB data as ArrayBuffer
24
24
  */
25
25
  export declare function exportGLBData(sceneRoot: Object3D): Promise<ArrayBuffer>;
26
+ export declare function focusCameraOnObject(object: Object3D, camera: Object3D, target: Vector3, update?: () => void): void;
26
27
  /** Find a node by ID in the tree */
27
28
  export declare function findNode(root: GameObject, id: string): GameObject | null;
28
29
  /** Find the parent of a node by ID */
@@ -44,3 +45,7 @@ export declare function regenerateIds(node: GameObject): GameObject;
44
45
  /** Get component data from a node */
45
46
  export declare function getComponent<T = any>(node: GameObject, type: string): T | undefined;
46
47
  export declare function updateNodeById(root: GameObject, id: string, updater: (node: GameObject) => GameObject): GameObject;
48
+ /** Create a GameObject node for a 3D model file */
49
+ export declare function createModelNode(filename: string, name?: string): GameObject;
50
+ /** Create a GameObject node for an image as a textured plane */
51
+ export declare function createImageNode(texturePath: string, name?: string): GameObject;