babylon.quarks-editor 0.27.0 → 0.28.0

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 (138) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +31 -4
  3. package/dist/core/assetTree.d.ts +5 -0
  4. package/dist/core/assets.d.ts +7 -0
  5. package/dist/core/assets.js +11 -7
  6. package/dist/core/binding.d.ts +9 -0
  7. package/dist/core/binding.js +8 -0
  8. package/dist/core/collision.d.ts +4 -0
  9. package/dist/core/collision.js +4 -0
  10. package/dist/core/colors.d.ts +16 -0
  11. package/dist/core/colors.js +8 -0
  12. package/dist/core/defaultEffect.d.ts +3 -2
  13. package/dist/core/defaultEffect.js +1 -1
  14. package/dist/core/editorSession.d.ts +229 -0
  15. package/dist/core/editorSession.js +983 -0
  16. package/dist/core/effectTree.d.ts +5 -0
  17. package/dist/core/galleryPlayback.js +2 -1
  18. package/dist/core/history.d.ts +6 -0
  19. package/dist/core/history.js +4 -0
  20. package/dist/core/indexedDbProjectStore.d.ts +13 -0
  21. package/dist/core/indexedDbProjectStore.js +13 -0
  22. package/dist/core/material.d.ts +12 -2
  23. package/dist/core/playback.d.ts +6 -0
  24. package/dist/core/projectStore.d.ts +26 -0
  25. package/dist/core/projectStore.js +1 -0
  26. package/dist/core/sceneEntries.d.ts +3 -0
  27. package/dist/core/scrub.d.ts +1 -1
  28. package/dist/core/scrub.js +2 -2
  29. package/dist/core/shapes.d.ts +24 -0
  30. package/dist/core/shapes.js +5 -0
  31. package/dist/core/systems.d.ts +2 -0
  32. package/dist/core/systems.js +3 -1
  33. package/dist/core/timeline.d.ts +23 -3
  34. package/dist/core/values.d.ts +21 -4
  35. package/dist/core/values.js +5 -0
  36. package/dist/index.d.ts +5 -1
  37. package/dist/react/AssetsPanel.d.ts +7 -0
  38. package/dist/react/ChoiceDialog.d.ts +7 -0
  39. package/dist/react/ContextMenu.d.ts +10 -1
  40. package/dist/react/EditorWorkspace.d.ts +7 -0
  41. package/dist/react/EditorWorkspace.js +6 -3
  42. package/dist/react/EffectEditor.d.ts +8 -2
  43. package/dist/react/EffectEditorHost.d.ts +32 -28
  44. package/dist/react/EffectEditorHost.js +241 -1665
  45. package/dist/react/EffectTreeView.d.ts +3 -0
  46. package/dist/react/MessageDialog.d.ts +5 -0
  47. package/dist/react/ModuleSection.d.ts +10 -0
  48. package/dist/react/ModuleSection.js +4 -1
  49. package/dist/react/ProjectsPage.d.ts +6 -0
  50. package/dist/react/ProjectsPage.js +27 -11
  51. package/dist/react/PromptDialog.d.ts +7 -0
  52. package/dist/react/ScenePanel.d.ts +7 -0
  53. package/dist/react/ScenePanel.js +7 -5
  54. package/dist/react/TextureSelect.d.ts +5 -5
  55. package/dist/react/TimelinePanel.d.ts +13 -0
  56. package/dist/react/TimelinePanel.js +10 -6
  57. package/dist/react/TimelineTrackRow.d.ts +10 -0
  58. package/dist/react/TransportBar.d.ts +9 -0
  59. package/dist/react/ValueField.d.ts +32 -5
  60. package/dist/react/ValueField.js +20 -9
  61. package/dist/react/behaviorModules.d.ts +16 -5
  62. package/dist/react/behaviorModules.js +16 -1
  63. package/dist/react/fields.d.ts +8 -0
  64. package/dist/react/fields.js +3 -0
  65. package/dist/react/host/BottomDocks.d.ts +36 -0
  66. package/dist/react/host/BottomDocks.js +63 -0
  67. package/dist/react/host/HostDialogs.d.ts +50 -0
  68. package/dist/react/host/HostDialogs.js +113 -0
  69. package/dist/react/host/InspectorSidebar.d.ts +32 -0
  70. package/dist/react/host/InspectorSidebar.js +59 -0
  71. package/dist/react/host/SceneSidebar.d.ts +53 -0
  72. package/dist/react/host/SceneSidebar.js +62 -0
  73. package/dist/react/host/ViewportPane.d.ts +56 -0
  74. package/dist/react/host/ViewportPane.js +91 -0
  75. package/dist/react/host/hostHooks.d.ts +44 -0
  76. package/dist/react/host/hostHooks.js +164 -0
  77. package/dist/react/host/hostStyles.d.ts +21 -0
  78. package/dist/react/host/hostStyles.js +57 -0
  79. package/dist/react/index.d.ts +3 -1
  80. package/dist/react/modules.d.ts +17 -6
  81. package/dist/react/modules.js +38 -20
  82. package/dist/react/theme.d.ts +15 -0
  83. package/dist/react/theme.js +15 -0
  84. package/dist/session.d.ts +9 -0
  85. package/dist/session.js +8 -0
  86. package/package.json +17 -11
  87. package/src/core/assetTree.ts +14 -1
  88. package/src/core/assets.ts +25 -10
  89. package/src/core/binding.ts +9 -0
  90. package/src/core/collision.ts +4 -0
  91. package/src/core/colors.ts +21 -1
  92. package/src/core/defaultEffect.ts +4 -3
  93. package/src/core/editorSession.ts +1299 -0
  94. package/src/core/effectTree.ts +5 -0
  95. package/src/core/galleryPlayback.ts +2 -1
  96. package/src/core/history.ts +6 -0
  97. package/src/core/indexedDbProjectStore.ts +13 -0
  98. package/src/core/loadEffect.ts +1 -1
  99. package/src/core/material.ts +14 -4
  100. package/src/core/playback.ts +6 -0
  101. package/src/core/projectStore.ts +26 -0
  102. package/src/core/sceneEntries.ts +3 -0
  103. package/src/core/scrub.ts +2 -2
  104. package/src/core/shapes.ts +28 -1
  105. package/src/core/systems.ts +9 -2
  106. package/src/core/timeline.ts +40 -15
  107. package/src/core/values.ts +20 -4
  108. package/src/index.ts +5 -1
  109. package/src/react/AssetsPanel.tsx +7 -0
  110. package/src/react/ChoiceDialog.tsx +7 -0
  111. package/src/react/ContextMenu.tsx +10 -1
  112. package/src/react/EditorWorkspace.tsx +11 -5
  113. package/src/react/EffectEditor.tsx +7 -1
  114. package/src/react/EffectEditorHost.tsx +406 -2340
  115. package/src/react/EffectTreeView.tsx +3 -0
  116. package/src/react/MessageDialog.tsx +5 -0
  117. package/src/react/ModuleSection.tsx +11 -0
  118. package/src/react/ProjectsPage.tsx +38 -11
  119. package/src/react/PromptDialog.tsx +7 -0
  120. package/src/react/ScenePanel.tsx +14 -5
  121. package/src/react/TextureSelect.tsx +6 -6
  122. package/src/react/TimelinePanel.tsx +23 -6
  123. package/src/react/TimelineTrackRow.tsx +10 -0
  124. package/src/react/TransportBar.tsx +9 -0
  125. package/src/react/ValueField.tsx +58 -14
  126. package/src/react/behaviorModules.tsx +34 -32
  127. package/src/react/fields.tsx +12 -1
  128. package/src/react/host/BottomDocks.tsx +178 -0
  129. package/src/react/host/HostDialogs.tsx +228 -0
  130. package/src/react/host/InspectorSidebar.tsx +147 -0
  131. package/src/react/host/SceneSidebar.tsx +227 -0
  132. package/src/react/host/ViewportPane.tsx +300 -0
  133. package/src/react/host/hostHooks.ts +198 -0
  134. package/src/react/host/hostStyles.ts +65 -0
  135. package/src/react/index.ts +3 -1
  136. package/src/react/modules.tsx +70 -44
  137. package/src/react/theme.ts +15 -0
  138. package/src/session.ts +17 -0
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ArrowLeftIcon, ArrowPathIcon, ArrowsPointingOutIcon, ArrowUturnLeftIcon, ArrowUturnRightIcon, CubeTransparentIcon, CursorArrowRaysIcon, Square2StackIcon, } from '@heroicons/react/24/solid';
3
+ import { iconStyle } from '../icons.js';
4
+ import { buttonStyle, theme } from '../theme.js';
5
+ import { activeSquareButtonStyle, floatingPanelStyle, saveButtonStyle, squareButtonStyle } from './hostStyles.js';
6
+ const GIZMO_MODE_BUTTONS = [
7
+ { value: 'none', Icon: CursorArrowRaysIcon, title: 'Select (hide stage gizmo)' },
8
+ { value: 'position', Icon: ArrowsPointingOutIcon, title: 'Move the stage node (position)' },
9
+ { value: 'rotation', Icon: ArrowPathIcon, title: 'Rotate the stage node' },
10
+ { value: 'scale', Icon: Square2StackIcon, title: 'Scale the stage node' },
11
+ ];
12
+ /** The 3D viewport and what floats over it: toolbars, transport, drag and import feedback. */
13
+ export function ViewportPane({ canvasRef, fpsRef, inset, transport, dragHover, assetsLoading, prefabInfo, prefabDirty, backLink, canUndo, canRedo, hasFocus, gizmoMode, showWireframes, dragHandlers, onHistoryStep, onGizmoMode, onToggleWireframes, onExitPrefab, onSavePrefab, }) {
14
+ return (_jsxs("div", { style: { position: 'relative', flex: 1, minWidth: 0 }, ...dragHandlers, children: [_jsx("canvas", { ref: canvasRef, style: { width: '100%', height: '100%', display: 'block', touchAction: 'none', outline: 'none' } }), _jsx("div", { style: {
15
+ ...floatingPanelStyle,
16
+ position: 'absolute',
17
+ bottom: 12 + inset,
18
+ left: 12,
19
+ padding: '4px 10px',
20
+ fontSize: 12,
21
+ color: theme.textDim,
22
+ fontVariantNumeric: 'tabular-nums',
23
+ pointerEvents: 'none',
24
+ fontFamily: theme.font,
25
+ }, children: _jsx("span", { ref: fpsRef, children: "\u2014 FPS" }) }), transport && (_jsx("div", { style: { position: 'absolute', bottom: 12 + inset, left: '50%', transform: 'translateX(-50%)' }, children: transport })), dragHover && (_jsx("div", { style: {
26
+ position: 'absolute',
27
+ inset: 10,
28
+ border: '2px dashed rgba(120, 165, 255, 0.75)',
29
+ borderRadius: 12,
30
+ pointerEvents: 'none',
31
+ boxShadow: 'inset 0 0 0 1px rgba(120, 165, 255, 0.15)',
32
+ }, children: _jsxs("div", { style: {
33
+ position: 'absolute',
34
+ left: '50%',
35
+ top: 18,
36
+ transform: 'translateX(-50%)',
37
+ background: 'rgba(7, 11, 22, 0.88)',
38
+ border: `1px solid ${theme.border}`,
39
+ borderRadius: 8,
40
+ padding: '8px 14px',
41
+ fontSize: 13,
42
+ color: theme.text,
43
+ whiteSpace: 'nowrap',
44
+ }, children: ["Drop ", dragHover.count === 1 ? 'effect' : `${dragHover.count} effects`, " \u2014 ", dragHover.label] }) })), assetsLoading && (_jsx("div", { style: {
45
+ position: 'absolute',
46
+ inset: 0,
47
+ display: 'flex',
48
+ alignItems: 'center',
49
+ justifyContent: 'center',
50
+ background: 'rgba(7, 11, 22, 0.72)',
51
+ pointerEvents: 'none',
52
+ }, children: _jsxs("div", { style: { ...floatingPanelStyle, borderRadius: 10, padding: '16px 22px', textAlign: 'center' }, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 6 }, children: "Importing assets\u2026" }), _jsxs("div", { style: { fontSize: 13, color: theme.textDim }, children: [assetsLoading.done, " / ", assetsLoading.total || '…', " files", assetsLoading.failed > 0 ? ` · ${assetsLoading.failed} skipped` : ''] })] }) })), prefabInfo && (_jsxs("div", { style: {
53
+ ...floatingPanelStyle,
54
+ position: 'absolute',
55
+ top: 12,
56
+ left: '50%',
57
+ transform: 'translateX(-50%)',
58
+ display: 'flex',
59
+ alignItems: 'center',
60
+ gap: 8,
61
+ padding: '4px 4px 4px 6px',
62
+ border: `1px solid ${theme.borderActive}`,
63
+ fontSize: 12.5,
64
+ zIndex: 5,
65
+ }, children: [_jsx("button", { type: "button", className: "qe-hover", title: "Back to the scene", style: { ...buttonStyle, padding: '3px 10px' }, onClick: onExitPrefab, children: "\u2190 Scene" }), _jsx("span", { style: { color: theme.textDim }, children: "\u203A" }), _jsxs("span", { style: { fontWeight: 600, whiteSpace: 'nowrap' }, children: [prefabInfo.name, prefabDirty ? ' *' : ''] }), _jsx("button", { type: "button", className: "qe-hover", title: "Overwrite the prefab asset (Ctrl+S)", style: saveButtonStyle(prefabDirty, '3px 12px'), onClick: onSavePrefab, children: "Save" })] })), backLink && !prefabInfo && (_jsxs("a", { className: "qe-hover", href: backLink.href, style: {
66
+ ...floatingPanelStyle,
67
+ position: 'absolute',
68
+ top: 12,
69
+ left: 12,
70
+ display: 'inline-flex',
71
+ alignItems: 'center',
72
+ gap: 6,
73
+ padding: '6px 12px',
74
+ fontSize: 13,
75
+ fontWeight: 500,
76
+ color: theme.text,
77
+ textDecoration: 'none',
78
+ fontFamily: theme.font,
79
+ }, children: [_jsx(ArrowLeftIcon, { style: iconStyle(14) }), backLink.label.replace(/^←\s*/, '')] })), _jsxs("div", { style: {
80
+ position: 'absolute',
81
+ top: 12,
82
+ right: 12,
83
+ display: 'flex',
84
+ flexDirection: 'column',
85
+ alignItems: 'flex-end',
86
+ gap: 6,
87
+ }, children: [_jsxs("div", { style: { ...floatingPanelStyle, display: 'flex', gap: 4, padding: 4 }, children: [_jsx("button", { className: "qe-hover", style: squareButtonStyle, title: "Undo (Ctrl+Z)", disabled: !canUndo || !hasFocus, onClick: () => onHistoryStep(false), children: _jsx(ArrowUturnLeftIcon, { style: iconStyle(14) }) }), _jsx("button", { className: "qe-hover", style: squareButtonStyle, title: "Redo (Ctrl+Shift+Z)", disabled: !canRedo || !hasFocus, onClick: () => onHistoryStep(true), children: _jsx(ArrowUturnRightIcon, { style: iconStyle(14) }) })] }), _jsx("div", { style: { ...floatingPanelStyle, display: 'flex', flexDirection: 'column', gap: 4, padding: 4 }, children: GIZMO_MODE_BUTTONS.map((m) => (_jsx("button", { className: "qe-hover", style: {
88
+ ...squareButtonStyle,
89
+ ...(gizmoMode === m.value ? activeSquareButtonStyle : {}),
90
+ }, title: m.title, onClick: () => onGizmoMode(m.value), children: _jsx(m.Icon, { style: iconStyle(14) }) }, m.value))) }), _jsx("div", { style: { ...floatingPanelStyle, display: 'flex', flexDirection: 'column', gap: 4, padding: 4 }, children: _jsx("button", { className: "qe-hover", style: { ...squareButtonStyle, ...(showWireframes ? activeSquareButtonStyle : {}) }, title: "Show emitter shape wireframes", disabled: !hasFocus, onClick: onToggleWireframes, children: _jsx(CubeTransparentIcon, { style: iconStyle(14) }) }) })] })] }));
91
+ }
@@ -0,0 +1,44 @@
1
+ import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
2
+ import type { EditorSession } from '../../core/editorSession.js';
3
+ import type { GizmoMode } from './ViewportPane.js';
4
+ /** True for a field that takes typed text, where editor shortcuts stay out of the way. */
5
+ export declare function isTextInput(target: EventTarget | null): boolean;
6
+ /**
7
+ * Inspector value controls (numbers, checkboxes, selects): Ctrl+Z there means the effect's undo,
8
+ * not the browser's per-field text undo. Free-text inputs (names, filters) keep the native one.
9
+ */
10
+ export declare function isInspectorValueControl(target: EventTarget | null): boolean;
11
+ /** What the editor's keyboard shortcuts do. */
12
+ export interface HostShortcuts {
13
+ /** Ctrl+S: save the scene, or ask about the open prefab. */
14
+ save(): void;
15
+ /** Ctrl+Space: open or close the Assets drawer. */
16
+ toggleAssets(): void;
17
+ /** Space: play or pause the stage. */
18
+ togglePlayback(): void;
19
+ /** Escape: close the floating Assets drawer. */
20
+ closeDrawer(): void;
21
+ /** Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z. */
22
+ historyStep(redo: boolean): void;
23
+ /** Delete: remove the focused effect; returns whether it did. */
24
+ deleteFocused(): boolean;
25
+ }
26
+ /** Handles a keydown on the editor's window; exported for tests. */
27
+ export declare function handleHostShortcut(e: KeyboardEvent, shortcuts: HostShortcuts): void;
28
+ /** Installs the editor's keyboard shortcuts on the window; the latest `shortcuts` are always used. */
29
+ export declare function useHostShortcuts(shortcuts: HostShortcuts): void;
30
+ /**
31
+ * Puts the stage gizmo in `mode` and attaches it to `target`, leaving it where it is while
32
+ * `paused`. One gizmo drag is one undo step: the binding snapshots at drag start, and the UI
33
+ * refreshes at most once a frame while dragging.
34
+ */
35
+ export declare function useStageGizmo(session: EditorSession | null, mode: GizmoMode, target: TransformNode | null, paused: boolean): void;
36
+ /**
37
+ * The floating drawer is opaque, so the scene renders only in the part of the viewport above it:
38
+ * the view, and every "frame this effect" camera move, re-centres on what is visible. Measured
39
+ * again when the drawer floats or docks and when the timeline collapses. Returns the pixels the
40
+ * drawer covers.
41
+ */
42
+ export declare function useViewportInset(session: EditorSession | null, canvasRef: React.RefObject<HTMLCanvasElement | null>, areaRef: React.RefObject<HTMLDivElement | null>, drawerFloating: boolean, timelineCollapsed: boolean): number;
43
+ /** Asks before the page unloads while `dirty`. */
44
+ export declare function useUnsavedChangesGuard(dirty: boolean): void;
@@ -0,0 +1,164 @@
1
+ import { Viewport } from '@babylonjs/core/Maths/math.viewport.js';
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { notifyTransformChanged } from '../../core/transformStore.js';
4
+ import { DRAWER_HEIGHT } from './hostStyles.js';
5
+ /** True for a field that takes typed text, where editor shortcuts stay out of the way. */
6
+ export function isTextInput(target) {
7
+ const el = target;
8
+ return !!el && (el.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(el.tagName));
9
+ }
10
+ /**
11
+ * Inspector value controls (numbers, checkboxes, selects): Ctrl+Z there means the effect's undo,
12
+ * not the browser's per-field text undo. Free-text inputs (names, filters) keep the native one.
13
+ */
14
+ export function isInspectorValueControl(target) {
15
+ const el = target;
16
+ if (!el)
17
+ return false;
18
+ if (el.tagName === 'SELECT')
19
+ return true;
20
+ return el.tagName === 'INPUT' && ['number', 'checkbox', 'range', 'color'].includes(el.type);
21
+ }
22
+ /** Handles a keydown on the editor's window; exported for tests. */
23
+ export function handleHostShortcut(e, shortcuts) {
24
+ const key = e.key.toLowerCase();
25
+ const command = e.ctrlKey || e.metaKey;
26
+ if (command && key === 's') {
27
+ e.preventDefault();
28
+ shortcuts.save();
29
+ return;
30
+ }
31
+ if (e.code === 'Space' && command) {
32
+ e.preventDefault();
33
+ shortcuts.toggleAssets();
34
+ return;
35
+ }
36
+ const target = e.target;
37
+ if (e.code === 'Space' && !isTextInput(target) && target?.tagName !== 'BUTTON') {
38
+ e.preventDefault();
39
+ shortcuts.togglePlayback();
40
+ return;
41
+ }
42
+ if (e.key === 'Escape') {
43
+ shortcuts.closeDrawer();
44
+ }
45
+ if (command && (key === 'z' || key === 'y')) {
46
+ if (isTextInput(e.target) && !isInspectorValueControl(e.target)) {
47
+ return;
48
+ }
49
+ e.preventDefault();
50
+ // Leave the field first so it shows the restored value instead of its draft text.
51
+ e.target?.blur?.();
52
+ shortcuts.historyStep(key === 'y' || e.shiftKey);
53
+ return;
54
+ }
55
+ if (e.key === 'Delete' && !isTextInput(e.target) && shortcuts.deleteFocused()) {
56
+ e.preventDefault();
57
+ }
58
+ }
59
+ /** Installs the editor's keyboard shortcuts on the window; the latest `shortcuts` are always used. */
60
+ export function useHostShortcuts(shortcuts) {
61
+ const ref = useRef(shortcuts);
62
+ ref.current = shortcuts;
63
+ useEffect(() => {
64
+ const onKey = (e) => handleHostShortcut(e, ref.current);
65
+ window.addEventListener('keydown', onKey);
66
+ return () => window.removeEventListener('keydown', onKey);
67
+ }, []);
68
+ }
69
+ /**
70
+ * Puts the stage gizmo in `mode` and attaches it to `target`, leaving it where it is while
71
+ * `paused`. One gizmo drag is one undo step: the binding snapshots at drag start, and the UI
72
+ * refreshes at most once a frame while dragging.
73
+ */
74
+ export function useStageGizmo(session, mode, target, paused) {
75
+ useEffect(() => {
76
+ const gm = session?.gizmoManager;
77
+ if (!gm)
78
+ return;
79
+ gm.positionGizmoEnabled = mode === 'position';
80
+ gm.rotationGizmoEnabled = mode === 'rotation';
81
+ gm.scaleGizmoEnabled = mode === 'scale';
82
+ const activeGizmo = mode === 'position'
83
+ ? gm.gizmos.positionGizmo
84
+ : mode === 'rotation'
85
+ ? gm.gizmos.rotationGizmo
86
+ : mode === 'scale'
87
+ ? gm.gizmos.scaleGizmo
88
+ : null;
89
+ let dragRaf = 0;
90
+ const scheduleTransformRefresh = () => {
91
+ if (dragRaf !== 0) {
92
+ return;
93
+ }
94
+ dragRaf = requestAnimationFrame(() => {
95
+ dragRaf = 0;
96
+ notifyTransformChanged();
97
+ });
98
+ };
99
+ const dragStartObserver = activeGizmo?.onDragStartObservable.add(() => session.binding?.onBeforeChange?.());
100
+ const dragObserver = activeGizmo?.onDragObservable.add(scheduleTransformRefresh);
101
+ const dragEndObserver = activeGizmo?.onDragEndObservable.add(() => {
102
+ notifyTransformChanged();
103
+ session.binding?.markChanged();
104
+ });
105
+ return () => {
106
+ if (dragRaf !== 0) {
107
+ cancelAnimationFrame(dragRaf);
108
+ }
109
+ if (activeGizmo) {
110
+ activeGizmo.onDragStartObservable.remove(dragStartObserver ?? null);
111
+ activeGizmo.onDragObservable.remove(dragObserver ?? null);
112
+ activeGizmo.onDragEndObservable.remove(dragEndObserver ?? null);
113
+ }
114
+ };
115
+ }, [session, mode]);
116
+ useEffect(() => {
117
+ if (!paused) {
118
+ session?.gizmoManager.attachToNode(target);
119
+ }
120
+ }, [session, target, paused]);
121
+ }
122
+ /**
123
+ * The floating drawer is opaque, so the scene renders only in the part of the viewport above it:
124
+ * the view, and every "frame this effect" camera move, re-centres on what is visible. Measured
125
+ * again when the drawer floats or docks and when the timeline collapses. Returns the pixels the
126
+ * drawer covers.
127
+ */
128
+ export function useViewportInset(session, canvasRef, areaRef, drawerFloating, timelineCollapsed) {
129
+ const [inset, setInset] = useState(0);
130
+ useEffect(() => {
131
+ const update = () => {
132
+ const camera = session?.camera;
133
+ const canvas = canvasRef.current;
134
+ const area = areaRef.current;
135
+ if (!camera || !canvas || !area)
136
+ return;
137
+ const canvasRect = canvas.getBoundingClientRect();
138
+ const drawerTop = area.getBoundingClientRect().bottom - DRAWER_HEIGHT;
139
+ const covered = drawerFloating
140
+ ? Math.max(0, Math.min(canvasRect.height * 0.8, canvasRect.bottom - drawerTop))
141
+ : 0;
142
+ const fraction = canvasRect.height > 0 ? covered / canvasRect.height : 0;
143
+ camera.viewport = new Viewport(0, fraction, 1, 1 - fraction);
144
+ setInset(covered);
145
+ };
146
+ update();
147
+ window.addEventListener('resize', update);
148
+ return () => window.removeEventListener('resize', update);
149
+ }, [session, canvasRef, areaRef, drawerFloating, timelineCollapsed]);
150
+ return inset;
151
+ }
152
+ /** Asks before the page unloads while `dirty`. */
153
+ export function useUnsavedChangesGuard(dirty) {
154
+ useEffect(() => {
155
+ if (!dirty)
156
+ return;
157
+ const onBeforeUnload = (e) => {
158
+ e.preventDefault();
159
+ e.returnValue = '';
160
+ };
161
+ window.addEventListener('beforeunload', onBeforeUnload);
162
+ return () => window.removeEventListener('beforeunload', onBeforeUnload);
163
+ }, [dirty]);
164
+ }
@@ -0,0 +1,21 @@
1
+ import type React from 'react';
2
+ /** Width of the Scene list on the left. */
3
+ export declare const SCENE_PANEL_WIDTH = 280;
4
+ /** Width of the inspector on the right. */
5
+ export declare const INSPECTOR_WIDTH = 340;
6
+ /** Height of the timeline dock when expanded. */
7
+ export declare const TIMELINE_HEIGHT = 200;
8
+ /** Height of the Assets drawer. */
9
+ export declare const DRAWER_HEIGHT = 280;
10
+ /** A square icon button of the viewport toolbars. */
11
+ export declare const squareButtonStyle: React.CSSProperties;
12
+ /** Added to a toolbar button that is switched on. */
13
+ export declare const activeSquareButtonStyle: React.CSSProperties;
14
+ /** Title of a dock (Assets, Timeline). */
15
+ export declare const dockTitleStyle: React.CSSProperties;
16
+ /** A small icon button in a dock's header. */
17
+ export declare const dockIconButtonStyle: React.CSSProperties;
18
+ /** A floating panel over the viewport. */
19
+ export declare const floatingPanelStyle: React.CSSProperties;
20
+ /** A Save button, highlighted while there is something to save. */
21
+ export declare function saveButtonStyle(pending: boolean, padding: string): React.CSSProperties;
@@ -0,0 +1,57 @@
1
+ import { buttonStyle, theme } from '../theme.js';
2
+ /** Width of the Scene list on the left. */
3
+ export const SCENE_PANEL_WIDTH = 280;
4
+ /** Width of the inspector on the right. */
5
+ export const INSPECTOR_WIDTH = 340;
6
+ /** Height of the timeline dock when expanded. */
7
+ export const TIMELINE_HEIGHT = 200;
8
+ /** Height of the Assets drawer. */
9
+ export const DRAWER_HEIGHT = 280;
10
+ /** A square icon button of the viewport toolbars. */
11
+ export const squareButtonStyle = {
12
+ ...buttonStyle,
13
+ width: 28,
14
+ height: 28,
15
+ padding: 0,
16
+ display: 'flex',
17
+ alignItems: 'center',
18
+ justifyContent: 'center',
19
+ fontWeight: 700,
20
+ };
21
+ /** Added to a toolbar button that is switched on. */
22
+ export const activeSquareButtonStyle = {
23
+ border: `1px solid ${theme.borderActive}`,
24
+ background: 'rgba(120, 165, 255, 0.35)',
25
+ };
26
+ /** Title of a dock (Assets, Timeline). */
27
+ export const dockTitleStyle = {
28
+ fontSize: 11,
29
+ fontWeight: 600,
30
+ letterSpacing: 0.4,
31
+ textTransform: 'uppercase',
32
+ color: theme.text,
33
+ };
34
+ /** A small icon button in a dock's header. */
35
+ export const dockIconButtonStyle = {
36
+ ...buttonStyle,
37
+ width: 24,
38
+ height: 22,
39
+ padding: 0,
40
+ display: 'inline-flex',
41
+ alignItems: 'center',
42
+ justifyContent: 'center',
43
+ };
44
+ /** A floating panel over the viewport. */
45
+ export const floatingPanelStyle = {
46
+ background: theme.panelBg,
47
+ border: `1px solid ${theme.border}`,
48
+ borderRadius: 8,
49
+ };
50
+ /** A Save button, highlighted while there is something to save. */
51
+ export function saveButtonStyle(pending, padding) {
52
+ return {
53
+ ...buttonStyle,
54
+ padding,
55
+ ...(pending ? { background: theme.accent, color: '#0a1128', border: `1px solid ${theme.accent}` } : {}),
56
+ };
57
+ }
@@ -11,7 +11,7 @@ export type { EditorWorkspaceProps } from './EditorWorkspace.js';
11
11
  export { EffectEditor } from './EffectEditor.js';
