react-three-game 0.0.32 → 0.0.33

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.
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useRef, useEffect } from "react";
3
3
  import { useFrame } from "@react-three/fiber";
4
4
  import { Vector3 } from "three";
5
+ import { Input, Label } from "./Input";
5
6
  function DirectionalLightComponentEditor({ component, onUpdate }) {
6
7
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
7
8
  const props = {
@@ -17,7 +18,18 @@ function DirectionalLightComponentEditor({ component, onUpdate }) {
17
18
  shadowCameraRight: (_k = component.properties.shadowCameraRight) !== null && _k !== void 0 ? _k : 30,
18
19
  targetOffset: (_l = component.properties.targetOffset) !== null && _l !== void 0 ? _l : [0, -5, 0]
19
20
  };
20
- return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) }), _jsx("input", { type: "text", style: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) })] })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.intensity, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'intensity': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Cast Shadow" }), _jsx("input", { type: "checkbox", style: { height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }, checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'castShadow': e.target.checked })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Shadow Map Size" }), _jsx("input", { type: "number", step: "256", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowMapSize, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowMapSize': parseFloat(e.target.value) })) })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Shadow Camera" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Near" }), _jsx("input", { type: "number", step: "0.1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraNear, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraNear': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Far" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraFar, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraFar': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Top" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraTop, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraTop': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Bottom" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraBottom, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraBottom': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Left" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraLeft, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraLeft': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Right" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraRight, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraRight': parseFloat(e.target.value) })) })] })] })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Target Offset" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "X" }), _jsx("input", { type: "number", step: "0.5", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.targetOffset[0], onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'targetOffset': [parseFloat(e.target.value), props.targetOffset[1], props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Y" }), _jsx("input", { type: "number", step: "0.5", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.targetOffset[1], onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'targetOffset': [props.targetOffset[0], parseFloat(e.target.value), props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Z" }), _jsx("input", { type: "number", step: "0.5", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.targetOffset[2], onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'targetOffset': [props.targetOffset[0], props.targetOffset[1], parseFloat(e.target.value)] })) })] })] })] })] });
21
+ const textInputStyle = {
22
+ flex: 1,
23
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
24
+ border: '1px solid rgba(34, 211, 238, 0.3)',
25
+ padding: '2px 4px',
26
+ fontSize: '10px',
27
+ color: 'rgba(165, 243, 252, 1)',
28
+ fontFamily: 'monospace',
29
+ outline: 'none',
30
+ };
31
+ const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 };
32
+ return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) }), _jsx("input", { type: "text", style: textInputStyle, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Intensity" }), _jsx(Input, { step: "0.1", value: props.intensity, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { intensity: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Cast Shadow" }), _jsx("input", { type: "checkbox", style: { height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }, checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { castShadow: e.target.checked })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Shadow Map Size" }), _jsx(Input, { step: "256", value: props.shadowMapSize, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowMapSize: value })) })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Shadow Camera" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Near" }), _jsx(Input, { step: "0.1", value: props.shadowCameraNear, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraNear: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Far" }), _jsx(Input, { step: "1", value: props.shadowCameraFar, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraFar: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Top" }), _jsx(Input, { step: "1", value: props.shadowCameraTop, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraTop: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Bottom" }), _jsx(Input, { step: "1", value: props.shadowCameraBottom, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraBottom: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Left" }), _jsx(Input, { step: "1", value: props.shadowCameraLeft, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraLeft: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Right" }), _jsx(Input, { step: "1", value: props.shadowCameraRight, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraRight: value })) })] })] })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Target Offset" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "X" }), _jsx(Input, { step: "0.5", value: props.targetOffset[0], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [value, props.targetOffset[1], props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Y" }), _jsx(Input, { step: "0.5", value: props.targetOffset[1], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [props.targetOffset[0], value, props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Z" }), _jsx(Input, { step: "0.5", value: props.targetOffset[2], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [props.targetOffset[0], props.targetOffset[1], value] })) })] })] })] })] });
21
33
  }
22
34
  function DirectionalLightView({ properties, editMode }) {
23
35
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Input, Label } from "./Input";
2
3
  const GEOMETRY_ARGS = {
3
4
  box: {
4
5
  labels: ["Width", "Height", "Depth"],
@@ -16,17 +17,24 @@ const GEOMETRY_ARGS = {
16
17
  function GeometryComponentEditor({ component, onUpdate, }) {
17
18
  const { geometryType, args = [] } = component.properties;
18
19
  const schema = GEOMETRY_ARGS[geometryType];
19
- return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "label", children: "Type" }), _jsxs("select", { className: "select", value: geometryType, onChange: e => {
20
- const type = e.target.value;
21
- onUpdate({
22
- geometryType: type,
23
- args: GEOMETRY_ARGS[type].defaults,
24
- });
25
- }, children: [_jsx("option", { value: "box", children: "Box" }), _jsx("option", { value: "sphere", children: "Sphere" }), _jsx("option", { value: "plane", children: "Plane" })] }), schema.labels.map((label, i) => {
20
+ const selectStyle = {
21
+ width: '100%',
22
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
23
+ border: '1px solid rgba(34, 211, 238, 0.3)',
24
+ padding: '2px 4px',
25
+ fontSize: '10px',
26
+ color: 'rgba(165, 243, 252, 1)',
27
+ fontFamily: 'monospace',
28
+ outline: 'none',
29
+ };
30
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Type" }), _jsxs("select", { style: selectStyle, value: geometryType, onChange: e => {
31
+ const type = e.target.value;
32
+ onUpdate({ geometryType: type, args: GEOMETRY_ARGS[type].defaults });
33
+ }, children: [_jsx("option", { value: "box", children: "Box" }), _jsx("option", { value: "sphere", children: "Sphere" }), _jsx("option", { value: "plane", children: "Plane" })] })] }), schema.labels.map((label, i) => {
26
34
  var _a;
27
- return (_jsxs("div", { children: [_jsx("label", { className: "label", children: label }), _jsx("input", { type: "number", className: "input", value: (_a = args[i]) !== null && _a !== void 0 ? _a : schema.defaults[i], step: "0.1", onChange: e => {
35
+ return (_jsxs("div", { children: [_jsx(Label, { children: label }), _jsx(Input, { value: (_a = args[i]) !== null && _a !== void 0 ? _a : schema.defaults[i], step: "0.1", onChange: value => {
28
36
  const next = [...args];
29
- next[i] = parseFloat(e.target.value);
37
+ next[i] = value;
30
38
  onUpdate({ args: next });
31
39
  } })] }, label));
32
40
  })] }));
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ interface InputProps {
3
+ value: number;
4
+ onChange: (value: number) => void;
5
+ step?: string | number;
6
+ min?: number;
7
+ max?: number;
8
+ style?: React.CSSProperties;
9
+ }
10
+ export declare function Input({ value, onChange, step, min, max, style }: InputProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function Label({ children }: {
12
+ children: React.ReactNode;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ export declare function Vector3Input({ label, value, onChange }: {
15
+ label: string;
16
+ value: [number, number, number];
17
+ onChange: (v: [number, number, number]) => void;
18
+ }): import("react/jsx-runtime").JSX.Element;
19
+ export {};
@@ -0,0 +1,123 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ // Shared styles
4
+ const styles = {
5
+ input: {
6
+ width: '100%',
7
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
8
+ border: '1px solid rgba(34, 211, 238, 0.3)',
9
+ padding: '2px 4px',
10
+ fontSize: '10px',
11
+ color: 'rgba(165, 243, 252, 1)',
12
+ fontFamily: 'monospace',
13
+ outline: 'none',
14
+ },
15
+ label: {
16
+ display: 'block',
17
+ fontSize: '9px',
18
+ color: 'rgba(34, 211, 238, 0.6)',
19
+ textTransform: 'uppercase',
20
+ letterSpacing: '0.05em',
21
+ marginBottom: 2,
22
+ },
23
+ };
24
+ export function Input({ value, onChange, step, min, max, style }) {
25
+ return (_jsx("input", { type: "number", value: value, onChange: (e) => onChange(parseFloat(e.target.value)), step: step, min: min, max: max, style: Object.assign(Object.assign({}, styles.input), style) }));
26
+ }
27
+ export function Label({ children }) {
28
+ return _jsx("label", { style: styles.label, children: children });
29
+ }
30
+ export function Vector3Input({ label, value, onChange }) {
31
+ const [draft, setDraft] = useState(() => value.map(v => v.toString()));
32
+ // Sync external changes (gizmo, undo, etc.)
33
+ useEffect(() => {
34
+ setDraft(value.map(v => v.toString()));
35
+ }, [value[0], value[1], value[2]]);
36
+ const dragState = useRef(null);
37
+ const commit = (index) => {
38
+ const num = parseFloat(draft[index]);
39
+ if (Number.isFinite(num)) {
40
+ const next = [...value];
41
+ next[index] = num;
42
+ onChange(next);
43
+ }
44
+ };
45
+ const startScrub = (e, index) => {
46
+ e.preventDefault();
47
+ dragState.current = {
48
+ index,
49
+ startX: e.clientX,
50
+ startValue: value[index]
51
+ };
52
+ e.target.setPointerCapture(e.pointerId);
53
+ document.body.style.cursor = "ew-resize";
54
+ };
55
+ const onScrubMove = (e) => {
56
+ if (!dragState.current)
57
+ return;
58
+ const { index, startX, startValue } = dragState.current;
59
+ const dx = e.clientX - startX;
60
+ let speed = 0.02;
61
+ if (e.shiftKey)
62
+ speed *= 0.1; // fine
63
+ if (e.altKey)
64
+ speed *= 5; // coarse
65
+ const nextValue = startValue + dx * speed;
66
+ const next = [...value];
67
+ next[index] = nextValue;
68
+ setDraft(d => {
69
+ const copy = [...d];
70
+ copy[index] = nextValue.toFixed(3);
71
+ return copy;
72
+ });
73
+ onChange(next);
74
+ };
75
+ const endScrub = (e) => {
76
+ if (!dragState.current)
77
+ return;
78
+ dragState.current = null;
79
+ document.body.style.cursor = "";
80
+ e.target.releasePointerCapture(e.pointerId);
81
+ };
82
+ const axes = [
83
+ { key: "x", color: 'rgba(248, 113, 113, 1)', index: 0 },
84
+ { key: "y", color: 'rgba(134, 239, 172, 1)', index: 1 },
85
+ { key: "z", color: 'rgba(96, 165, 250, 1)', index: 2 }
86
+ ];
87
+ return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 4 }), children: label }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axes.map(({ key, color, index }) => (_jsxs("div", { style: {
88
+ flex: 1,
89
+ display: 'flex',
90
+ alignItems: 'center',
91
+ gap: 4,
92
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
93
+ border: '1px solid rgba(34, 211, 238, 0.2)',
94
+ borderRadius: 4,
95
+ padding: '4px 6px',
96
+ minHeight: 32,
97
+ }, children: [_jsx("span", { style: {
98
+ fontSize: '12px',
99
+ fontWeight: 'bold',
100
+ color,
101
+ width: 12,
102
+ cursor: 'ew-resize',
103
+ userSelect: 'none',
104
+ }, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: key.toUpperCase() }), _jsx("input", { style: {
105
+ flex: 1,
106
+ backgroundColor: 'transparent',
107
+ border: 'none',
108
+ fontSize: '12px',
109
+ color: 'rgba(165, 243, 252, 1)',
110
+ fontFamily: 'monospace',
111
+ outline: 'none',
112
+ width: '100%',
113
+ minWidth: 0,
114
+ }, type: "text", value: draft[index], onChange: e => {
115
+ const next = [...draft];
116
+ next[index] = e.target.value;
117
+ setDraft(next);
118
+ }, onBlur: () => commit(index), onKeyDown: e => {
119
+ if (e.key === "Enter") {
120
+ e.target.blur();
121
+ }
122
+ } })] }, key))) })] }));
123
+ }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { TextureListViewer } from '../../assetviewer/page';
3
3
  import { useEffect, useState } from 'react';
