quilt-three 0.10.0 → 0.11.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.
- package/README.md +12 -11
- package/package.json +4 -25
- package/dist/react.d.ts +0 -28
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js +0 -247
- package/dist/react.js.map +0 -1
- package/src/react.tsx +0 -298
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# quilt-three
|
|
2
2
|
|
|
3
|
-
Shared-scene Three.js viewports for Quilt, with
|
|
3
|
+
Shared-scene Three.js viewports for Quilt, with React Three Fiber bindings available separately in `quilt-three-react`.
|
|
4
4
|
The main workspace shares one canvas and renderer. Each same-origin companion document
|
|
5
5
|
has its own renderer. Geometry, authored materials, and camera poses remain
|
|
6
6
|
application-owned; moving a pane does not delete scene data.
|
|
@@ -8,20 +8,21 @@ application-owned; moving a pane does not delete scene data.
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
npm install quilt-three
|
|
12
|
-
npm install --save-dev @types/three
|
|
11
|
+
npm install quilt-three
|
|
13
12
|
```
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
With modern npm's default peer resolution, this installs the Quilt renderer,
|
|
15
|
+
Three.js r186, and its TypeScript declarations automatically. React and R3F are
|
|
16
|
+
not installed. For React applications instead use:
|
|
16
17
|
|
|
17
18
|
```sh
|
|
18
|
-
npm install quilt-
|
|
19
|
+
npm install quilt-three-react
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
React 19
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
That package installs the shared integration, Quilt React bindings, React 19,
|
|
23
|
+
React DOM 19, R3F 9, and the required declarations. Compatible existing peers
|
|
24
|
+
are reused; incompatible versions must be upgraded. Existing `quilt-react`
|
|
25
|
+
React 18 support is unchanged.
|
|
25
26
|
|
|
26
27
|
## Vanilla
|
|
27
28
|
|
|
@@ -141,7 +142,7 @@ Disposal is idempotent and releases only helper-owned graphics. Session disposal
|
|
|
141
142
|
disposes attached helpers, without disposing application scene assets.
|
|
142
143
|
|
|
143
144
|
In React, place `<CameraHelpers visible={showCameras} ray="pointer" />` from
|
|
144
|
-
`quilt-three
|
|
145
|
+
`quilt-three-react` inside `ThreeSessionProvider`, alongside `SharedScene` and the workspace.
|
|
145
146
|
`visible` updates reactively; other helper options are mount-only. Unmounting cleans up
|
|
146
147
|
the helper instance, including React Strict Mode effect replays.
|
|
147
148
|
|
|
@@ -150,7 +151,7 @@ the helper instance, including React Strict Mode effect replays.
|
|
|
150
151
|
```tsx
|
|
151
152
|
import { Workspace } from 'quilt-react';
|
|
152
153
|
import type { PaneProps } from 'quilt-react';
|
|
153
|
-
import { SharedScene, ThreeSessionProvider, ThreeView } from 'quilt-three
|
|
154
|
+
import { SharedScene, ThreeSessionProvider, ThreeView } from 'quilt-three-react';
|
|
154
155
|
|
|
155
156
|
// `camera` and `initialLayout` are application-owned, as in the vanilla example.
|
|
156
157
|
function ScenePane(props: PaneProps) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quilt-three",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Shared-scene Three.js viewports for Quilt",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,10 +15,6 @@
|
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"import": "./dist/index.js"
|
|
17
17
|
},
|
|
18
|
-
"./react": {
|
|
19
|
-
"types": "./dist/react.d.ts",
|
|
20
|
-
"import": "./dist/react.js"
|
|
21
|
-
},
|
|
22
18
|
"./webgpu": {
|
|
23
19
|
"types": "./dist/webgpu.d.ts",
|
|
24
20
|
"import": "./dist/webgpu.js"
|
|
@@ -29,28 +25,11 @@
|
|
|
29
25
|
"prepack": "node ../../scripts/build-package.mjs three --dependencies"
|
|
30
26
|
},
|
|
31
27
|
"dependencies": {
|
|
32
|
-
"quilt-vanilla": "0.
|
|
28
|
+
"quilt-vanilla": "0.11.0",
|
|
29
|
+
"@types/three": "^0.186.0"
|
|
33
30
|
},
|
|
34
31
|
"peerDependencies": {
|
|
35
|
-
"three": "^0.186.0"
|
|
36
|
-
"@react-three/fiber": "^9.8.1",
|
|
37
|
-
"react": "^18.3.0 || ^19.0.0",
|
|
38
|
-
"react-dom": "^18.3.0 || ^19.0.0",
|
|
39
|
-
"quilt-react": "0.10.0"
|
|
40
|
-
},
|
|
41
|
-
"peerDependenciesMeta": {
|
|
42
|
-
"@react-three/fiber": {
|
|
43
|
-
"optional": true
|
|
44
|
-
},
|
|
45
|
-
"react": {
|
|
46
|
-
"optional": true
|
|
47
|
-
},
|
|
48
|
-
"react-dom": {
|
|
49
|
-
"optional": true
|
|
50
|
-
},
|
|
51
|
-
"quilt-react": {
|
|
52
|
-
"optional": true
|
|
53
|
-
}
|
|
32
|
+
"three": "^0.186.0"
|
|
54
33
|
},
|
|
55
34
|
"sideEffects": false,
|
|
56
35
|
"publishConfig": {
|
package/dist/react.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import type { PaneProps } from 'quilt-react';
|
|
3
|
-
import { ThreeSession } from './index.js';
|
|
4
|
-
import type { CameraHelpersOptions, SessionOptions, ViewOptions } from './index.js';
|
|
5
|
-
export declare function useThreeSession(): ThreeSession;
|
|
6
|
-
export interface ThreeSessionProviderProps {
|
|
7
|
-
children: ReactNode;
|
|
8
|
-
options?: Omit<SessionOptions, 'scene' | 'window' | 'requestFrame'>;
|
|
9
|
-
onReady?: (session: ThreeSession) => void;
|
|
10
|
-
}
|
|
11
|
-
/** Owns one persistent R3F scene tree; place outside the Quilt Workspace. Options are mount-only. */
|
|
12
|
-
export declare function ThreeSessionProvider({ children, options, onReady }: ThreeSessionProviderProps): import("react").JSX.Element | null;
|
|
13
|
-
/** Declare scene content exactly once, outside individual pane components. */
|
|
14
|
-
export declare function SharedScene({ children }: {
|
|
15
|
-
children: ReactNode;
|
|
16
|
-
}): null;
|
|
17
|
-
export interface ThreeViewProps extends ViewOptions {
|
|
18
|
-
paneContext: PaneProps;
|
|
19
|
-
children?: ReactNode;
|
|
20
|
-
}
|
|
21
|
-
/** The children here are HTML overlays. Shared Three.js JSX belongs in SharedScene. */
|
|
22
|
-
export declare function ThreeView({ paneContext, children, ...options }: ThreeViewProps): import("react").JSX.Element;
|
|
23
|
-
export type { ThreeElements, ThreeEvent } from '@react-three/fiber';
|
|
24
|
-
/** Mount inside ThreeSessionProvider, alongside SharedScene (not inside its R3F tree).
|
|
25
|
-
* Visibility is reactive; other options are mount-only.
|
|
26
|
-
*/
|
|
27
|
-
export declare function CameraHelpers({ visible, ...options }: CameraHelpersOptions): null;
|
|
28
|
-
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,YAAY,EAGb,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAkBpF,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAsBD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,QAAQ,GAAG,cAAc,CAAC,CAAC;IACpE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3C;AACD,qGAAqG;AACrG,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,yBAAyB,sCA8J7F;AACD,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,QAOhE;AACD,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,WAAW,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AACD,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,EAAE,cAAc,+BAiC9E;AAGD,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEpE;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,OAAc,EAAE,GAAG,OAAO,EAAE,EAAE,oBAAoB,QAgBjF"}
|
package/dist/react.js
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Component, createContext, useContext, useEffect, useLayoutEffect, useRef, useState, } from 'react';
|
|
3
|
-
import { createRoot, events, extend, useFrame } from '@react-three/fiber';
|
|
4
|
-
import * as THREE from 'three';
|
|
5
|
-
import { ThreeSession, isWebGPURenderer, CameraHelpers as CameraHelpersController, } from './index.js';
|
|
6
|
-
const Context = createContext(null);
|
|
7
|
-
function useBridge() {
|
|
8
|
-
const bridge = useContext(Context);
|
|
9
|
-
if (!bridge)
|
|
10
|
-
throw new Error('Three components require ThreeSessionProvider');
|
|
11
|
-
return bridge;
|
|
12
|
-
}
|
|
13
|
-
export function useThreeSession() {
|
|
14
|
-
return useBridge().session;
|
|
15
|
-
}
|
|
16
|
-
class SceneBoundary extends Component {
|
|
17
|
-
state = { failed: false };
|
|
18
|
-
static getDerivedStateFromError() {
|
|
19
|
-
return { failed: true };
|
|
20
|
-
}
|
|
21
|
-
componentDidCatch(error) {
|
|
22
|
-
this.props.fail(error);
|
|
23
|
-
}
|
|
24
|
-
render() {
|
|
25
|
-
return this.state.failed ? null : this.props.children;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function FrameOwner({ ready, children }) {
|
|
29
|
-
// Positive priority delegates drawing to the shared session, while useFrame still runs once.
|
|
30
|
-
useFrame(() => { }, 1);
|
|
31
|
-
useLayoutEffect(ready, [ready]);
|
|
32
|
-
return children;
|
|
33
|
-
}
|
|
34
|
-
/** Owns one persistent R3F scene tree; place outside the Quilt Workspace. Options are mount-only. */
|
|
35
|
-
export function ThreeSessionProvider({ children, options, onReady }) {
|
|
36
|
-
const [bridge, setBridge] = useState(null);
|
|
37
|
-
const initial = useRef({ options, onReady });
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
extend(Object.fromEntries(Object.entries(THREE).filter(([, value]) => typeof value === 'function')));
|
|
40
|
-
const win = window;
|
|
41
|
-
let root;
|
|
42
|
-
let store;
|
|
43
|
-
let content = null;
|
|
44
|
-
let frameWindow = win;
|
|
45
|
-
let animation = 0, disposed = false, elapsed = 0, previous;
|
|
46
|
-
let resolve, reject;
|
|
47
|
-
const ready = new Promise((yes, no) => {
|
|
48
|
-
resolve = yes;
|
|
49
|
-
reject = no;
|
|
50
|
-
});
|
|
51
|
-
void ready.catch(() => { });
|
|
52
|
-
const committed = () => {
|
|
53
|
-
resolve();
|
|
54
|
-
session.invalidate();
|
|
55
|
-
};
|
|
56
|
-
const failed = (error) => {
|
|
57
|
-
reject(error);
|
|
58
|
-
initial.current.options?.onError?.(error);
|
|
59
|
-
};
|
|
60
|
-
const sceneTree = () => (_jsx(SceneBoundary, { fail: failed, children: _jsx(FrameOwner, { ready: committed, children: content }) }));
|
|
61
|
-
const drawScene = () => {
|
|
62
|
-
root?.render(sceneTree());
|
|
63
|
-
};
|
|
64
|
-
const session = new ThreeSession({
|
|
65
|
-
...initial.current.options,
|
|
66
|
-
scene: new THREE.Scene(),
|
|
67
|
-
window: win,
|
|
68
|
-
requestFrame(owner) {
|
|
69
|
-
if (disposed)
|
|
70
|
-
return;
|
|
71
|
-
if (animation && frameWindow !== owner) {
|
|
72
|
-
frameWindow.cancelAnimationFrame(animation);
|
|
73
|
-
animation = 0;
|
|
74
|
-
}
|
|
75
|
-
if (animation)
|
|
76
|
-
return;
|
|
77
|
-
frameWindow = owner;
|
|
78
|
-
animation = owner.requestAnimationFrame(() => {
|
|
79
|
-
const time = win.performance.now();
|
|
80
|
-
animation = 0;
|
|
81
|
-
if (previous !== undefined)
|
|
82
|
-
elapsed += Math.min((time - previous) / 1000, 0.1);
|
|
83
|
-
previous = time;
|
|
84
|
-
try {
|
|
85
|
-
store?.getState().advance(elapsed, true);
|
|
86
|
-
session.render(time);
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
failed(error);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
},
|
|
93
|
-
createRenderer(canvas) {
|
|
94
|
-
const created = initial.current.options?.createRenderer?.(canvas) ??
|
|
95
|
-
new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
|
|
96
|
-
const connect = (renderer) => {
|
|
97
|
-
if (!disposed && canvas.ownerDocument === win.document) {
|
|
98
|
-
root = createRoot(canvas);
|
|
99
|
-
const currentRoot = root;
|
|
100
|
-
const initialized = isWebGPURenderer(renderer) ? renderer.init() : Promise.resolve();
|
|
101
|
-
void initialized
|
|
102
|
-
.then(() => {
|
|
103
|
-
if (disposed)
|
|
104
|
-
return;
|
|
105
|
-
return currentRoot.configure({
|
|
106
|
-
gl: renderer,
|
|
107
|
-
scene: session.scene,
|
|
108
|
-
frameloop: 'never',
|
|
109
|
-
events,
|
|
110
|
-
size: { width: 1, height: 1, top: 0, left: 0 },
|
|
111
|
-
onCreated(state) {
|
|
112
|
-
state.set({ invalidate: session.invalidate });
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
})
|
|
116
|
-
.then(() => {
|
|
117
|
-
if (disposed)
|
|
118
|
-
return;
|
|
119
|
-
store = root.render(sceneTree());
|
|
120
|
-
// Events are forwarded from pane elements, including companion documents.
|
|
121
|
-
store.getState().events.disconnect?.();
|
|
122
|
-
store.getState().setEvents({
|
|
123
|
-
enabled: true,
|
|
124
|
-
compute(event, state) {
|
|
125
|
-
const element = event.currentTarget;
|
|
126
|
-
const rect = element.getBoundingClientRect();
|
|
127
|
-
state.pointer.set(((event.clientX - rect.left) / rect.width) * 2 - 1, (-(event.clientY - rect.top) / rect.height) * 2 + 1);
|
|
128
|
-
state.raycaster.layers.mask = state.camera.layers.mask;
|
|
129
|
-
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
})
|
|
133
|
-
.catch(failed);
|
|
134
|
-
}
|
|
135
|
-
return renderer;
|
|
136
|
-
};
|
|
137
|
-
return 'then' in created ? Promise.resolve(created).then(connect) : connect(created);
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
const names = {
|
|
141
|
-
click: 'onClick',
|
|
142
|
-
dblclick: 'onDoubleClick',
|
|
143
|
-
contextmenu: 'onContextMenu',
|
|
144
|
-
wheel: 'onWheel',
|
|
145
|
-
pointerdown: 'onPointerDown',
|
|
146
|
-
pointerup: 'onPointerUp',
|
|
147
|
-
pointermove: 'onPointerMove',
|
|
148
|
-
pointerleave: 'onPointerLeave',
|
|
149
|
-
pointercancel: 'onPointerCancel',
|
|
150
|
-
lostpointercapture: 'onLostPointerCapture',
|
|
151
|
-
};
|
|
152
|
-
const value = {
|
|
153
|
-
session,
|
|
154
|
-
ready,
|
|
155
|
-
setScene(next) {
|
|
156
|
-
content = next;
|
|
157
|
-
drawScene();
|
|
158
|
-
session.invalidate();
|
|
159
|
-
},
|
|
160
|
-
pointer(_element, camera, event) {
|
|
161
|
-
if (!store)
|
|
162
|
-
return;
|
|
163
|
-
const state = store.getState();
|
|
164
|
-
const original = state.camera;
|
|
165
|
-
state.set({ camera: camera });
|
|
166
|
-
try {
|
|
167
|
-
const name = names[event.type];
|
|
168
|
-
if (name)
|
|
169
|
-
state.events.handlers?.[name]?.(event);
|
|
170
|
-
}
|
|
171
|
-
finally {
|
|
172
|
-
state.set({ camera: original });
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
setBridge(value);
|
|
177
|
-
initial.current.onReady?.(session);
|
|
178
|
-
return () => {
|
|
179
|
-
disposed = true;
|
|
180
|
-
if (animation)
|
|
181
|
-
frameWindow.cancelAnimationFrame(animation);
|
|
182
|
-
reject(new Error('Three session disposed before scene readiness'));
|
|
183
|
-
root?.unmount();
|
|
184
|
-
session.dispose();
|
|
185
|
-
};
|
|
186
|
-
}, []);
|
|
187
|
-
return bridge ? _jsx(Context.Provider, { value: bridge, children: children }) : null;
|
|
188
|
-
}
|
|
189
|
-
/** Declare scene content exactly once, outside individual pane components. */
|
|
190
|
-
export function SharedScene({ children }) {
|
|
191
|
-
const bridge = useBridge();
|
|
192
|
-
useLayoutEffect(() => {
|
|
193
|
-
bridge.setScene(children);
|
|
194
|
-
}, [bridge, children]);
|
|
195
|
-
useLayoutEffect(() => () => bridge.setScene(null), [bridge]);
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
/** The children here are HTML overlays. Shared Three.js JSX belongs in SharedScene. */
|
|
199
|
-
export function ThreeView({ paneContext, children, ...options }) {
|
|
200
|
-
const bridge = useBridge();
|
|
201
|
-
const element = useRef(null);
|
|
202
|
-
const current = useRef(options);
|
|
203
|
-
current.current = options;
|
|
204
|
-
useLayoutEffect(() => {
|
|
205
|
-
if (!element.current)
|
|
206
|
-
return;
|
|
207
|
-
const host = element.current;
|
|
208
|
-
const view = bridge.session.register({ ...paneContext, element: host }, {
|
|
209
|
-
...current.current,
|
|
210
|
-
onPointer(event, hits, camera) {
|
|
211
|
-
bridge.pointer(host, camera, event);
|
|
212
|
-
current.current.onPointer?.(event, hits, camera);
|
|
213
|
-
},
|
|
214
|
-
});
|
|
215
|
-
paneContext.deferReady(bridge.ready);
|
|
216
|
-
return () => view.dispose();
|
|
217
|
-
}, [
|
|
218
|
-
bridge,
|
|
219
|
-
paneContext.pane.id,
|
|
220
|
-
paneContext.document,
|
|
221
|
-
options.camera,
|
|
222
|
-
options.controls,
|
|
223
|
-
options.pipeline,
|
|
224
|
-
]);
|
|
225
|
-
return (_jsx("div", { ref: element, style: { width: '100%', height: '100%', position: 'relative' }, children: children }));
|
|
226
|
-
}
|
|
227
|
-
/** Mount inside ThreeSessionProvider, alongside SharedScene (not inside its R3F tree).
|
|
228
|
-
* Visibility is reactive; other options are mount-only.
|
|
229
|
-
*/
|
|
230
|
-
export function CameraHelpers({ visible = true, ...options }) {
|
|
231
|
-
const session = useThreeSession();
|
|
232
|
-
const initial = useRef(options);
|
|
233
|
-
const instance = useRef(null);
|
|
234
|
-
useEffect(() => {
|
|
235
|
-
const helpers = new CameraHelpersController(session, { ...initial.current, visible });
|
|
236
|
-
instance.current = helpers;
|
|
237
|
-
return () => {
|
|
238
|
-
helpers.dispose();
|
|
239
|
-
instance.current = null;
|
|
240
|
-
};
|
|
241
|
-
}, [session]);
|
|
242
|
-
useEffect(() => {
|
|
243
|
-
instance.current?.setVisible(visible);
|
|
244
|
-
}, [visible]);
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
//# sourceMappingURL=react.js.map
|
package/dist/react.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\nimport type { ReactNode } from 'react';\nimport { createRoot, events, extend, useFrame } from '@react-three/fiber';\nimport type { RootStore, ReconcilerRoot, Events } from '@react-three/fiber';\nimport * as THREE from 'three';\nimport type { PaneProps } from 'quilt-react';\nimport {\n ThreeSession,\n isWebGPURenderer,\n CameraHelpers as CameraHelpersController,\n} from './index.js';\nimport type { CameraHelpersOptions, SessionOptions, ViewOptions } from './index.js';\n\ninterface Bridge {\n session: ThreeSession;\n ready: Promise<void>;\n setScene(children: ReactNode): void;\n pointer(\n element: HTMLElement,\n camera: THREE.Camera,\n event: PointerEvent | MouseEvent | WheelEvent,\n ): void;\n}\nconst Context = createContext<Bridge | null>(null);\nfunction useBridge() {\n const bridge = useContext(Context);\n if (!bridge) throw new Error('Three components require ThreeSessionProvider');\n return bridge;\n}\nexport function useThreeSession(): ThreeSession {\n return useBridge().session;\n}\nclass SceneBoundary extends Component<\n { children: ReactNode; fail(error: unknown): void },\n { failed: boolean }\n> {\n override state = { failed: false };\n static getDerivedStateFromError() {\n return { failed: true };\n }\n override componentDidCatch(error: unknown) {\n this.props.fail(error);\n }\n override render() {\n return this.state.failed ? null : this.props.children;\n }\n}\nfunction FrameOwner({ ready, children }: { ready(): void; children: ReactNode }) {\n // Positive priority delegates drawing to the shared session, while useFrame still runs once.\n useFrame(() => {}, 1);\n useLayoutEffect(ready, [ready]);\n return children;\n}\nexport interface ThreeSessionProviderProps {\n children: ReactNode;\n options?: Omit<SessionOptions, 'scene' | 'window' | 'requestFrame'>;\n onReady?: (session: ThreeSession) => void;\n}\n/** Owns one persistent R3F scene tree; place outside the Quilt Workspace. Options are mount-only. */\nexport function ThreeSessionProvider({ children, options, onReady }: ThreeSessionProviderProps) {\n const [bridge, setBridge] = useState<Bridge | null>(null);\n const initial = useRef({ options, onReady });\n useEffect(() => {\n extend(\n Object.fromEntries(\n Object.entries(THREE).filter(([, value]) => typeof value === 'function'),\n ) as Parameters<typeof extend>[0],\n );\n const win = window;\n let root: ReconcilerRoot<HTMLCanvasElement> | undefined;\n let store: RootStore | undefined;\n let content: ReactNode = null;\n let frameWindow: Window = win;\n let animation = 0,\n disposed = false,\n elapsed = 0,\n previous: number | undefined;\n let resolve!: () => void, reject!: (error: unknown) => void;\n const ready = new Promise<void>((yes, no) => {\n resolve = yes;\n reject = no;\n });\n void ready.catch(() => {});\n const committed = () => {\n resolve();\n session.invalidate();\n };\n const failed = (error: unknown) => {\n reject(error);\n initial.current.options?.onError?.(error);\n };\n const sceneTree = () => (\n <SceneBoundary fail={failed}>\n <FrameOwner ready={committed}>{content}</FrameOwner>\n </SceneBoundary>\n );\n const drawScene = () => {\n root?.render(sceneTree());\n };\n const session = new ThreeSession({\n ...initial.current.options,\n scene: new THREE.Scene(),\n window: win,\n requestFrame(owner) {\n if (disposed) return;\n if (animation && frameWindow !== owner) {\n frameWindow.cancelAnimationFrame(animation);\n animation = 0;\n }\n if (animation) return;\n frameWindow = owner;\n animation = owner.requestAnimationFrame(() => {\n const time = win.performance.now();\n animation = 0;\n if (previous !== undefined) elapsed += Math.min((time - previous) / 1000, 0.1);\n previous = time;\n try {\n store?.getState().advance(elapsed, true);\n session.render(time);\n } catch (error) {\n failed(error);\n }\n });\n },\n createRenderer(canvas) {\n const created =\n initial.current.options?.createRenderer?.(canvas) ??\n new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });\n const connect = (renderer: import('./index.js').ThreeRenderer) => {\n if (!disposed && canvas.ownerDocument === win.document) {\n root = createRoot(canvas);\n const currentRoot = root;\n const initialized = isWebGPURenderer(renderer) ? renderer.init() : Promise.resolve();\n void initialized\n .then(() => {\n if (disposed) return;\n return currentRoot.configure({\n gl: renderer,\n scene: session.scene,\n frameloop: 'never',\n events,\n size: { width: 1, height: 1, top: 0, left: 0 },\n onCreated(state) {\n state.set({ invalidate: session.invalidate });\n },\n });\n })\n .then(() => {\n if (disposed) return;\n store = root!.render(sceneTree());\n // Events are forwarded from pane elements, including companion documents.\n store.getState().events.disconnect?.();\n store.getState().setEvents({\n enabled: true,\n compute(event, state) {\n const element = event.currentTarget as HTMLElement;\n const rect = element.getBoundingClientRect();\n state.pointer.set(\n ((event.clientX - rect.left) / rect.width) * 2 - 1,\n (-(event.clientY - rect.top) / rect.height) * 2 + 1,\n );\n state.raycaster.layers.mask = state.camera.layers.mask;\n state.raycaster.setFromCamera(state.pointer, state.camera);\n },\n });\n })\n .catch(failed);\n }\n return renderer;\n };\n return 'then' in created ? Promise.resolve(created).then(connect) : connect(created);\n },\n });\n const names: Record<string, keyof Events> = {\n click: 'onClick',\n dblclick: 'onDoubleClick',\n contextmenu: 'onContextMenu',\n wheel: 'onWheel',\n pointerdown: 'onPointerDown',\n pointerup: 'onPointerUp',\n pointermove: 'onPointerMove',\n pointerleave: 'onPointerLeave',\n pointercancel: 'onPointerCancel',\n lostpointercapture: 'onLostPointerCapture',\n };\n const value: Bridge = {\n session,\n ready,\n setScene(next) {\n content = next;\n drawScene();\n session.invalidate();\n },\n pointer(_element, camera, event) {\n if (!store) return;\n const state = store.getState();\n const original = state.camera;\n state.set({ camera: camera as THREE.PerspectiveCamera });\n try {\n const name = names[event.type];\n if (name) state.events.handlers?.[name]?.(event);\n } finally {\n state.set({ camera: original });\n }\n },\n };\n setBridge(value);\n initial.current.onReady?.(session);\n return () => {\n disposed = true;\n if (animation) frameWindow.cancelAnimationFrame(animation);\n reject(new Error('Three session disposed before scene readiness'));\n root?.unmount();\n session.dispose();\n };\n }, []);\n return bridge ? <Context.Provider value={bridge}>{children}</Context.Provider> : null;\n}\n/** Declare scene content exactly once, outside individual pane components. */\nexport function SharedScene({ children }: { children: ReactNode }) {\n const bridge = useBridge();\n useLayoutEffect(() => {\n bridge.setScene(children);\n }, [bridge, children]);\n useLayoutEffect(() => () => bridge.setScene(null), [bridge]);\n return null;\n}\nexport interface ThreeViewProps extends ViewOptions {\n paneContext: PaneProps;\n children?: ReactNode;\n}\n/** The children here are HTML overlays. Shared Three.js JSX belongs in SharedScene. */\nexport function ThreeView({ paneContext, children, ...options }: ThreeViewProps) {\n const bridge = useBridge();\n const element = useRef<HTMLDivElement>(null);\n const current = useRef(options);\n current.current = options;\n useLayoutEffect(() => {\n if (!element.current) return;\n const host = element.current;\n const view = bridge.session.register(\n { ...paneContext, element: host },\n {\n ...current.current,\n onPointer(event, hits, camera) {\n bridge.pointer(host, camera, event);\n current.current.onPointer?.(event, hits, camera);\n },\n },\n );\n paneContext.deferReady(bridge.ready);\n return () => view.dispose();\n }, [\n bridge,\n paneContext.pane.id,\n paneContext.document,\n options.camera,\n options.controls,\n options.pipeline,\n ]);\n return (\n <div ref={element} style={{ width: '100%', height: '100%', position: 'relative' }}>\n {children}\n </div>\n );\n}\n\n// Preserve R3F JSX augmentation for consumers importing only quilt-three/react.\nexport type { ThreeElements, ThreeEvent } from '@react-three/fiber';\n\n/** Mount inside ThreeSessionProvider, alongside SharedScene (not inside its R3F tree).\n * Visibility is reactive; other options are mount-only.\n */\nexport function CameraHelpers({ visible = true, ...options }: CameraHelpersOptions) {\n const session = useThreeSession();\n const initial = useRef(options);\n const instance = useRef<CameraHelpersController | null>(null);\n useEffect(() => {\n const helpers = new CameraHelpersController(session, { ...initial.current, visible });\n instance.current = helpers;\n return () => {\n helpers.dispose();\n instance.current = null;\n };\n }, [session]);\n useEffect(() => {\n instance.current?.setVisible(visible);\n }, [visible]);\n return null;\n}\n"]}
|
package/src/react.tsx
DELETED
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
createContext,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useLayoutEffect,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
} from 'react';
|
|
10
|
-
import type { ReactNode } from 'react';
|
|
11
|
-
import { createRoot, events, extend, useFrame } from '@react-three/fiber';
|
|
12
|
-
import type { RootStore, ReconcilerRoot, Events } from '@react-three/fiber';
|
|
13
|
-
import * as THREE from 'three';
|
|
14
|
-
import type { PaneProps } from 'quilt-react';
|
|
15
|
-
import {
|
|
16
|
-
ThreeSession,
|
|
17
|
-
isWebGPURenderer,
|
|
18
|
-
CameraHelpers as CameraHelpersController,
|
|
19
|
-
} from './index.js';
|
|
20
|
-
import type { CameraHelpersOptions, SessionOptions, ViewOptions } from './index.js';
|
|
21
|
-
|
|
22
|
-
interface Bridge {
|
|
23
|
-
session: ThreeSession;
|
|
24
|
-
ready: Promise<void>;
|
|
25
|
-
setScene(children: ReactNode): void;
|
|
26
|
-
pointer(
|
|
27
|
-
element: HTMLElement,
|
|
28
|
-
camera: THREE.Camera,
|
|
29
|
-
event: PointerEvent | MouseEvent | WheelEvent,
|
|
30
|
-
): void;
|
|
31
|
-
}
|
|
32
|
-
const Context = createContext<Bridge | null>(null);
|
|
33
|
-
function useBridge() {
|
|
34
|
-
const bridge = useContext(Context);
|
|
35
|
-
if (!bridge) throw new Error('Three components require ThreeSessionProvider');
|
|
36
|
-
return bridge;
|
|
37
|
-
}
|
|
38
|
-
export function useThreeSession(): ThreeSession {
|
|
39
|
-
return useBridge().session;
|
|
40
|
-
}
|
|
41
|
-
class SceneBoundary extends Component<
|
|
42
|
-
{ children: ReactNode; fail(error: unknown): void },
|
|
43
|
-
{ failed: boolean }
|
|
44
|
-
> {
|
|
45
|
-
override state = { failed: false };
|
|
46
|
-
static getDerivedStateFromError() {
|
|
47
|
-
return { failed: true };
|
|
48
|
-
}
|
|
49
|
-
override componentDidCatch(error: unknown) {
|
|
50
|
-
this.props.fail(error);
|
|
51
|
-
}
|
|
52
|
-
override render() {
|
|
53
|
-
return this.state.failed ? null : this.props.children;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function FrameOwner({ ready, children }: { ready(): void; children: ReactNode }) {
|
|
57
|
-
// Positive priority delegates drawing to the shared session, while useFrame still runs once.
|
|
58
|
-
useFrame(() => {}, 1);
|
|
59
|
-
useLayoutEffect(ready, [ready]);
|
|
60
|
-
return children;
|
|
61
|
-
}
|
|
62
|
-
export interface ThreeSessionProviderProps {
|
|
63
|
-
children: ReactNode;
|
|
64
|
-
options?: Omit<SessionOptions, 'scene' | 'window' | 'requestFrame'>;
|
|
65
|
-
onReady?: (session: ThreeSession) => void;
|
|
66
|
-
}
|
|
67
|
-
/** Owns one persistent R3F scene tree; place outside the Quilt Workspace. Options are mount-only. */
|
|
68
|
-
export function ThreeSessionProvider({ children, options, onReady }: ThreeSessionProviderProps) {
|
|
69
|
-
const [bridge, setBridge] = useState<Bridge | null>(null);
|
|
70
|
-
const initial = useRef({ options, onReady });
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
extend(
|
|
73
|
-
Object.fromEntries(
|
|
74
|
-
Object.entries(THREE).filter(([, value]) => typeof value === 'function'),
|
|
75
|
-
) as Parameters<typeof extend>[0],
|
|
76
|
-
);
|
|
77
|
-
const win = window;
|
|
78
|
-
let root: ReconcilerRoot<HTMLCanvasElement> | undefined;
|
|
79
|
-
let store: RootStore | undefined;
|
|
80
|
-
let content: ReactNode = null;
|
|
81
|
-
let frameWindow: Window = win;
|
|
82
|
-
let animation = 0,
|
|
83
|
-
disposed = false,
|
|
84
|
-
elapsed = 0,
|
|
85
|
-
previous: number | undefined;
|
|
86
|
-
let resolve!: () => void, reject!: (error: unknown) => void;
|
|
87
|
-
const ready = new Promise<void>((yes, no) => {
|
|
88
|
-
resolve = yes;
|
|
89
|
-
reject = no;
|
|
90
|
-
});
|
|
91
|
-
void ready.catch(() => {});
|
|
92
|
-
const committed = () => {
|
|
93
|
-
resolve();
|
|
94
|
-
session.invalidate();
|
|
95
|
-
};
|
|
96
|
-
const failed = (error: unknown) => {
|
|
97
|
-
reject(error);
|
|
98
|
-
initial.current.options?.onError?.(error);
|
|
99
|
-
};
|
|
100
|
-
const sceneTree = () => (
|
|
101
|
-
<SceneBoundary fail={failed}>
|
|
102
|
-
<FrameOwner ready={committed}>{content}</FrameOwner>
|
|
103
|
-
</SceneBoundary>
|
|
104
|
-
);
|
|
105
|
-
const drawScene = () => {
|
|
106
|
-
root?.render(sceneTree());
|
|
107
|
-
};
|
|
108
|
-
const session = new ThreeSession({
|
|
109
|
-
...initial.current.options,
|
|
110
|
-
scene: new THREE.Scene(),
|
|
111
|
-
window: win,
|
|
112
|
-
requestFrame(owner) {
|
|
113
|
-
if (disposed) return;
|
|
114
|
-
if (animation && frameWindow !== owner) {
|
|
115
|
-
frameWindow.cancelAnimationFrame(animation);
|
|
116
|
-
animation = 0;
|
|
117
|
-
}
|
|
118
|
-
if (animation) return;
|
|
119
|
-
frameWindow = owner;
|
|
120
|
-
animation = owner.requestAnimationFrame(() => {
|
|
121
|
-
const time = win.performance.now();
|
|
122
|
-
animation = 0;
|
|
123
|
-
if (previous !== undefined) elapsed += Math.min((time - previous) / 1000, 0.1);
|
|
124
|
-
previous = time;
|
|
125
|
-
try {
|
|
126
|
-
store?.getState().advance(elapsed, true);
|
|
127
|
-
session.render(time);
|
|
128
|
-
} catch (error) {
|
|
129
|
-
failed(error);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
},
|
|
133
|
-
createRenderer(canvas) {
|
|
134
|
-
const created =
|
|
135
|
-
initial.current.options?.createRenderer?.(canvas) ??
|
|
136
|
-
new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
|
|
137
|
-
const connect = (renderer: import('./index.js').ThreeRenderer) => {
|
|
138
|
-
if (!disposed && canvas.ownerDocument === win.document) {
|
|
139
|
-
root = createRoot(canvas);
|
|
140
|
-
const currentRoot = root;
|
|
141
|
-
const initialized = isWebGPURenderer(renderer) ? renderer.init() : Promise.resolve();
|
|
142
|
-
void initialized
|
|
143
|
-
.then(() => {
|
|
144
|
-
if (disposed) return;
|
|
145
|
-
return currentRoot.configure({
|
|
146
|
-
gl: renderer,
|
|
147
|
-
scene: session.scene,
|
|
148
|
-
frameloop: 'never',
|
|
149
|
-
events,
|
|
150
|
-
size: { width: 1, height: 1, top: 0, left: 0 },
|
|
151
|
-
onCreated(state) {
|
|
152
|
-
state.set({ invalidate: session.invalidate });
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
})
|
|
156
|
-
.then(() => {
|
|
157
|
-
if (disposed) return;
|
|
158
|
-
store = root!.render(sceneTree());
|
|
159
|
-
// Events are forwarded from pane elements, including companion documents.
|
|
160
|
-
store.getState().events.disconnect?.();
|
|
161
|
-
store.getState().setEvents({
|
|
162
|
-
enabled: true,
|
|
163
|
-
compute(event, state) {
|
|
164
|
-
const element = event.currentTarget as HTMLElement;
|
|
165
|
-
const rect = element.getBoundingClientRect();
|
|
166
|
-
state.pointer.set(
|
|
167
|
-
((event.clientX - rect.left) / rect.width) * 2 - 1,
|
|
168
|
-
(-(event.clientY - rect.top) / rect.height) * 2 + 1,
|
|
169
|
-
);
|
|
170
|
-
state.raycaster.layers.mask = state.camera.layers.mask;
|
|
171
|
-
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
})
|
|
175
|
-
.catch(failed);
|
|
176
|
-
}
|
|
177
|
-
return renderer;
|
|
178
|
-
};
|
|
179
|
-
return 'then' in created ? Promise.resolve(created).then(connect) : connect(created);
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
const names: Record<string, keyof Events> = {
|
|
183
|
-
click: 'onClick',
|
|
184
|
-
dblclick: 'onDoubleClick',
|
|
185
|
-
contextmenu: 'onContextMenu',
|
|
186
|
-
wheel: 'onWheel',
|
|
187
|
-
pointerdown: 'onPointerDown',
|
|
188
|
-
pointerup: 'onPointerUp',
|
|
189
|
-
pointermove: 'onPointerMove',
|
|
190
|
-
pointerleave: 'onPointerLeave',
|
|
191
|
-
pointercancel: 'onPointerCancel',
|
|
192
|
-
lostpointercapture: 'onLostPointerCapture',
|
|
193
|
-
};
|
|
194
|
-
const value: Bridge = {
|
|
195
|
-
session,
|
|
196
|
-
ready,
|
|
197
|
-
setScene(next) {
|
|
198
|
-
content = next;
|
|
199
|
-
drawScene();
|
|
200
|
-
session.invalidate();
|
|
201
|
-
},
|
|
202
|
-
pointer(_element, camera, event) {
|
|
203
|
-
if (!store) return;
|
|
204
|
-
const state = store.getState();
|
|
205
|
-
const original = state.camera;
|
|
206
|
-
state.set({ camera: camera as THREE.PerspectiveCamera });
|
|
207
|
-
try {
|
|
208
|
-
const name = names[event.type];
|
|
209
|
-
if (name) state.events.handlers?.[name]?.(event);
|
|
210
|
-
} finally {
|
|
211
|
-
state.set({ camera: original });
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
};
|
|
215
|
-
setBridge(value);
|
|
216
|
-
initial.current.onReady?.(session);
|
|
217
|
-
return () => {
|
|
218
|
-
disposed = true;
|
|
219
|
-
if (animation) frameWindow.cancelAnimationFrame(animation);
|
|
220
|
-
reject(new Error('Three session disposed before scene readiness'));
|
|
221
|
-
root?.unmount();
|
|
222
|
-
session.dispose();
|
|
223
|
-
};
|
|
224
|
-
}, []);
|
|
225
|
-
return bridge ? <Context.Provider value={bridge}>{children}</Context.Provider> : null;
|
|
226
|
-
}
|
|
227
|
-
/** Declare scene content exactly once, outside individual pane components. */
|
|
228
|
-
export function SharedScene({ children }: { children: ReactNode }) {
|
|
229
|
-
const bridge = useBridge();
|
|
230
|
-
useLayoutEffect(() => {
|
|
231
|
-
bridge.setScene(children);
|
|
232
|
-
}, [bridge, children]);
|
|
233
|
-
useLayoutEffect(() => () => bridge.setScene(null), [bridge]);
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
export interface ThreeViewProps extends ViewOptions {
|
|
237
|
-
paneContext: PaneProps;
|
|
238
|
-
children?: ReactNode;
|
|
239
|
-
}
|
|
240
|
-
/** The children here are HTML overlays. Shared Three.js JSX belongs in SharedScene. */
|
|
241
|
-
export function ThreeView({ paneContext, children, ...options }: ThreeViewProps) {
|
|
242
|
-
const bridge = useBridge();
|
|
243
|
-
const element = useRef<HTMLDivElement>(null);
|
|
244
|
-
const current = useRef(options);
|
|
245
|
-
current.current = options;
|
|
246
|
-
useLayoutEffect(() => {
|
|
247
|
-
if (!element.current) return;
|
|
248
|
-
const host = element.current;
|
|
249
|
-
const view = bridge.session.register(
|
|
250
|
-
{ ...paneContext, element: host },
|
|
251
|
-
{
|
|
252
|
-
...current.current,
|
|
253
|
-
onPointer(event, hits, camera) {
|
|
254
|
-
bridge.pointer(host, camera, event);
|
|
255
|
-
current.current.onPointer?.(event, hits, camera);
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
);
|
|
259
|
-
paneContext.deferReady(bridge.ready);
|
|
260
|
-
return () => view.dispose();
|
|
261
|
-
}, [
|
|
262
|
-
bridge,
|
|
263
|
-
paneContext.pane.id,
|
|
264
|
-
paneContext.document,
|
|
265
|
-
options.camera,
|
|
266
|
-
options.controls,
|
|
267
|
-
options.pipeline,
|
|
268
|
-
]);
|
|
269
|
-
return (
|
|
270
|
-
<div ref={element} style={{ width: '100%', height: '100%', position: 'relative' }}>
|
|
271
|
-
{children}
|
|
272
|
-
</div>
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Preserve R3F JSX augmentation for consumers importing only quilt-three/react.
|
|
277
|
-
export type { ThreeElements, ThreeEvent } from '@react-three/fiber';
|
|
278
|
-
|
|
279
|
-
/** Mount inside ThreeSessionProvider, alongside SharedScene (not inside its R3F tree).
|
|
280
|
-
* Visibility is reactive; other options are mount-only.
|
|
281
|
-
*/
|
|
282
|
-
export function CameraHelpers({ visible = true, ...options }: CameraHelpersOptions) {
|
|
283
|
-
const session = useThreeSession();
|
|
284
|
-
const initial = useRef(options);
|
|
285
|
-
const instance = useRef<CameraHelpersController | null>(null);
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
const helpers = new CameraHelpersController(session, { ...initial.current, visible });
|
|
288
|
-
instance.current = helpers;
|
|
289
|
-
return () => {
|
|
290
|
-
helpers.dispose();
|
|
291
|
-
instance.current = null;
|
|
292
|
-
};
|
|
293
|
-
}, [session]);
|
|
294
|
-
useEffect(() => {
|
|
295
|
-
instance.current?.setVisible(visible);
|
|
296
|
-
}, [visible]);
|
|
297
|
-
return null;
|
|
298
|
-
}
|