12
12
  export type { EffectEditorProps } from './EffectEditor.js';
13
13
  export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost.js';
14
- export type { EffectEditorHostHandle, EffectEditorHostProps } from './EffectEditorHost.js';
14
+ export type { EditorProject, EffectEditorHostHandle, EffectEditorHostProps } from './EffectEditorHost.js';
15
15
  export { EffectTreeView } from './EffectTreeView.js';
16
16
  export type { EffectTreeViewProps } from './EffectTreeView.js';
17
17
  export { CheckboxField, NumberField, Row, SelectField } from './fields.js';
@@ -21,6 +21,7 @@ export type { MessageDialogProps } from './MessageDialog.js';
21
21
  export { ColorOverLifeModule, EmissionModule, MainModule, RendererModule, ShapeModule, SizeOverLifeModule, } from './modules.js';
22
22
  export type { GeometryData, GeometryOption, TextureOption } from './modules.js';
23
23
  export { ModuleSection } from './ModuleSection.js';
24
+ export type { ModuleProps } from './ModuleSection.js';
24
25
  export { ProjectsPage } from './ProjectsPage.js';
25
26
  export type { ProjectsPageProps } from './ProjectsPage.js';
26
27
  export { PromptDialog } from './PromptDialog.js';
@@ -38,3 +39,4 @@ export type { TimelineTrackRowProps } from './TimelineTrackRow.js';
38
39
  export { TransportBar } from './TransportBar.js';