4
+ import { Input, Label } from './Input';
4
5
  function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
5
6
  var _a, _b, _c, _d;
6
7
  const [textureFiles, setTextureFiles] = useState([]);
@@ -11,14 +12,24 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
11
12
  .then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
12
13
  .catch(console.error);
13
14
  }, [basePath]);
14
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [_jsxs("div", { style: { marginBottom: 4 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) }), _jsx("input", { type: "text", style: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) })] })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4 }, children: [_jsx("input", { type: "checkbox", style: { width: 12, height: 12 }, checked: component.properties.wireframe || false, onChange: e => onUpdate({ 'wireframe': e.target.checked }) }), _jsx("label", { style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Wireframe" })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Texture" }), _jsx("div", { style: { maxHeight: 128, overflowY: 'auto' }, children: _jsx(TextureListViewer, { files: textureFiles, selected: component.properties.texture || undefined, onSelect: (file) => onUpdate({ 'texture': file }), basePath: basePath }) })] }), component.properties.texture && (_jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 4, marginTop: 4 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4 }, children: [_jsx("input", { type: "checkbox", style: { width: 12, height: 12 }, checked: component.properties.repeat || false, onChange: e => onUpdate({ 'repeat': e.target.checked }) }), _jsx("label", { style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Repeat Texture" })] }), component.properties.repeat && (_jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Repeat (X, Y)" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "number", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, onChange: e => {
15
+ const textInputStyle = {
16
+ flex: 1,
17
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
18
+ border: '1px solid rgba(34, 211, 238, 0.3)',
19
+ padding: '2px 4px',
20
+ fontSize: '10px',
21
+ color: 'rgba(165, 243, 252, 1)',
22
+ fontFamily: 'monospace',
23
+ outline: 'none',
24
+ };
25
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: component.properties.color, onChange: e => onUpdate({ color: e.target.value }) }), _jsx("input", { type: "text", style: textInputStyle, value: component.properties.color, onChange: e => onUpdate({ color: e.target.value }) })] })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("input", { type: "checkbox", style: { width: 12, height: 12 }, checked: component.properties.wireframe || false, onChange: e => onUpdate({ wireframe: e.target.checked }) }), _jsx("label", { style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Wireframe" })] }), _jsxs("div", { children: [_jsx(Label, { children: "Texture" }), _jsx("div", { style: { maxHeight: 128, overflowY: 'auto' }, children: _jsx(TextureListViewer, { files: textureFiles, selected: component.properties.texture || undefined, onSelect: file => onUpdate({ texture: file }), basePath: basePath }) })] }), component.properties.texture && (_jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 4, marginTop: 4 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4 }, children: [_jsx("input", { type: "checkbox", style: { width: 12, height: 12 }, checked: component.properties.repeat || false, onChange: e => onUpdate({ repeat: e.target.checked }) }), _jsx("label", { style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Repeat Texture" })] }), component.properties.repeat && (_jsxs("div", { children: [_jsx(Label, { children: "Repeat (X, Y)" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { value: (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, onChange: value => {
15
26
  var _a, _b;
16
27
  const y = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 1;
17
- onUpdate({ 'repeatCount': [parseFloat(e.target.value), y] });
18
- } }), _jsx("input", { type: "number", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: (_d = (_c = component.properties.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1, onChange: e => {
28
+ onUpdate({ repeatCount: [value, y] });
29
+ } }), _jsx(Input, { value: (_d = (_c = component.properties.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1, onChange: value => {
19
30
  var _a, _b;
20
31
  const x = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1;
21
- onUpdate({ 'repeatCount': [x, parseFloat(e.target.value)] });
32
+ onUpdate({ repeatCount: [x, value] });
22
33
  } })] })] }))] }))] }));