39
40
  export type { TransportBarProps } from './TransportBar.js';
40
41
  export { ValueField } from './ValueField.js';
42
+ export type { ConstantValueFieldProps, CurveValueFieldProps } from './ValueField.js';
@@ -1,15 +1,21 @@
1
+ import { Texture } from '@babylonjs/core/Materials/Textures/texture.js';
1
2
  import React from 'react';
2
- import { EffectBinding } from '../core/binding.js';
3
- interface ModuleProps {
4
- binding: EffectBinding;
5
- }
3
+ import { type ModuleProps } from './ModuleSection.js';
4
+ /** The Main module (Unity's main module): duration, looping, start delay, life, speed, size, rotation and colour. */
6
5
  export declare function MainModule({ binding }: ModuleProps): React.JSX.Element;
6
+ /** The Emission module (Unity's Emission): rate over time and distance, and bursts. */
7
7
  export declare function EmissionModule({ binding }: ModuleProps): React.JSX.Element;
8
+ /** The Shape module (Unity's Shape): the emitter shape and its settings. */
8
9
  export declare function ShapeModule({ binding }: ModuleProps): React.JSX.Element;
10
+ /** The Size over Lifetime module (Unity's Size over Lifetime): size by a curve over the particle's life. */
9
11
  export declare function SizeOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