23
34
  }
24
35
  ;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ModelListViewer } from '../../assetviewer/page';
3
3
  import { useEffect, useState, useMemo } from 'react';
4
+ import { Label } from './Input';
4
5
  function ModelComponentEditor({ component, onUpdate, basePath = "" }) {
5
6
  const [modelFiles, setModelFiles] = useState([]);
6
7
  useEffect(() => {
@@ -15,7 +16,7 @@ function ModelComponentEditor({ component, onUpdate, basePath = "" }) {
15
16
  const filename = file.startsWith('/') ? file.slice(1) : file;
16
17
  onUpdate({ 'filename': filename });
17
18
  };
18
- return _jsxs("div", { children: [_jsxs("div", { style: { marginBottom: 4 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Model" }), _jsx("div", { style: { maxHeight: 128, overflowY: 'auto' }, children: _jsx(ModelListViewer, { files: modelFiles, selected: component.properties.filename ? `/${component.properties.filename}` : undefined, onSelect: handleModelSelect, basePath: basePath }) })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("input", { type: "checkbox", id: "instanced-checkbox", checked: component.properties.instanced || false, onChange: e => onUpdate({ 'instanced': e.target.checked }), style: { width: 12, height: 12 } }), _jsx("label", { htmlFor: "instanced-checkbox", style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Instanced" })] })] });
19
+ return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Model" }), _jsx("div", { style: { maxHeight: 128, overflowY: 'auto' }, children: _jsx(ModelListViewer, { files: modelFiles, selected: component.properties.filename ? `/${component.properties.filename}` : undefined, onSelect: handleModelSelect, basePath: basePath }) })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("input", { type: "checkbox", id: "instanced-checkbox", checked: component.properties.instanced || false, onChange: e => onUpdate({ instanced: e.target.checked }), style: { width: 12, height: 12 } }), _jsx("label", { htmlFor: "instanced-checkbox", style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Instanced" })] })] });
19
20
  }