12
+ /** The Color over Lifetime module (Unity's Color over Lifetime): a gradient over the particle's life. */
10
13
  export declare function ColorOverLifeModule({ binding }: ModuleProps): React.JSX.Element;
14
+ /** A texture preset the host offers in the Renderer module's texture menus. */
11
15
  export interface TextureOption {
16
+ /** Its label in the menu. */
12
17
  label: string;
18
+ /** The URL the host's texture loader receives. */
13
19
  url: string;
14
20
  }
15
21
  /**
@@ -18,20 +24,25 @@ export interface TextureOption {
18
24
  * them into the system's instancing geometry.
19
25
  */
20
26
  export interface GeometryOption {
27
+ /** Its label in the menu. */
21
28
  label: string;
29
+ /** Vertex positions, three values per vertex. */
22
30
  positions: Float32Array | number[];
31
+ /** Triangle indices; without them the vertices are drawn in sequence. */
23
32
  indices?: Uint32Array | number[];
33
+ /** The first UV set, two values per vertex; without it the mesh samples UV (0, 0). */
24
34
  uvs?: Float32Array | number[];
25
35
  /** A second UV set (glTF `TEXCOORD_1`), what textures on UV channel 1 read on the mesh. */
26
36
  uvs2?: Float32Array | number[];
37
+ /** Vertex normals, three values per vertex; without them lighting uses +Z. */
27
38
  normals?: Float32Array | number[];
28
39
  }
29
40
  /** Vertex buffers a host loader (e.g. a GLB importer) returns for the Mesh render mode. */
30
41
  export type GeometryData = Omit<GeometryOption, 'label'>;
42
+ /** The Renderer module (Unity's Renderer): render mode, material, textures, sorting and mesh settings. */
31
43
  export declare function RendererModule({ binding, textureOptions, geometryOptions, resolveTexture, resolveGeometry, }: ModuleProps & {
32
44
  textureOptions?: TextureOption[];
33
45
  geometryOptions?: GeometryOption[];
34
- resolveTexture?: (url: string) => unknown;
46
+ resolveTexture?: (url: string) => Texture | null | undefined;
35
47
  resolveGeometry?: (file: File) => Promise<GeometryData>;
36
48
  }): React.JSX.Element;
37
- export {};
@@ -99,17 +99,19 @@ function ColorInput(props) {
99
99
  cursor: 'pointer',
100
100
  } }), _jsx(NumberField, { value: value.w, min: 0, step: 0.05, onChange: (a) => onChange(new Vector4(value.x, value.y, value.z, Math.min(1, a))) })] }));
101
101
  }
102
+ /** The Main module (Unity's main module): duration, looping, start delay, life, speed, size, rotation and colour. */
102
103
  export function MainModule({ binding }) {
103
104
  const system = binding.system;
104
105
  const startColor = system.startColor;
105
106
  const colorMode = startColor instanceof RandomColor ? 'random' : startColor instanceof ColorRange ? 'range' : 'constant';
106
- const twoColors = startColor instanceof RandomColor || startColor instanceof ColorRange;
107
- const colorA = twoColors
108
- ? startColor.a
107
+ const twoColor = startColor instanceof RandomColor || startColor instanceof ColorRange ? startColor : null;
108
+ const colorA = twoColor
109
+ ? twoColor.a
109
110
  : startColor instanceof ConstantColor
110
111
  ? startColor.color
111
112
  : new Vector4(1, 1, 1, 1);
112
- const colorB = twoColors ? startColor.b : new Vector4(1, 0.5, 0.2, 1);
113
+ const colorB = twoColor ? twoColor.b : new Vector4(1, 0.5, 0.2, 1);
114
+ const twoColors = twoColor !== null;
113
115
  /** The two-colour generator of the current mode. */
114
116
  const makeTwoColors = (a, b) => colorMode === 'range' ? new ColorRange(a, b) : new RandomColor(a, b);
115
117
  const rotation = system.startRotation;
@@ -135,8 +137,7 @@ export function MainModule({ binding }) {
135
137
  const cur = s.startRotation;
136
138
  s.startRotation = new EulerGenerator(axis === 'x' ? new ConstantValue(v) : cur.angleX, axis === 'y' ? new ConstantValue(v) : cur.angleY, axis === 'z' ? new ConstantValue(v) : cur.angleZ);
137
139
  });