20
21
  // View for Model component
21
22
  function ModelComponentView({ properties, loadedModels, children }) {
@@ -1,10 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { RigidBody } from "@react-three/rapier";
3
- const selectClass = { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' };
4
- const labelClass = { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 };
3
+ import { Label } from "./Input";
5
4
  function PhysicsComponentEditor({ component, onUpdate }) {
6
5
  const { type = 'dynamic', collider = 'hull' } = component.properties;
7
- return (_jsxs("div", { children: [_jsx("label", { style: labelClass, children: "Type" }), _jsxs("select", { style: selectClass, value: type, onChange: e => onUpdate({ type: e.target.value }), children: [_jsx("option", { value: "dynamic", children: "Dynamic" }), _jsx("option", { value: "fixed", children: "Fixed" })] }), _jsx("label", { style: Object.assign(Object.assign({}, labelClass), { marginTop: 8 }), children: "Collider" }), _jsxs("select", { style: selectClass, value: collider, onChange: e => onUpdate({ collider: e.target.value }), children: [_jsx("option", { value: "hull", children: "Hull (convex)" }), _jsx("option", { value: "trimesh", children: "Trimesh (exact)" }), _jsx("option", { value: "cuboid", children: "Cuboid (box)" }), _jsx("option", { value: "ball", children: "Ball (sphere)" })] })] }));
6
+ const selectStyle = {
7
+ width: '100%',
8
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
9
+ border: '1px solid rgba(34, 211, 238, 0.3)',
10
+ padding: '2px 4px',
11
+ fontSize: '10px',
12
+ color: 'rgba(165, 243, 252, 1)',
13
+ fontFamily: 'monospace',
14
+ outline: 'none',
15
+ };
16
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Type" }), _jsxs("select", { style: selectStyle, value: type, onChange: e => onUpdate({ type: e.target.value }), children: [_jsx("option", { value: "dynamic", children: "Dynamic" }), _jsx("option", { value: "fixed", children: "Fixed" })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Collider" }), _jsxs("select", { style: selectStyle, value: collider, onChange: e => onUpdate({ collider: e.target.value }), children: [_jsx("option", { value: "hull", children: "Hull (convex)" }), _jsx("option", { value: "trimesh", children: "Trimesh (exact)" }), _jsx("option", { value: "cuboid", children: "Cuboid (box)" }), _jsx("option", { value: "ball", children: "Ball (sphere)" })] })] })] }));
8
17
  }
9
18
  function PhysicsComponentView({ properties, editMode, children }) {
10
19
  if (editMode)
@@ -1,5 +1,6 @@
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 { Input, Label } from "./Input";
3
4
  function SpotLightComponentEditor({ component, onUpdate }) {
4
5
  var _a, _b, _c, _d, _e, _f;
5
6
  const props = {
@@ -10,7 +11,17 @@ function SpotLightComponentEditor({ component, onUpdate }) {
10
11
  distance: (_e = component.properties.distance) !== null && _e !== void 0 ? _e : 100,
11
12
  castShadow: (_f = component.properties.castShadow) !== null && _f !== void 0 ? _f : true
12
13
  };
13
- return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) }), _jsx("input", { type: "text", style: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) })] })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.intensity, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'intensity': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Angle" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: Math.PI, style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.angle, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'angle': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Penumbra" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.penumbra, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'penumbra': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Distance" }), _jsx("input", { type: "number", step: "1", min: "0", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.distance, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'distance': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Cast Shadow" }), _jsx("input", { type: "checkbox", style: { height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }, checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'castShadow': e.target.checked })) })] })] });
14
+ const textInputStyle = {
15
+ flex: 1,
16
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
17
+ border: '1px solid rgba(34, 211, 238, 0.3)',
18
+ padding: '2px 4px',
19
+ fontSize: '10px',
20
+ color: 'rgba(165, 243, 252, 1)',
21
+ fontFamily: 'monospace',
22
+ outline: 'none',
23
+ };
24
+ return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) }), _jsx("input", { type: "text", style: textInputStyle, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Intensity" }), _jsx(Input, { step: "0.1", value: props.intensity, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { intensity: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Angle" }), _jsx(Input, { step: "0.1", min: 0, max: Math.PI, value: props.angle, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { angle: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Penumbra" }), _jsx(Input, { step: "0.1", min: 0, max: 1, value: props.penumbra, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { penumbra: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Distance" }), _jsx(Input, { step: "1", min: 0, value: props.distance, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { distance: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Cast Shadow" }), _jsx("input", { type: "checkbox", style: { height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }, checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { castShadow: e.target.checked })) })] })] });
14
25
  }
15
26
  function SpotLightView({ properties, editMode }) {
16
27
  var _a, _b, _c, _d, _e, _f;
@@ -1,8 +1,3 @@
1
1
  import { Component } from "./ComponentRegistry";
2
2
  declare const TransformComponent: Component;
3
3
  export default TransformComponent;
4
- export declare function Vector3Input({ label, value, onChange }: {
5
- label: string;
6
- value: [number, number, number];
7
- onChange: (v: [number, number, number]) => void;
8
- }): import("react/jsx-runtime").JSX.Element;
@@ -1,27 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
2
+ import { Vector3Input, Label } from "./Input";
3
+ const buttonStyle = {
4
+ padding: '2px 6px',
5
+ background: 'transparent',
6
+ color: 'rgba(255,255,255,0.9)',
7
+ border: '1px solid rgba(255,255,255,0.14)',
8
+ borderRadius: 4,
9
+ cursor: 'pointer',
10
+ font: 'inherit',
11
+ flex: 1,
12
+ };
3
13
  function TransformComponentEditor({ component, onUpdate, transformMode, setTransformMode }) {
4
- const s = {
5
- button: {
6
- padding: '2px 6px',
7
- background: 'transparent',
8
- color: 'rgba(255,255,255,0.9)',
9
- border: '1px solid rgba(255,255,255,0.14)',
10
- borderRadius: 4,
11
- cursor: 'pointer',
12
- font: 'inherit',
13
- },
14
- buttonActive: {
15
- background: 'rgba(255,255,255,0.10)',
16
- },
17
- };
18
- return _jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [transformMode && setTransformMode && (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Transform Mode" }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => (_jsx("button", { onClick: () => setTransformMode(mode), style: Object.assign(Object.assign(Object.assign({}, s.button), { flex: 1 }), (transformMode === mode ? s.buttonActive : {})), onPointerEnter: (e) => {
19
- if (transformMode !== mode)
20
- e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
21
- }, onPointerLeave: (e) => {
22
- if (transformMode !== mode)
23
- e.currentTarget.style.background = 'transparent';
24
- }, children: mode }, mode))) })] })), _jsx(Vector3Input, { label: "Position", value: component.properties.position, onChange: v => onUpdate({ position: v }) }), _jsx(Vector3Input, { label: "Rotation", value: component.properties.rotation, onChange: v => onUpdate({ rotation: v }) }), _jsx(Vector3Input, { label: "Scale", value: component.properties.scale, onChange: v => onUpdate({ scale: v }) })] });
14
+ return _jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [transformMode && setTransformMode && (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx(Label, { children: "Transform Mode" }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => {
15
+ const isActive = transformMode === mode;
16
+ return (_jsx("button", { onClick: () => setTransformMode(mode), style: Object.assign(Object.assign({}, buttonStyle), { background: isActive ? 'rgba(255,255,255,0.10)' : 'transparent' }), onPointerEnter: (e) => {
17
+ if (!isActive)
18
+ e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
19
+ }, onPointerLeave: (e) => {
20
+ if (!isActive)
21
+ e.currentTarget.style.background = 'transparent';
22
+ }, children: mode }, mode));
23
+ }) })] })), _jsx(Vector3Input, { label: "Position", value: component.properties.position, onChange: v => onUpdate({ position: v }) }), _jsx(Vector3Input, { label: "Rotation", value: component.properties.rotation, onChange: v => onUpdate({ rotation: v }) }), _jsx(Vector3Input, { label: "Scale", value: component.properties.scale, onChange: v => onUpdate({ scale: v }) })] });
25
24
  }