138
- const startSize = system.startSize;
139
- const size3D = startSize.type === 'vec3function';
140
+ const startSize3D = system.startSize instanceof Vector3Function ? system.startSize : null;
140
141
  const colorGenMode = startColor instanceof RandomColorBetweenGradient
141
142
  ? 'gradient2'
142
143
  : startColor instanceof Gradient
@@ -152,20 +153,20 @@ export function MainModule({ binding }) {
152
153
  ], onChange: (mode) => binding.apply((s) => (s.cullingMode = mode)) }) }), _jsx(ValueField, { label: "Start delay", generator: system.startDelay, min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => {
153
154
  s.startDelay = g;
154
155
  s.restart();
155
- }) }), _jsx(ValueField, { label: "Start lifetime", generator: system.startLife, min: 0.01, curveMax: 10, onChange: (g) => binding.apply((s) => (s.startLife = g)) }), _jsx(ValueField, { label: "Start speed", generator: system.startSpeed, min: 0, curveMax: 20, onChange: (g) => binding.apply((s) => (s.startSpeed = g)) }), _jsx(Row, { label: "Start size", children: _jsx(SelectField, { value: size3D ? '3d' : 'uniform', options: [
156
+ }) }), _jsx(ValueField, { label: "Start lifetime", generator: system.startLife, min: 0.01, curveMax: 10, onChange: (g) => binding.apply((s) => (s.startLife = g)) }), _jsx(ValueField, { label: "Start speed", generator: system.startSpeed, min: 0, curveMax: 20, onChange: (g) => binding.apply((s) => (s.startSpeed = g)) }), _jsx(Row, { label: "Start size", children: _jsx(SelectField, { value: startSize3D ? '3d' : 'uniform', options: [
156
157
  { value: 'uniform', label: 'Uniform' },
157
158
  { value: '3d', label: '3D (per axis)' },
158
159
  ], onChange: (mode) => binding.apply((s) => {
159
160
  const cur = s.startSize;
160
- if (mode === '3d') {
161
+ if (mode === '3d' && cur.type !== 'vec3function') {
161
162
  // Seed all three axes from the current uniform generator.
162
163
  s.startSize = new Vector3Function(cur.clone(), cur.clone(), cur.clone());
163
164
  }
164
- else {
165
+ else if (mode !== '3d' && cur instanceof Vector3Function) {
165
166
  // Collapse back to the X-axis generator.
166
- s.startSize = s.startSize.x;
167
+ s.startSize = cur.x;
167
168
  }
168
- }) }) }), !size3D && (_jsx(ValueField, { label: "", generator: system.startSize, min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => (s.startSize = g)) })), size3D && (_jsx(_Fragment, { children: ['x', 'y', 'z'].map((axis) => (_jsx(ValueField, { label: axis.toUpperCase(), generator: startSize[axis], min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => {
169
+ }) }) }), !startSize3D && (_jsx(ValueField, { label: "", generator: system.startSize, min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => (s.startSize = g)) })), startSize3D && (_jsx(_Fragment, { children: ['x', 'y', 'z'].map((axis) => (_jsx(ValueField, { label: axis.toUpperCase(), generator: startSize3D[axis], min: 0, curveMax: 5, onChange: (g) => binding.apply((s) => {
169
170
  const cur = s.startSize;
170
171
  s.startSize = new Vector3Function(axis === 'x' ? g : cur.x, axis === 'y' ? g : cur.y, axis === 'z' ? g : cur.z);
171
172
  }) }, axis))) })), _jsx(Row, { label: "Start color", children: _jsx(SelectField, { value: colorGenMode, options: [
@@ -221,6 +222,7 @@ export function MainModule({ binding }) {
221
222
  s.startRotation = new AxisAngleGenerator(next, axisAngle.angle);
222
223
  }) }, axis))) }) }), _jsx(ValueField, { label: " Angle (rad)", generator: axisAngle.angle, curveMax: Math.PI * 2, onChange: (g) => binding.apply((s) => (s.startRotation = new AxisAngleGenerator(axisAngle.axis.clone(), g))) })] })), rotationMode === 'angle' && (_jsx(Row, { label: "", children: _jsx(NumberField, { value: rotationState.value, step: 0.1, onChange: (v) => binding.apply((s) => (s.startRotation = new ConstantValue(v))) }) })), rotationMode === 'random' && (_jsx(Row, { label: "", children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: rotationState.min, step: 0.1, onChange: (v) => binding.apply((s) => (s.startRotation = new IntervalValue(v, rotationState.max))) }), _jsx(NumberField, { value: rotationState.max, step: 0.1, onChange: (v) => binding.apply((s) => (s.startRotation = new IntervalValue(rotationState.min, v))) })] }) }))] }));