26
25
  const TransformComponent = {
27
26
  name: 'Transform',
@@ -33,70 +32,3 @@ const TransformComponent = {
33
32
  }
34
33
  };
35
34
  export default TransformComponent;
36
- export function Vector3Input({ label, value, onChange }) {
37
- const [draft, setDraft] = useState(() => value.map(v => v.toString()));
38
- // Sync external changes (gizmo, undo, etc.)
39
- useEffect(() => {
40
- setDraft(value.map(v => v.toString()));
41
- }, [value[0], value[1], value[2]]);
42
- const dragState = useRef(null);
43
- const commit = (index) => {
44
- const num = parseFloat(draft[index]);
45
- if (Number.isFinite(num)) {
46
- const next = [...value];
47
- next[index] = num;
48
- onChange(next);
49
- }
50
- };
51
- const startScrub = (e, index) => {
52
- e.preventDefault();
53
- dragState.current = {
54
- index,
55
- startX: e.clientX,
56
- startValue: value[index]
57
- };
58
- e.target.setPointerCapture(e.pointerId);
59
- document.body.style.cursor = "ew-resize";
60
- };
61
- const onScrubMove = (e) => {
62
- if (!dragState.current)
63
- return;
64
- const { index, startX, startValue } = dragState.current;
65
- const dx = e.clientX - startX;
66
- let speed = 0.02;
67
- if (e.shiftKey)
68
- speed *= 0.1; // fine
69
- if (e.altKey)
70
- speed *= 5; // coarse
71
- const nextValue = startValue + dx * speed;
72
- const next = [...value];
73
- next[index] = nextValue;
74
- setDraft(d => {
75
- const copy = [...d];
76
- copy[index] = nextValue.toFixed(3);
77
- return copy;
78
- });
79
- onChange(next);
80
- };
81
- const endScrub = (e) => {
82
- if (!dragState.current)
83
- return;
84
- dragState.current = null;
85
- document.body.style.cursor = "";
86
- e.target.releasePointerCapture(e.pointerId);
87
- };
88
- const axes = [
89
- { key: "x", color: "red", index: 0 },
90
- { key: "y", color: "green", index: 1 },
91
- { key: "z", color: "blue", index: 2 }
92
- ];
93
- return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: label }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axes.map(({ key, color, index }) => (_jsxs("div", { style: { flex: 1, display: 'flex', alignItems: 'center', gap: 4, backgroundColor: 'rgba(0, 0, 0, 0.3)', border: '1px solid rgba(34, 211, 238, 0.2)', borderRadius: 4, padding: '4px 6px', minHeight: 32 }, children: [_jsx("span", { style: { fontSize: '12px', fontWeight: 'bold', color: color === 'red' ? 'rgba(248, 113, 113, 1)' : color === 'green' ? 'rgba(134, 239, 172, 1)' : 'rgba(96, 165, 250, 1)', width: 12, cursor: 'ew-resize', userSelect: 'none' }, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: key.toUpperCase() }), _jsx("input", { style: { flex: 1, backgroundColor: 'transparent', fontSize: '12px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none', width: '100%', minWidth: 0 }, type: "text", value: draft[index], onChange: e => {
94
- const next = [...draft];
95
- next[index] = e.target.value;
96
- setDraft(next);
97
- }, onBlur: () => commit(index), onKeyDown: e => {
98
- if (e.key === "Enter") {
99
- e.target.blur();
100
- }
101
- } })] }, key))) })] }));
102
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "description": "Batteries included React Three Fiber game engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",