223
224
  }
225
+ /** The Emission module (Unity's Emission): rate over time and distance, and bursts. */
224
226
  export function EmissionModule({ binding }) {
225
227
  const system = binding.system;
226
228
  const bursts = system.emissionBursts ?? [];
@@ -255,6 +257,7 @@ export function EmissionModule({ binding }) {
255
257
  // for them would edit a value the shape silently ignores, just like the old "default to
256
258
  // first texture" bug: a control that looks live but does nothing.
257
259
  const SHAPES_WITHOUT_MODE_SPREAD = new Set(['point', 'grid', 'box', 'mesh_surface']);
260
+ /** The Shape module (Unity's Shape): the emitter shape and its settings. */
258
261
  export function ShapeModule({ binding }) {
259
262
  const system = binding.system;
260
263
  const type = getShapeType(system.emitterShape);
@@ -280,12 +283,11 @@ export function ShapeModule({ binding }) {
280
283
  { value: 3, label: 'Burst spread' },
281
284
  ], onChange: (mode) => binding.apply((s) => (s.emitterShape = createShape(type, { ...params, mode }))) }) }), _jsx(Row, { label: "Spread", children: _jsx(NumberField, { value: params.spread, min: 0, step: 0.05, onChange: (spread) => binding.apply((s) => (s.emitterShape = createShape(type, { ...params, spread }))) }) })] })), _jsx(Row, { label: "Align to direction", children: _jsx(CheckboxField, { value: binding.system.alignToDirection, onChange: (v) => binding.apply((s) => (s.alignToDirection = v)) }) })] }));
282
285
  }
286
+ /** The Size over Lifetime module (Unity's Size over Lifetime): size by a curve over the particle's life. */
283
287
  export function SizeOverLifeModule({ binding }) {
284
288
  const system = binding.system;
285
289
  const behavior = findBehavior(system.behaviors, 'SizeOverLife');
286
- const curve = behavior
287
- ? readScalar(behavior.size).curve
288
- : [0.2, 1, 1, 0.4];
290
+ const curve = behavior ? readScalar(behavior.size).curve : [0.2, 1, 1, 0.4];
289
291
  return (_jsx(ModuleSection, { title: "Size over Lifetime", enabled: !!behavior, onToggle: (enabled) => binding.apply((s) => {
290
292
  removeBehavior(s, 'SizeOverLife');
291
293
  if (enabled) {
@@ -296,6 +298,7 @@ export function SizeOverLifeModule({ binding }) {
296
298
  s.addBehavior(new SizeOverLife(buildCurve(pieces)));
297
299
  }) }) }) }));
298
300
  }
301
+ /** The Color over Lifetime module (Unity's Color over Lifetime): a gradient over the particle's life. */
299
302
  export function ColorOverLifeModule({ binding }) {
300
303
  const system = binding.system;
301
304
  const behavior = findBehavior(system.behaviors, 'ColorOverLife');
@@ -376,6 +379,7 @@ function MeshPreview({ positions, indices, size = 96, }) {
376
379
  }, [positions, indices, size]);
377
380
  return (_jsx("canvas", { ref: canvasRef, width: size, height: size, style: { width: size, height: size, border: '1px solid #2b3761', borderRadius: 6, background: '#0c1330' } }));
378
381
  }
382
+ /** The Renderer module (Unity's Renderer): render mode, material, textures, sorting and mesh settings. */
379
383
  export function RendererModule({ binding, textureOptions, geometryOptions, resolveTexture, resolveGeometry, }) {
380
384
  const system = binding.system;
381
385
  const materialLabel = getMaterialLabel(system.material);
@@ -470,13 +474,23 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
470
474
  const triangleCount = (system.getRendererSettings().instancingIndices?.length ?? 0) / 3;
471
475
  const hostGeometry = geometryOptions ?? [];
472
476
  const applyGeometry = (g) => binding.apply((s) => {
477
+ // The previous mesh's sets go first, so the positions setter gives
478
+ // this mesh the library's own fallback UVs and normals; then every set
479
+ // this mesh has. An unindexed mesh draws its vertices in sequence.
473
480
  const settings = s.getRendererSettings();
474
- // Positions first: the setter keeps or drops the other sets by vertex count.
481
+ settings.instancingUVs = undefined;
482
+ settings.instancingUVs2 = undefined;
483
+ settings.instancingNormals = undefined;
475
484
  s.instancingGeometry = new Float32Array(g.positions);
476
- settings.instancingIndices = (g.indices ? new Uint32Array(g.indices) : undefined);
477
- settings.instancingUVs = (g.uvs ? new Float32Array(g.uvs) : undefined);
478
- settings.instancingUVs2 = (g.uvs2 ? new Float32Array(g.uvs2) : undefined);
479
- settings.instancingNormals = (g.normals ? new Float32Array(g.normals) : undefined);
485
+ s.instancingIndices = g.indices
486
+ ? new Uint32Array(g.indices)
487
+ : Uint32Array.from({ length: g.positions.length / 3 }, (_, i) => i);
488
+ if (g.uvs)
489
+ s.instancingUVs = new Float32Array(g.uvs);
490
+ if (g.uvs2)
491
+ s.instancingUVs2 = new Float32Array(g.uvs2);
492
+ if (g.normals)
493
+ settings.instancingNormals = new Float32Array(g.normals);
480
494
  });
481
495
  return (_jsx(Row, { label: "Geometry", children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(SelectField, { value: preset, options: [
482
496
  ...(preset === 'custom' ? [{ value: 'custom', label: 'Custom (imported)' }] : []),
@@ -547,7 +561,11 @@ export function RendererModule({ binding, textureOptions, geometryOptions, resol
547
561
  }) }, axis))) }) })), system.renderMode === RenderMode.Mesh && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Env map atlas", children: _jsx(SelectField, { value: system.getRendererSettings().reflectionAtlas?.url ?? '', options: [
548
562
  { value: '', label: 'None' },
549
563
  ...(textureOptions ?? []).map((option) => ({ value: option.url, label: option.label })),
550
- ], onChange: (url) => patchMaterial({ reflectionAtlas: url ? loadTexture(url) : null }) }) }), _jsx(Row, { label: "Reflection level", children: _jsx(NumberField, { value: system.getRendererSettings().reflectionLevel ?? 1, min: 0, step: 0.1, onChange: (v) => patchMaterial({ reflectionLevel: Math.max(0, v) }) }) })] })), _jsx(Row, { label: "Sort mode", children: _jsx(SelectField, { value: system.sortMode, options: [
564
+ ], onChange: (url) => patchMaterial({ reflectionAtlas: url ? loadTexture(url) : null }) }) }), _jsx(Row, { label: "Reflection level", children: _jsx(NumberField, { value: system.getRendererSettings().reflectionLevel ?? 1, min: 0, step: 0.1, onChange: (v) => patchMaterial({ reflectionLevel: Math.max(0, v) }) }) })] })), isMesh && (_jsx(Row, { label: "Render face", children: _jsx(SelectField, { value: system.renderFace, options: [
565
+ { value: 'both', label: 'Both' },
566
+ { value: 'front', label: 'Front' },
567
+ { value: 'back', label: 'Back' },
568
+ ], onChange: (face) => binding.apply((s) => (s.renderFace = face)) }) })), _jsx(Row, { label: "Sort mode", children: _jsx(SelectField, { value: system.sortMode, options: [
551
569
  { value: 'none', label: 'None' },
552
570
  { value: 'distance', label: 'By distance' },
553
571
  { value: 'oldestInFront', label: 'Oldest in front' },
@@ -1,19 +1,34 @@
1
1
  import type { CSSProperties } from 'react';
2
+ /** The editor's colours and font, for hosts that style their own panels to match. */
2
3
  export declare const theme: {
4
+ /** Background of the side panels and docks. */
3
5
  panelBg: string;
6
+ /** Background of an inspector module or a framed list. */
4
7
  sectionBg: string;
8
+ /** Borders and separators. */
5
9
  border: string;
10
+ /** The border of a selected or switched-on control. */
6
11
  borderActive: string;
12
+ /** Body text. */
7
13
  text: string;
14
+ /** Labels and secondary text. */
8
15
  textDim: string;
16
+ /** Highlights, such as a Save button with something to save. */
9
17
  accent: string;
18
+ /** Background of inputs. */
10
19
  inputBg: string;
20
+ /** The line of curve editors. */
11
21
  curveStroke: string;
22
+ /** The editor's font stack. */
12
23
  font: string;
13
24
  };
25
+ /** Style of a text or number input. */
14
26
  export declare const inputStyle: CSSProperties;
27
+ /** Style of an inspector row: a label column, then the controls. */
15
28
  export declare const rowStyle: CSSProperties;
29
+ /** Style of an inspector row's label. */
16
30
  export declare const labelStyle: CSSProperties;
31
+ /** Style of a button. */
17
32
  export declare const buttonStyle: CSSProperties;
18
33
  /** Visually hides a file input while keeping it activatable via an associated <label>. */
19
34
  export declare const hiddenFileInputStyle: CSSProperties;