@jamesyong42/infinite-canvas 1.0.0 → 1.2.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 +170 -9
- package/dist/SelectionRenderer-CR2PBQwx.d.cts +105 -0
- package/dist/SelectionRenderer-CR2PBQwx.d.cts.map +1 -0
- package/dist/SelectionRenderer-DlsBstAq.d.mts +105 -0
- package/dist/SelectionRenderer-DlsBstAq.d.mts.map +1 -0
- package/dist/WebGLWidgetLayer-BBMuwzHq.cjs +3560 -0
- package/dist/WebGLWidgetLayer-BBMuwzHq.cjs.map +1 -0
- package/dist/WebGLWidgetLayer-C3p1tnpm.mjs +3375 -0
- package/dist/WebGLWidgetLayer-C3p1tnpm.mjs.map +1 -0
- package/dist/advanced.cjs +110 -165
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +58 -40
- package/dist/advanced.d.cts.map +1 -0
- package/dist/advanced.d.mts +99 -0
- package/dist/advanced.d.mts.map +1 -0
- package/dist/advanced.mjs +105 -0
- package/dist/advanced.mjs.map +1 -0
- package/dist/devtools.cjs +654 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +23 -0
- package/dist/devtools.d.cts.map +1 -0
- package/dist/devtools.d.mts +23 -0
- package/dist/devtools.d.mts.map +1 -0
- package/dist/devtools.mjs +652 -0
- package/dist/devtools.mjs.map +1 -0
- package/dist/engine-BfbvWXSk.d.mts +982 -0
- package/dist/engine-BfbvWXSk.d.mts.map +1 -0
- package/dist/engine-CCjuFMC-.d.cts +982 -0
- package/dist/engine-CCjuFMC-.d.cts.map +1 -0
- package/dist/hooks-BwY7rRHg.mjs +425 -0
- package/dist/hooks-BwY7rRHg.mjs.map +1 -0
- package/dist/hooks-DHShH86C.cjs +707 -0
- package/dist/hooks-DHShH86C.cjs.map +1 -0
- package/dist/index.cjs +909 -803
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +199 -67
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +258 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +855 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +47 -15
- package/dist/SelectionRenderer-CeWSNZT8.d.cts +0 -891
- package/dist/SelectionRenderer-CeWSNZT8.d.ts +0 -891
- package/dist/advanced.d.ts +0 -81
- package/dist/advanced.js +0 -124
- package/dist/advanced.js.map +0 -1
- package/dist/chunk-VSHXWTJH.cjs +0 -3228
- package/dist/chunk-VSHXWTJH.cjs.map +0 -1
- package/dist/chunk-Z6JQQOWL.js +0 -3142
- package/dist/chunk-Z6JQQOWL.js.map +0 -1
- package/dist/index.d.ts +0 -126
- package/dist/index.js +0 -602
- package/dist/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,821 +1,927 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
(_entityId, widgetType) => {
|
|
15
|
-
const def = engine.getWidget(widgetType);
|
|
16
|
-
if (!def) return null;
|
|
17
|
-
if (chunkVSHXWTJH_cjs.isR3FWidget(def)) {
|
|
18
|
-
return { surface: "webgl", component: def.component };
|
|
19
|
-
}
|
|
20
|
-
return { surface: "dom", component: def.component };
|
|
21
|
-
},
|
|
22
|
-
[engine]
|
|
23
|
-
);
|
|
24
|
-
return /* @__PURE__ */ jsxRuntime.jsx(chunkVSHXWTJH_cjs.WidgetResolverProvider, { value: resolver, children });
|
|
25
|
-
}
|
|
26
|
-
var InfiniteCanvas = React__default.default.forwardRef(
|
|
27
|
-
function InfiniteCanvas2({
|
|
28
|
-
engine,
|
|
29
|
-
grid,
|
|
30
|
-
selection,
|
|
31
|
-
onSelectionChange,
|
|
32
|
-
onCameraChange,
|
|
33
|
-
onNavigationChange,
|
|
34
|
-
className,
|
|
35
|
-
style,
|
|
36
|
-
children
|
|
37
|
-
}, ref) {
|
|
38
|
-
const containerRef = React.useRef(null);
|
|
39
|
-
const onSelectionChangeRef = React.useRef(onSelectionChange);
|
|
40
|
-
const onCameraChangeRef = React.useRef(onCameraChange);
|
|
41
|
-
const onNavigationChangeRef = React.useRef(onNavigationChange);
|
|
42
|
-
React.useEffect(() => {
|
|
43
|
-
onSelectionChangeRef.current = onSelectionChange;
|
|
44
|
-
}, [onSelectionChange]);
|
|
45
|
-
React.useEffect(() => {
|
|
46
|
-
onCameraChangeRef.current = onCameraChange;
|
|
47
|
-
}, [onCameraChange]);
|
|
48
|
-
React.useEffect(() => {
|
|
49
|
-
onNavigationChangeRef.current = onNavigationChange;
|
|
50
|
-
}, [onNavigationChange]);
|
|
51
|
-
React.useImperativeHandle(
|
|
52
|
-
ref,
|
|
53
|
-
() => ({
|
|
54
|
-
panTo: (x, y) => {
|
|
55
|
-
engine.panTo(x, y);
|
|
56
|
-
engine.markDirty();
|
|
57
|
-
},
|
|
58
|
-
zoomTo: (zoom) => {
|
|
59
|
-
engine.zoomTo(zoom);
|
|
60
|
-
engine.markDirty();
|
|
61
|
-
},
|
|
62
|
-
zoomToFit: (padding) => {
|
|
63
|
-
engine.zoomToFit(void 0, padding);
|
|
64
|
-
engine.markDirty();
|
|
65
|
-
},
|
|
66
|
-
undo: () => {
|
|
67
|
-
engine.undo();
|
|
68
|
-
engine.markDirty();
|
|
69
|
-
},
|
|
70
|
-
redo: () => {
|
|
71
|
-
engine.redo();
|
|
72
|
-
engine.markDirty();
|
|
73
|
-
},
|
|
74
|
-
getEngine: () => engine
|
|
75
|
-
}),
|
|
76
|
-
[engine]
|
|
77
|
-
);
|
|
78
|
-
const webglCanvasRef = React.useRef(null);
|
|
79
|
-
const gridRendererRef = React.useRef(null);
|
|
80
|
-
const selectionRendererRef = React.useRef(null);
|
|
81
|
-
const cameraLayerRef = React.useRef(null);
|
|
82
|
-
const slotRefs = React.useRef(/* @__PURE__ */ new Map());
|
|
83
|
-
const [visibleEntities, setVisibleEntities] = React.useState([]);
|
|
84
|
-
const registerSlotRef = React.useCallback((entityId, el) => {
|
|
85
|
-
if (el) {
|
|
86
|
-
slotRefs.current.set(entityId, el);
|
|
87
|
-
} else {
|
|
88
|
-
slotRefs.current.delete(entityId);
|
|
89
|
-
}
|
|
90
|
-
}, []);
|
|
91
|
-
React.useLayoutEffect(() => {
|
|
92
|
-
const container = containerRef.current;
|
|
93
|
-
const canvas = webglCanvasRef.current;
|
|
94
|
-
if (!container || !canvas) return;
|
|
95
|
-
const gridEnabled = grid !== false;
|
|
96
|
-
let gridInst = null;
|
|
97
|
-
if (gridEnabled) {
|
|
98
|
-
gridInst = new chunkVSHXWTJH_cjs.GridRenderer(canvas);
|
|
99
|
-
gridRendererRef.current = gridInst;
|
|
100
|
-
}
|
|
101
|
-
const selInst = new chunkVSHXWTJH_cjs.SelectionRenderer();
|
|
102
|
-
selectionRendererRef.current = selInst;
|
|
103
|
-
const updateSize = () => {
|
|
104
|
-
const rect = container.getBoundingClientRect();
|
|
105
|
-
const dpr = window.devicePixelRatio;
|
|
106
|
-
engine.setViewport(rect.width, rect.height, dpr);
|
|
107
|
-
canvas.style.width = `${rect.width}px`;
|
|
108
|
-
canvas.style.height = `${rect.height}px`;
|
|
109
|
-
if (gridInst) {
|
|
110
|
-
gridInst.setSize(rect.width, rect.height, dpr);
|
|
111
|
-
}
|
|
112
|
-
selInst.setSize(new three.Vector2(rect.width * dpr, rect.height * dpr), dpr);
|
|
113
|
-
};
|
|
114
|
-
updateSize();
|
|
115
|
-
const observer = new ResizeObserver(updateSize);
|
|
116
|
-
observer.observe(container);
|
|
117
|
-
return () => {
|
|
118
|
-
observer.disconnect();
|
|
119
|
-
if (gridInst) {
|
|
120
|
-
gridInst.dispose();
|
|
121
|
-
gridRendererRef.current = null;
|
|
122
|
-
}
|
|
123
|
-
selInst.dispose();
|
|
124
|
-
selectionRendererRef.current = null;
|
|
125
|
-
};
|
|
126
|
-
}, [engine, grid]);
|
|
127
|
-
React.useEffect(() => {
|
|
128
|
-
const gridR = gridRendererRef.current;
|
|
129
|
-
if (gridR && grid !== false) {
|
|
130
|
-
const isDark = document.documentElement.classList.contains("dark");
|
|
131
|
-
gridR.setConfig({
|
|
132
|
-
dotColor: isDark ? [1, 1, 1] : [0, 0, 0],
|
|
133
|
-
dotAlpha: isDark ? 0.12 : 0.18,
|
|
134
|
-
...grid
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
const selR = selectionRendererRef.current;
|
|
138
|
-
if (selR && selection) {
|
|
139
|
-
selR.setConfig(selection);
|
|
140
|
-
}
|
|
141
|
-
engine.markDirty();
|
|
142
|
-
}, [engine, grid, selection]);
|
|
143
|
-
React.useEffect(() => {
|
|
144
|
-
const container = containerRef.current;
|
|
145
|
-
if (!container) return;
|
|
146
|
-
const onWheel = (e) => {
|
|
147
|
-
e.preventDefault();
|
|
148
|
-
if (e.ctrlKey || e.metaKey) {
|
|
149
|
-
const rect = container.getBoundingClientRect();
|
|
150
|
-
engine.zoomAtPoint(e.clientX - rect.left, e.clientY - rect.top, -e.deltaY * 0.01);
|
|
151
|
-
} else {
|
|
152
|
-
engine.panBy(-e.deltaX, -e.deltaY);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
container.addEventListener("wheel", onWheel, { passive: false });
|
|
156
|
-
return () => container.removeEventListener("wheel", onWheel);
|
|
157
|
-
}, [engine]);
|
|
158
|
-
React.useEffect(() => {
|
|
159
|
-
const container = containerRef.current;
|
|
160
|
-
if (!container) return;
|
|
161
|
-
let gesture = { type: "idle" };
|
|
162
|
-
let lastTapTime = 0;
|
|
163
|
-
let lastTapX = 0;
|
|
164
|
-
let lastTapY = 0;
|
|
165
|
-
const DOUBLE_TAP_MS = 300;
|
|
166
|
-
const DOUBLE_TAP_DIST = 30;
|
|
167
|
-
function isOnWidget(target) {
|
|
168
|
-
let el = target;
|
|
169
|
-
while (el && el !== container) {
|
|
170
|
-
if (el.hasAttribute("data-widget-slot")) return true;
|
|
171
|
-
el = el.parentElement;
|
|
172
|
-
}
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
function isInteractive(target) {
|
|
176
|
-
const el = target;
|
|
177
|
-
if (!el) return false;
|
|
178
|
-
const tag = el.tagName;
|
|
179
|
-
return tag === "INPUT" || tag === "TEXTAREA" || tag === "BUTTON" || tag === "SELECT" || el.isContentEditable || el.closest("button") !== null;
|
|
180
|
-
}
|
|
181
|
-
function getRect() {
|
|
182
|
-
return container?.getBoundingClientRect() ?? new DOMRect();
|
|
183
|
-
}
|
|
184
|
-
function touchDist(t1, t2) {
|
|
185
|
-
const dx = t1.clientX - t2.clientX;
|
|
186
|
-
const dy = t1.clientY - t2.clientY;
|
|
187
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
188
|
-
}
|
|
189
|
-
function touchCenter(t1, t2, rect) {
|
|
190
|
-
return {
|
|
191
|
-
x: (t1.clientX + t2.clientX) / 2 - rect.left,
|
|
192
|
-
y: (t1.clientY + t2.clientY) / 2 - rect.top
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
function cancelEngineGesture() {
|
|
196
|
-
if (gesture.type === "pending-entity" || gesture.type === "entity-dragging") {
|
|
197
|
-
engine.handlePointerUp();
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const noMods = { shift: false, ctrl: false, alt: false, meta: false };
|
|
201
|
-
function onTouchStart(e) {
|
|
202
|
-
const rect = getRect();
|
|
203
|
-
const touches = e.touches;
|
|
204
|
-
if (touches.length >= 2) {
|
|
205
|
-
e.preventDefault();
|
|
206
|
-
cancelEngineGesture();
|
|
207
|
-
const dist = touchDist(touches[0], touches[1]);
|
|
208
|
-
const center = touchCenter(touches[0], touches[1], rect);
|
|
209
|
-
gesture = { type: "pinching", lastDist: dist, lastCx: center.x, lastCy: center.y };
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
const touch = touches[0];
|
|
213
|
-
const x = touch.clientX - rect.left;
|
|
214
|
-
const y = touch.clientY - rect.top;
|
|
215
|
-
if (isInteractive(e.target)) return;
|
|
216
|
-
e.preventDefault();
|
|
217
|
-
const now = Date.now();
|
|
218
|
-
if (now - lastTapTime < DOUBLE_TAP_MS && Math.abs(x - lastTapX) < DOUBLE_TAP_DIST && Math.abs(y - lastTapY) < DOUBLE_TAP_DIST) {
|
|
219
|
-
lastTapTime = 0;
|
|
220
|
-
const directive = engine.handlePointerDown(x, y, 0, noMods);
|
|
221
|
-
try {
|
|
222
|
-
if (directive.action === "passthrough-track-drag") {
|
|
223
|
-
const selected = engine.getSelectedEntities();
|
|
224
|
-
if (selected.length === 1) {
|
|
225
|
-
engine.enterContainer(selected[0]);
|
|
226
|
-
}
|
|
227
|
-
} else {
|
|
228
|
-
const camera = engine.getCamera();
|
|
229
|
-
const target = camera.zoom < 0.9 ? 1 : camera.zoom < 1.8 ? 2 : 1;
|
|
230
|
-
engine.zoomAtPoint(x, y, (target - camera.zoom) / camera.zoom);
|
|
231
|
-
}
|
|
232
|
-
} finally {
|
|
233
|
-
engine.handlePointerUp();
|
|
234
|
-
engine.markDirty();
|
|
235
|
-
}
|
|
236
|
-
gesture = { type: "idle" };
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
if (isOnWidget(e.target)) {
|
|
240
|
-
engine.handlePointerDown(x, y, 0, noMods);
|
|
241
|
-
gesture = { type: "pending-entity", x, y, time: now };
|
|
242
|
-
} else {
|
|
243
|
-
gesture = { type: "pending-pan", x, y, time: now };
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function onTouchMove(e) {
|
|
247
|
-
e.preventDefault();
|
|
248
|
-
const rect = getRect();
|
|
249
|
-
const touches = e.touches;
|
|
250
|
-
if (gesture.type === "pinching" && touches.length >= 2) {
|
|
251
|
-
const dist = touchDist(touches[0], touches[1]);
|
|
252
|
-
const center = touchCenter(touches[0], touches[1], rect);
|
|
253
|
-
const scale = dist / gesture.lastDist;
|
|
254
|
-
engine.zoomAtPoint(center.x, center.y, scale - 1);
|
|
255
|
-
engine.panBy(center.x - gesture.lastCx, center.y - gesture.lastCy);
|
|
256
|
-
gesture.lastDist = dist;
|
|
257
|
-
gesture.lastCx = center.x;
|
|
258
|
-
gesture.lastCy = center.y;
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
if (touches.length >= 2) {
|
|
262
|
-
cancelEngineGesture();
|
|
263
|
-
const dist = touchDist(touches[0], touches[1]);
|
|
264
|
-
const center = touchCenter(touches[0], touches[1], rect);
|
|
265
|
-
gesture = { type: "pinching", lastDist: dist, lastCx: center.x, lastCy: center.y };
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (touches.length < 1) return;
|
|
269
|
-
const touch = touches[0];
|
|
270
|
-
const x = touch.clientX - rect.left;
|
|
271
|
-
const y = touch.clientY - rect.top;
|
|
272
|
-
if (gesture.type === "pending-pan") {
|
|
273
|
-
if (Math.abs(x - gesture.x) > chunkVSHXWTJH_cjs.DEAD_ZONE_TOUCH_PX || Math.abs(y - gesture.y) > chunkVSHXWTJH_cjs.DEAD_ZONE_TOUCH_PX) {
|
|
274
|
-
gesture = { type: "panning", lastX: x, lastY: y };
|
|
275
|
-
}
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
if (gesture.type === "panning") {
|
|
279
|
-
engine.panBy(x - gesture.lastX, y - gesture.lastY);
|
|
280
|
-
gesture.lastX = x;
|
|
281
|
-
gesture.lastY = y;
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (gesture.type === "pending-entity" || gesture.type === "entity-dragging") {
|
|
285
|
-
engine.handlePointerMove(x, y, noMods);
|
|
286
|
-
if (gesture.type === "pending-entity") {
|
|
287
|
-
if (Math.abs(x - gesture.x) > chunkVSHXWTJH_cjs.DEAD_ZONE_TOUCH_PX || Math.abs(y - gesture.y) > chunkVSHXWTJH_cjs.DEAD_ZONE_TOUCH_PX) {
|
|
288
|
-
gesture = { type: "entity-dragging" };
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
function onTouchEnd(e) {
|
|
294
|
-
e.preventDefault();
|
|
295
|
-
const remaining = e.touches.length;
|
|
296
|
-
const rect = getRect();
|
|
297
|
-
if (gesture.type === "pinching") {
|
|
298
|
-
if (remaining === 1) {
|
|
299
|
-
const t = e.touches[0];
|
|
300
|
-
gesture = {
|
|
301
|
-
type: "panning",
|
|
302
|
-
lastX: t.clientX - rect.left,
|
|
303
|
-
lastY: t.clientY - rect.top
|
|
304
|
-
};
|
|
305
|
-
} else if (remaining === 0) {
|
|
306
|
-
gesture = { type: "idle" };
|
|
307
|
-
}
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (remaining > 0) return;
|
|
311
|
-
if (gesture.type === "pending-pan") {
|
|
312
|
-
engine.handlePointerDown(gesture.x, gesture.y, 0, noMods);
|
|
313
|
-
engine.handlePointerUp();
|
|
314
|
-
engine.markDirty();
|
|
315
|
-
lastTapTime = Date.now();
|
|
316
|
-
lastTapX = gesture.x;
|
|
317
|
-
lastTapY = gesture.y;
|
|
318
|
-
}
|
|
319
|
-
if (gesture.type === "pending-entity") {
|
|
320
|
-
engine.handlePointerUp();
|
|
321
|
-
engine.markDirty();
|
|
322
|
-
lastTapTime = Date.now();
|
|
323
|
-
lastTapX = gesture.x;
|
|
324
|
-
lastTapY = gesture.y;
|
|
325
|
-
}
|
|
326
|
-
if (gesture.type === "entity-dragging") {
|
|
327
|
-
engine.handlePointerUp();
|
|
328
|
-
engine.markDirty();
|
|
329
|
-
}
|
|
330
|
-
gesture = { type: "idle" };
|
|
331
|
-
}
|
|
332
|
-
function onTouchCancel(_e) {
|
|
333
|
-
gesture = { type: "idle" };
|
|
334
|
-
engine.handlePointerCancel();
|
|
335
|
-
}
|
|
336
|
-
container.addEventListener("touchstart", onTouchStart, { passive: false });
|
|
337
|
-
container.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
338
|
-
container.addEventListener("touchend", onTouchEnd, { passive: false });
|
|
339
|
-
container.addEventListener("touchcancel", onTouchCancel, { passive: true });
|
|
340
|
-
return () => {
|
|
341
|
-
container.removeEventListener("touchstart", onTouchStart);
|
|
342
|
-
container.removeEventListener("touchmove", onTouchMove);
|
|
343
|
-
container.removeEventListener("touchend", onTouchEnd);
|
|
344
|
-
container.removeEventListener("touchcancel", onTouchCancel);
|
|
345
|
-
};
|
|
346
|
-
}, [engine]);
|
|
347
|
-
const onCanvasPointerDown = React.useCallback(
|
|
348
|
-
(e) => {
|
|
349
|
-
const target = e.target;
|
|
350
|
-
if (target?.closest("button, input, textarea, select, [contenteditable]")) return;
|
|
351
|
-
const rect = containerRef.current?.getBoundingClientRect();
|
|
352
|
-
if (!rect) return;
|
|
353
|
-
const directive = engine.handlePointerDown(
|
|
354
|
-
e.clientX - rect.left,
|
|
355
|
-
e.clientY - rect.top,
|
|
356
|
-
e.button,
|
|
357
|
-
{
|
|
358
|
-
shift: e.shiftKey,
|
|
359
|
-
ctrl: e.ctrlKey,
|
|
360
|
-
alt: e.altKey,
|
|
361
|
-
meta: e.metaKey
|
|
362
|
-
}
|
|
363
|
-
);
|
|
364
|
-
if (directive.action === "capture-resize" || directive.action === "passthrough-track-drag") {
|
|
365
|
-
containerRef.current?.setPointerCapture(e.pointerId);
|
|
366
|
-
}
|
|
367
|
-
if (directive.action === "capture-resize") e.preventDefault();
|
|
368
|
-
},
|
|
369
|
-
[engine]
|
|
370
|
-
);
|
|
371
|
-
const onCanvasPointerMove = React.useCallback(
|
|
372
|
-
(e) => {
|
|
373
|
-
const target = e.target;
|
|
374
|
-
if (target.closest?.("[data-widget-slot]") && target !== containerRef.current) {
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
const rect = containerRef.current?.getBoundingClientRect();
|
|
378
|
-
if (!rect) return;
|
|
379
|
-
engine.handlePointerMove(e.clientX - rect.left, e.clientY - rect.top, {
|
|
380
|
-
shift: e.shiftKey,
|
|
381
|
-
ctrl: e.ctrlKey,
|
|
382
|
-
alt: e.altKey,
|
|
383
|
-
meta: e.metaKey
|
|
384
|
-
});
|
|
385
|
-
},
|
|
386
|
-
[engine]
|
|
387
|
-
);
|
|
388
|
-
const onCanvasPointerUp = React.useCallback(
|
|
389
|
-
(e) => {
|
|
390
|
-
if (containerRef.current?.hasPointerCapture(e.pointerId)) {
|
|
391
|
-
containerRef.current.releasePointerCapture(e.pointerId);
|
|
392
|
-
}
|
|
393
|
-
engine.handlePointerUp();
|
|
394
|
-
},
|
|
395
|
-
[engine]
|
|
396
|
-
);
|
|
397
|
-
React.useEffect(() => {
|
|
398
|
-
let rafId;
|
|
399
|
-
let running = true;
|
|
400
|
-
function loop() {
|
|
401
|
-
if (!running) return;
|
|
402
|
-
const didTick = engine.flushIfDirty();
|
|
403
|
-
if (didTick) {
|
|
404
|
-
const camera2 = engine.getCamera();
|
|
405
|
-
const changes = engine.getFrameChanges();
|
|
406
|
-
if (cameraLayerRef.current) {
|
|
407
|
-
cameraLayerRef.current.style.transform = `scale(${camera2.zoom}) translate(${-camera2.x}px, ${-camera2.y}px)`;
|
|
408
|
-
}
|
|
409
|
-
const cursor = engine.world.getResource(chunkVSHXWTJH_cjs.CursorResource).cursor;
|
|
410
|
-
if (containerRef.current && containerRef.current.style.cursor !== cursor) {
|
|
411
|
-
containerRef.current.style.cursor = cursor;
|
|
412
|
-
}
|
|
413
|
-
if (gridRendererRef.current) {
|
|
414
|
-
gridRendererRef.current.render(camera2.x, camera2.y, camera2.zoom);
|
|
415
|
-
}
|
|
416
|
-
if (selectionRendererRef.current && gridRendererRef.current) {
|
|
417
|
-
const selected = engine.getSelectedEntities();
|
|
418
|
-
const selBounds = [];
|
|
419
|
-
for (const id of selected) {
|
|
420
|
-
const wb = engine.get(id, chunkVSHXWTJH_cjs.WorldBounds);
|
|
421
|
-
if (wb)
|
|
422
|
-
selBounds.push({
|
|
423
|
-
x: wb.worldX,
|
|
424
|
-
y: wb.worldY,
|
|
425
|
-
width: wb.worldWidth,
|
|
426
|
-
height: wb.worldHeight
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
const hovId = engine.getHoveredEntity();
|
|
430
|
-
let hovBounds = null;
|
|
431
|
-
if (hovId !== null) {
|
|
432
|
-
const wb = engine.get(hovId, chunkVSHXWTJH_cjs.WorldBounds);
|
|
433
|
-
if (wb)
|
|
434
|
-
hovBounds = {
|
|
435
|
-
x: wb.worldX,
|
|
436
|
-
y: wb.worldY,
|
|
437
|
-
width: wb.worldWidth,
|
|
438
|
-
height: wb.worldHeight
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
selectionRendererRef.current.render(
|
|
442
|
-
gridRendererRef.current.getWebGLRenderer(),
|
|
443
|
-
camera2.x,
|
|
444
|
-
camera2.y,
|
|
445
|
-
camera2.zoom,
|
|
446
|
-
selBounds,
|
|
447
|
-
hovBounds,
|
|
448
|
-
engine.getSnapGuides(),
|
|
449
|
-
engine.getEqualSpacing()
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
for (const entityId of changes.positionsChanged) {
|
|
453
|
-
const el = slotRefs.current.get(entityId);
|
|
454
|
-
if (!el) continue;
|
|
455
|
-
const wb = engine.get(entityId, chunkVSHXWTJH_cjs.WorldBounds);
|
|
456
|
-
if (!wb) continue;
|
|
457
|
-
el.style.transform = `translate(${wb.worldX}px, ${wb.worldY}px)`;
|
|
458
|
-
el.style.width = `${wb.worldWidth}px`;
|
|
459
|
-
el.style.height = `${wb.worldHeight}px`;
|
|
460
|
-
}
|
|
461
|
-
if (changes.entered.length > 0 || changes.exited.length > 0) {
|
|
462
|
-
const visible2 = engine.getVisibleEntities();
|
|
463
|
-
setVisibleEntities(visible2.map((v) => v.entityId));
|
|
464
|
-
}
|
|
465
|
-
if (changes.selectionChanged && onSelectionChangeRef.current) {
|
|
466
|
-
onSelectionChangeRef.current(engine.getSelectedEntities());
|
|
467
|
-
}
|
|
468
|
-
if (changes.cameraChanged && onCameraChangeRef.current) {
|
|
469
|
-
onCameraChangeRef.current({ x: camera2.x, y: camera2.y, zoom: camera2.zoom });
|
|
470
|
-
}
|
|
471
|
-
if (changes.navigationChanged && onNavigationChangeRef.current) {
|
|
472
|
-
const navStack = engine.world.getResource(chunkVSHXWTJH_cjs.NavigationStackResource);
|
|
473
|
-
const depth = navStack.frames.length - 1;
|
|
474
|
-
const containerId = navStack.frames[navStack.frames.length - 1].containerId;
|
|
475
|
-
onNavigationChangeRef.current(depth, containerId);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
rafId = requestAnimationFrame(loop);
|
|
479
|
-
}
|
|
480
|
-
engine.tick();
|
|
481
|
-
const visible = engine.getVisibleEntities();
|
|
482
|
-
setVisibleEntities(visible.map((v) => v.entityId));
|
|
483
|
-
const camera = engine.getCamera();
|
|
484
|
-
if (cameraLayerRef.current) {
|
|
485
|
-
cameraLayerRef.current.style.transform = `scale(${camera.zoom}) translate(${-camera.x}px, ${-camera.y}px)`;
|
|
486
|
-
}
|
|
487
|
-
if (gridRendererRef.current) {
|
|
488
|
-
gridRendererRef.current.render(camera.x, camera.y, camera.zoom);
|
|
489
|
-
}
|
|
490
|
-
for (const v of visible) {
|
|
491
|
-
const el = slotRefs.current.get(v.entityId);
|
|
492
|
-
if (!el) continue;
|
|
493
|
-
el.style.transform = `translate(${v.worldX}px, ${v.worldY}px)`;
|
|
494
|
-
el.style.width = `${v.worldWidth}px`;
|
|
495
|
-
el.style.height = `${v.worldHeight}px`;
|
|
496
|
-
}
|
|
497
|
-
rafId = requestAnimationFrame(loop);
|
|
498
|
-
return () => {
|
|
499
|
-
running = false;
|
|
500
|
-
cancelAnimationFrame(rafId);
|
|
501
|
-
};
|
|
502
|
-
}, [engine]);
|
|
503
|
-
React.useLayoutEffect(() => {
|
|
504
|
-
for (const entityId of visibleEntities) {
|
|
505
|
-
const el = slotRefs.current.get(entityId);
|
|
506
|
-
if (!el) continue;
|
|
507
|
-
const wb = engine.get(entityId, chunkVSHXWTJH_cjs.WorldBounds);
|
|
508
|
-
if (!wb) continue;
|
|
509
|
-
el.style.transform = `translate(${wb.worldX}px, ${wb.worldY}px)`;
|
|
510
|
-
el.style.width = `${wb.worldWidth}px`;
|
|
511
|
-
el.style.height = `${wb.worldHeight}px`;
|
|
512
|
-
}
|
|
513
|
-
}, [visibleEntities, engine]);
|
|
514
|
-
const { domEntities, webglEntities } = React.useMemo(() => {
|
|
515
|
-
const dom = [];
|
|
516
|
-
const webgl = [];
|
|
517
|
-
for (const id of visibleEntities) {
|
|
518
|
-
const w = engine.get(id, chunkVSHXWTJH_cjs.Widget);
|
|
519
|
-
if (w?.surface === "webgl") {
|
|
520
|
-
webgl.push(id);
|
|
521
|
-
} else {
|
|
522
|
-
dom.push(id);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
return { domEntities: dom, webglEntities: webgl };
|
|
526
|
-
}, [visibleEntities, engine]);
|
|
527
|
-
const canvasContent = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
528
|
-
"div",
|
|
529
|
-
{
|
|
530
|
-
ref: containerRef,
|
|
531
|
-
className: `relative overflow-hidden ${className ?? ""}`,
|
|
532
|
-
style: {
|
|
533
|
-
...style,
|
|
534
|
-
touchAction: "none",
|
|
535
|
-
backgroundColor: "var(--canvas-bg, #fafafa)"
|
|
536
|
-
},
|
|
537
|
-
onPointerDown: onCanvasPointerDown,
|
|
538
|
-
onPointerMove: onCanvasPointerMove,
|
|
539
|
-
onPointerUp: onCanvasPointerUp,
|
|
540
|
-
children: [
|
|
541
|
-
/* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: webglCanvasRef, className: "absolute inset-0 pointer-events-none" }),
|
|
542
|
-
webglEntities.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(WebGLWidgetBridge, { engine, entities: webglEntities }),
|
|
543
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 pointer-events-none" }),
|
|
544
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
545
|
-
"div",
|
|
546
|
-
{
|
|
547
|
-
ref: cameraLayerRef,
|
|
548
|
-
className: "absolute left-0 top-0 origin-top-left will-change-transform",
|
|
549
|
-
children: [
|
|
550
|
-
domEntities.map((entityId) => /* @__PURE__ */ jsxRuntime.jsx(chunkVSHXWTJH_cjs.WidgetSlot, { entityId, slotRef: registerSlotRef }, entityId)),
|
|
551
|
-
webglEntities.map((entityId) => /* @__PURE__ */ jsxRuntime.jsx(chunkVSHXWTJH_cjs.SelectionOverlaySlot, { entityId, slotRef: registerSlotRef }, entityId))
|
|
552
|
-
]
|
|
553
|
-
}
|
|
554
|
-
),
|
|
555
|
-
children
|
|
556
|
-
]
|
|
557
|
-
}
|
|
558
|
-
);
|
|
559
|
-
return /* @__PURE__ */ jsxRuntime.jsx(chunkVSHXWTJH_cjs.EngineProvider, { value: engine, children: /* @__PURE__ */ jsxRuntime.jsx(chunkVSHXWTJH_cjs.ContainerRefProvider, { value: containerRef, children: /* @__PURE__ */ jsxRuntime.jsx(WidgetProvider, { engine, children: canvasContent }) }) });
|
|
560
|
-
}
|
|
561
|
-
);
|
|
562
|
-
function WebGLWidgetBridge({ engine, entities }) {
|
|
563
|
-
const resolver = chunkVSHXWTJH_cjs.useWidgetResolver();
|
|
564
|
-
const resolve = React.useCallback(
|
|
565
|
-
(entityId) => {
|
|
566
|
-
if (!resolver) return null;
|
|
567
|
-
const w = engine.get(entityId, chunkVSHXWTJH_cjs.Widget);
|
|
568
|
-
return resolver(entityId, w?.type ?? "");
|
|
569
|
-
},
|
|
570
|
-
[resolver, engine]
|
|
571
|
-
);
|
|
572
|
-
if (!resolver) return null;
|
|
573
|
-
return /* @__PURE__ */ jsxRuntime.jsx(chunkVSHXWTJH_cjs.WebGLWidgetLayer, { engine, entities, resolve });
|
|
574
|
-
}
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_WebGLWidgetLayer = require("./WebGLWidgetLayer-BBMuwzHq.cjs");
|
|
3
|
+
const require_hooks = require("./hooks-DHShH86C.cjs");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
react = require_WebGLWidgetLayer.__toESM(react, 1);
|
|
6
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
|
+
let _react_three_fiber = require("@react-three/fiber");
|
|
8
|
+
let three = require("three");
|
|
9
|
+
//#region src/react/widget-hooks.ts
|
|
10
|
+
/**
|
|
11
|
+
* Returns the custom data attached to a widget entity.
|
|
12
|
+
* Use the generic parameter for type safety: `useWidgetData<MyData>(entityId)`. Re-renders when data changes.
|
|
13
|
+
*/
|
|
575
14
|
function useWidgetData(entityId) {
|
|
576
|
-
|
|
577
|
-
return comp?.data ?? {};
|
|
15
|
+
return require_hooks.useComponent(entityId, require_hooks.WidgetData)?.data ?? {};
|
|
578
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns the current responsive breakpoint for a widget based on its screen-space size.
|
|
19
|
+
* Re-renders when the breakpoint changes.
|
|
20
|
+
*/
|
|
579
21
|
function useBreakpoint(entityId) {
|
|
580
|
-
|
|
581
|
-
return comp?.current ?? "normal";
|
|
22
|
+
return require_hooks.useComponent(entityId, require_hooks.WidgetBreakpoint)?.current ?? "normal";
|
|
582
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns child entity IDs of a container entity.
|
|
26
|
+
* Re-renders when children are added or removed.
|
|
27
|
+
*/
|
|
583
28
|
function useChildren(entityId) {
|
|
584
|
-
|
|
585
|
-
return comp?.ids ?? [];
|
|
29
|
+
return require_hooks.useComponent(entityId, require_hooks.Children)?.ids ?? [];
|
|
586
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns whether the entity is currently selected.
|
|
33
|
+
* Re-renders when the entity's selection state changes.
|
|
34
|
+
*/
|
|
587
35
|
function useIsSelected(entityId) {
|
|
588
|
-
|
|
36
|
+
return require_hooks.useTag(entityId, require_hooks.Selected);
|
|
589
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns a function to update the widget's custom data.
|
|
40
|
+
* Merges the patch into existing data via shallow spread.
|
|
41
|
+
*/
|
|
590
42
|
function useUpdateWidget(entityId) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
600
|
-
},
|
|
601
|
-
[engine, entityId]
|
|
602
|
-
);
|
|
43
|
+
const engine = require_hooks.useLayoutEngine();
|
|
44
|
+
return (0, react.useCallback)((patch) => {
|
|
45
|
+
const existing = engine.get(entityId, require_hooks.WidgetData);
|
|
46
|
+
if (existing) engine.set(entityId, require_hooks.WidgetData, { data: {
|
|
47
|
+
...existing.data,
|
|
48
|
+
...patch
|
|
49
|
+
} });
|
|
50
|
+
}, [engine, entityId]);
|
|
603
51
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
})
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
})
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
})
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/react/card.tsx
|
|
54
|
+
/**
|
|
55
|
+
* Built-in preset sizes, matching `CardPresetsResource` defaults.
|
|
56
|
+
* Used by `createCardWidget` to set `defaultSize` at widget-registration
|
|
57
|
+
* time (before the engine is constructed).
|
|
58
|
+
*/
|
|
59
|
+
const DEFAULT_CARD_PRESET_SIZES$1 = {
|
|
60
|
+
small: {
|
|
61
|
+
width: 155,
|
|
62
|
+
height: 155
|
|
63
|
+
},
|
|
64
|
+
medium: {
|
|
65
|
+
width: 329,
|
|
66
|
+
height: 155
|
|
67
|
+
},
|
|
68
|
+
large: {
|
|
69
|
+
width: 329,
|
|
70
|
+
height: 345
|
|
71
|
+
},
|
|
72
|
+
xl: {
|
|
73
|
+
width: 329,
|
|
74
|
+
height: 535
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Visual chrome for an iOS-style card: rounded corners, hairline ring,
|
|
79
|
+
* soft drop shadow, and a subtle lift (scale + stronger shadow) while
|
|
80
|
+
* the entity carries the `Dragging` tag.
|
|
81
|
+
*
|
|
82
|
+
* Uses CSS transitions — no animation library dependency.
|
|
83
|
+
*/
|
|
84
|
+
function CardFrame({ entityId, children, className, style }) {
|
|
85
|
+
const dragging = require_hooks.useTag(entityId, require_hooks.Dragging);
|
|
86
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
87
|
+
className,
|
|
88
|
+
style: {
|
|
89
|
+
width: "100%",
|
|
90
|
+
height: "100%",
|
|
91
|
+
borderRadius: "21.67px",
|
|
92
|
+
overflow: "hidden",
|
|
93
|
+
boxShadow: dragging ? "0 30px 60px rgba(0,0,0,0.22), 0 0 0 1px rgba(0,0,0,0.06)" : "0 20px 40px rgba(0,0,0,0.15), 0 0 0 1px rgba(0,0,0,0.05)",
|
|
94
|
+
transform: dragging ? "scale(1.05)" : "scale(1)",
|
|
95
|
+
transformOrigin: "center center",
|
|
96
|
+
transition: "transform 180ms cubic-bezier(0.2, 0.9, 0.3, 1.2), box-shadow 180ms cubic-bezier(0.2, 0.9, 0.3, 1.2)",
|
|
97
|
+
willChange: dragging ? "transform, box-shadow" : void 0,
|
|
98
|
+
...style
|
|
99
|
+
},
|
|
100
|
+
children
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Returns a paired widget + archetype for an iOS-style card. Register both
|
|
105
|
+
* with `createLayoutEngine({ widgets: [card.widget], archetypes: [card.archetype] })`
|
|
106
|
+
* (or via `engine.registerWidget` / `engine.registerArchetype`) and spawn with
|
|
107
|
+
* `engine.spawn('your-card-type', { at, data })`.
|
|
108
|
+
*
|
|
109
|
+
* The produced widget is non-resizable (Selectable + Draggable only), wrapped
|
|
110
|
+
* in `<CardFrame>`, and spawns with a `Card` component so `cardSystem` enforces
|
|
111
|
+
* the preset size each tick.
|
|
112
|
+
*/
|
|
113
|
+
function createCardWidget(opts) {
|
|
114
|
+
const defaultSize = DEFAULT_CARD_PRESET_SIZES$1[opts.size];
|
|
115
|
+
const Render = opts.render;
|
|
116
|
+
const Component = ({ entityId }) => {
|
|
117
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardFrame, {
|
|
118
|
+
entityId,
|
|
119
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Render, {
|
|
120
|
+
entityId,
|
|
121
|
+
data: useWidgetData(entityId)
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
widget: {
|
|
127
|
+
type: opts.type,
|
|
128
|
+
schema: opts.schema,
|
|
129
|
+
defaultData: opts.defaultData,
|
|
130
|
+
defaultSize,
|
|
131
|
+
component: Component
|
|
132
|
+
},
|
|
133
|
+
archetype: {
|
|
134
|
+
id: opts.type,
|
|
135
|
+
widget: opts.type,
|
|
136
|
+
components: [[require_hooks.Card, { preset: opts.size }]],
|
|
137
|
+
interactive: {
|
|
138
|
+
selectable: true,
|
|
139
|
+
draggable: true,
|
|
140
|
+
resizable: false,
|
|
141
|
+
selectionFrame: false
|
|
142
|
+
},
|
|
143
|
+
defaultSize
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/react/geometry-card.tsx
|
|
149
|
+
/** Must match {@link CardPresetsResource} defaults. */
|
|
150
|
+
const DEFAULT_CARD_PRESET_SIZES = {
|
|
151
|
+
small: {
|
|
152
|
+
width: 155,
|
|
153
|
+
height: 155
|
|
154
|
+
},
|
|
155
|
+
medium: {
|
|
156
|
+
width: 329,
|
|
157
|
+
height: 155
|
|
158
|
+
},
|
|
159
|
+
large: {
|
|
160
|
+
width: 329,
|
|
161
|
+
height: 345
|
|
162
|
+
},
|
|
163
|
+
xl: {
|
|
164
|
+
width: 329,
|
|
165
|
+
height: 535
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Pure-three rounded-rect extrude geometry — avoids a drei dependency.
|
|
170
|
+
* Rounded corners match the DOM CardFrame radius (21.67 px).
|
|
171
|
+
*/
|
|
172
|
+
function makeRoundedCardGeometry(width, height, radius, depth) {
|
|
173
|
+
const shape = new three.Shape();
|
|
174
|
+
const r = Math.min(radius, Math.min(width, height) / 2);
|
|
175
|
+
const x = -width / 2;
|
|
176
|
+
const y = -height / 2;
|
|
177
|
+
shape.moveTo(x, y + r);
|
|
178
|
+
shape.lineTo(x, y + height - r);
|
|
179
|
+
shape.quadraticCurveTo(x, y + height, x + r, y + height);
|
|
180
|
+
shape.lineTo(x + width - r, y + height);
|
|
181
|
+
shape.quadraticCurveTo(x + width, y + height, x + width, y + height - r);
|
|
182
|
+
shape.lineTo(x + width, y + r);
|
|
183
|
+
shape.quadraticCurveTo(x + width, y, x + width - r, y);
|
|
184
|
+
shape.lineTo(x + r, y);
|
|
185
|
+
shape.quadraticCurveTo(x, y, x, y + r);
|
|
186
|
+
return new three.ExtrudeGeometry(shape, {
|
|
187
|
+
depth,
|
|
188
|
+
bevelEnabled: true,
|
|
189
|
+
bevelSegments: 3,
|
|
190
|
+
bevelSize: .6,
|
|
191
|
+
bevelThickness: .6
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function CardBack({ width, height, color, roughness, metalness }) {
|
|
195
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("mesh", {
|
|
196
|
+
geometry: (0, react.useMemo)(() => makeRoundedCardGeometry(width, height, 21.67, 3), [width, height]),
|
|
197
|
+
position: [
|
|
198
|
+
0,
|
|
199
|
+
0,
|
|
200
|
+
-6
|
|
201
|
+
],
|
|
202
|
+
receiveShadow: true,
|
|
203
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("meshStandardMaterial", {
|
|
204
|
+
color,
|
|
205
|
+
roughness,
|
|
206
|
+
metalness
|
|
207
|
+
})
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Returns a paired R3F widget + archetype for a card-shaped 3D widget.
|
|
212
|
+
* Behaves like {@link createCardWidget} — fixed preset size, non-resizable,
|
|
213
|
+
* no engine-drawn selection frame, and lifts on drag (scale + z) — but
|
|
214
|
+
* renders a three.js scene instead of DOM content.
|
|
215
|
+
*
|
|
216
|
+
* Lighting: this helper adds no lights. Declare your own in the `geometry`
|
|
217
|
+
* component (typically a local `pointLight` scoped with `distance`).
|
|
218
|
+
*/
|
|
219
|
+
function createGeometryCardWidget(opts) {
|
|
220
|
+
const defaultSize = DEFAULT_CARD_PRESET_SIZES[opts.size];
|
|
221
|
+
const Render = opts.geometry;
|
|
222
|
+
const backgroundConfig = opts.background ?? "card";
|
|
223
|
+
const resolvedBack = backgroundConfig === "transparent" ? null : backgroundConfig === "card" ? {
|
|
224
|
+
color: "#1C1C1E",
|
|
225
|
+
roughness: .55,
|
|
226
|
+
metalness: 0
|
|
227
|
+
} : {
|
|
228
|
+
color: backgroundConfig.color,
|
|
229
|
+
roughness: backgroundConfig.roughness ?? .55,
|
|
230
|
+
metalness: backgroundConfig.metalness ?? 0
|
|
231
|
+
};
|
|
232
|
+
const Component = ({ entityId, width, height }) => {
|
|
233
|
+
const data = useWidgetData(entityId);
|
|
234
|
+
const dragging = require_hooks.useTag(entityId, require_hooks.Dragging);
|
|
235
|
+
const groupRef = (0, react.useRef)(null);
|
|
236
|
+
(0, _react_three_fiber.useFrame)(() => {
|
|
237
|
+
const g = groupRef.current;
|
|
238
|
+
if (!g) return;
|
|
239
|
+
const targetScale = dragging ? 1.05 : 1;
|
|
240
|
+
const targetZ = dragging ? 8 : 0;
|
|
241
|
+
const s = g.scale.x;
|
|
242
|
+
g.scale.setScalar(s + (targetScale - s) * .2);
|
|
243
|
+
g.position.z += (targetZ - g.position.z) * .2;
|
|
244
|
+
});
|
|
245
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("group", {
|
|
246
|
+
ref: groupRef,
|
|
247
|
+
children: [resolvedBack && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBack, {
|
|
248
|
+
width,
|
|
249
|
+
height,
|
|
250
|
+
color: resolvedBack.color,
|
|
251
|
+
roughness: resolvedBack.roughness,
|
|
252
|
+
metalness: resolvedBack.metalness
|
|
253
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Render, {
|
|
254
|
+
entityId,
|
|
255
|
+
data,
|
|
256
|
+
width,
|
|
257
|
+
height
|
|
258
|
+
})]
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
widget: {
|
|
263
|
+
type: opts.type,
|
|
264
|
+
surface: "webgl",
|
|
265
|
+
schema: opts.schema,
|
|
266
|
+
defaultData: opts.defaultData,
|
|
267
|
+
defaultSize,
|
|
268
|
+
component: Component
|
|
269
|
+
},
|
|
270
|
+
archetype: {
|
|
271
|
+
id: opts.type,
|
|
272
|
+
widget: opts.type,
|
|
273
|
+
components: [[require_hooks.Card, { preset: opts.size }]],
|
|
274
|
+
interactive: {
|
|
275
|
+
selectable: true,
|
|
276
|
+
draggable: true,
|
|
277
|
+
resizable: false,
|
|
278
|
+
selectionFrame: false
|
|
279
|
+
},
|
|
280
|
+
defaultSize
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/react/WidgetProvider.tsx
|
|
286
|
+
/**
|
|
287
|
+
* Bridges the engine's widget registry to React context so WidgetSlot /
|
|
288
|
+
* WebGLWidgetLayer can resolve components by type.
|
|
289
|
+
*/
|
|
290
|
+
function WidgetProvider({ engine, children }) {
|
|
291
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_hooks.WidgetResolverProvider, {
|
|
292
|
+
value: (0, react.useCallback)((_entityId, widgetType) => {
|
|
293
|
+
const def = engine.getWidget(widgetType);
|
|
294
|
+
if (!def) return null;
|
|
295
|
+
if (require_WebGLWidgetLayer.isR3FWidget(def)) return {
|
|
296
|
+
surface: "webgl",
|
|
297
|
+
component: def.component
|
|
298
|
+
};
|
|
299
|
+
return {
|
|
300
|
+
surface: "dom",
|
|
301
|
+
component: def.component
|
|
302
|
+
};
|
|
303
|
+
}, [engine]),
|
|
304
|
+
children
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/react/InfiniteCanvas.tsx
|
|
309
|
+
const InfiniteCanvas = react.default.forwardRef(function InfiniteCanvas({ engine, grid, selection, onSelectionChange, onCameraChange, onNavigationChange, className, style, children }, ref) {
|
|
310
|
+
const containerRef = (0, react.useRef)(null);
|
|
311
|
+
const onSelectionChangeRef = (0, react.useRef)(onSelectionChange);
|
|
312
|
+
const onCameraChangeRef = (0, react.useRef)(onCameraChange);
|
|
313
|
+
const onNavigationChangeRef = (0, react.useRef)(onNavigationChange);
|
|
314
|
+
(0, react.useEffect)(() => {
|
|
315
|
+
onSelectionChangeRef.current = onSelectionChange;
|
|
316
|
+
}, [onSelectionChange]);
|
|
317
|
+
(0, react.useEffect)(() => {
|
|
318
|
+
onCameraChangeRef.current = onCameraChange;
|
|
319
|
+
}, [onCameraChange]);
|
|
320
|
+
(0, react.useEffect)(() => {
|
|
321
|
+
onNavigationChangeRef.current = onNavigationChange;
|
|
322
|
+
}, [onNavigationChange]);
|
|
323
|
+
(0, react.useImperativeHandle)(ref, () => ({
|
|
324
|
+
panTo: (x, y) => {
|
|
325
|
+
engine.panTo(x, y);
|
|
326
|
+
engine.markDirty();
|
|
327
|
+
},
|
|
328
|
+
zoomTo: (zoom) => {
|
|
329
|
+
engine.zoomTo(zoom);
|
|
330
|
+
engine.markDirty();
|
|
331
|
+
},
|
|
332
|
+
zoomToFit: (padding) => {
|
|
333
|
+
engine.zoomToFit(void 0, padding);
|
|
334
|
+
engine.markDirty();
|
|
335
|
+
},
|
|
336
|
+
undo: () => {
|
|
337
|
+
engine.undo();
|
|
338
|
+
engine.markDirty();
|
|
339
|
+
},
|
|
340
|
+
redo: () => {
|
|
341
|
+
engine.redo();
|
|
342
|
+
engine.markDirty();
|
|
343
|
+
},
|
|
344
|
+
getEngine: () => engine
|
|
345
|
+
}), [engine]);
|
|
346
|
+
const webglCanvasRef = (0, react.useRef)(null);
|
|
347
|
+
const gridRendererRef = (0, react.useRef)(null);
|
|
348
|
+
const selectionRendererRef = (0, react.useRef)(null);
|
|
349
|
+
const cameraLayerRef = (0, react.useRef)(null);
|
|
350
|
+
const slotRefs = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
351
|
+
const [visibleEntities, setVisibleEntities] = (0, react.useState)([]);
|
|
352
|
+
const registerSlotRef = (0, react.useCallback)((entityId, el) => {
|
|
353
|
+
if (el) slotRefs.current.set(entityId, el);
|
|
354
|
+
else slotRefs.current.delete(entityId);
|
|
355
|
+
}, []);
|
|
356
|
+
(0, react.useLayoutEffect)(() => {
|
|
357
|
+
const container = containerRef.current;
|
|
358
|
+
const canvas = webglCanvasRef.current;
|
|
359
|
+
if (!container || !canvas) return;
|
|
360
|
+
const gridEnabled = grid !== false;
|
|
361
|
+
let gridInst = null;
|
|
362
|
+
if (gridEnabled) {
|
|
363
|
+
gridInst = new require_WebGLWidgetLayer.GridRenderer(canvas);
|
|
364
|
+
gridRendererRef.current = gridInst;
|
|
365
|
+
}
|
|
366
|
+
const selInst = new require_WebGLWidgetLayer.SelectionRenderer();
|
|
367
|
+
selectionRendererRef.current = selInst;
|
|
368
|
+
const updateSize = () => {
|
|
369
|
+
const rect = container.getBoundingClientRect();
|
|
370
|
+
const dpr = window.devicePixelRatio;
|
|
371
|
+
engine.setViewport(rect.width, rect.height, dpr);
|
|
372
|
+
canvas.style.width = `${rect.width}px`;
|
|
373
|
+
canvas.style.height = `${rect.height}px`;
|
|
374
|
+
if (gridInst) gridInst.setSize(rect.width, rect.height, dpr);
|
|
375
|
+
selInst.setSize(new three.Vector2(rect.width * dpr, rect.height * dpr), dpr);
|
|
376
|
+
};
|
|
377
|
+
updateSize();
|
|
378
|
+
const observer = new ResizeObserver(updateSize);
|
|
379
|
+
observer.observe(container);
|
|
380
|
+
return () => {
|
|
381
|
+
observer.disconnect();
|
|
382
|
+
if (gridInst) {
|
|
383
|
+
gridInst.dispose();
|
|
384
|
+
gridRendererRef.current = null;
|
|
385
|
+
}
|
|
386
|
+
selInst.dispose();
|
|
387
|
+
selectionRendererRef.current = null;
|
|
388
|
+
};
|
|
389
|
+
}, [engine, grid]);
|
|
390
|
+
(0, react.useEffect)(() => {
|
|
391
|
+
const gridR = gridRendererRef.current;
|
|
392
|
+
if (gridR && grid !== false) {
|
|
393
|
+
const isDark = document.documentElement.classList.contains("dark");
|
|
394
|
+
gridR.setConfig({
|
|
395
|
+
dotColor: isDark ? [
|
|
396
|
+
1,
|
|
397
|
+
1,
|
|
398
|
+
1
|
|
399
|
+
] : [
|
|
400
|
+
0,
|
|
401
|
+
0,
|
|
402
|
+
0
|
|
403
|
+
],
|
|
404
|
+
dotAlpha: isDark ? .12 : .18,
|
|
405
|
+
...grid
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const selR = selectionRendererRef.current;
|
|
409
|
+
if (selR && selection) selR.setConfig(selection);
|
|
410
|
+
engine.markDirty();
|
|
411
|
+
}, [
|
|
412
|
+
engine,
|
|
413
|
+
grid,
|
|
414
|
+
selection
|
|
415
|
+
]);
|
|
416
|
+
(0, react.useEffect)(() => {
|
|
417
|
+
const container = containerRef.current;
|
|
418
|
+
if (!container) return;
|
|
419
|
+
const onWheel = (e) => {
|
|
420
|
+
e.preventDefault();
|
|
421
|
+
if (e.ctrlKey || e.metaKey) {
|
|
422
|
+
const rect = container.getBoundingClientRect();
|
|
423
|
+
engine.zoomAtPoint(e.clientX - rect.left, e.clientY - rect.top, -e.deltaY * .01);
|
|
424
|
+
} else engine.panBy(-e.deltaX, -e.deltaY);
|
|
425
|
+
};
|
|
426
|
+
container.addEventListener("wheel", onWheel, { passive: false });
|
|
427
|
+
return () => container.removeEventListener("wheel", onWheel);
|
|
428
|
+
}, [engine]);
|
|
429
|
+
(0, react.useEffect)(() => {
|
|
430
|
+
const container = containerRef.current;
|
|
431
|
+
if (!container) return;
|
|
432
|
+
let gesture = { type: "idle" };
|
|
433
|
+
let lastTapTime = 0;
|
|
434
|
+
let lastTapX = 0;
|
|
435
|
+
let lastTapY = 0;
|
|
436
|
+
const DOUBLE_TAP_MS = 300;
|
|
437
|
+
const DOUBLE_TAP_DIST = 30;
|
|
438
|
+
function isOnWidget(target) {
|
|
439
|
+
let el = target;
|
|
440
|
+
while (el && el !== container) {
|
|
441
|
+
if (el.hasAttribute("data-widget-slot")) return true;
|
|
442
|
+
el = el.parentElement;
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
function isInteractive(target) {
|
|
447
|
+
const el = target;
|
|
448
|
+
if (!el) return false;
|
|
449
|
+
const tag = el.tagName;
|
|
450
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "BUTTON" || tag === "SELECT" || el.isContentEditable || el.closest("button") !== null;
|
|
451
|
+
}
|
|
452
|
+
function getRect() {
|
|
453
|
+
return container?.getBoundingClientRect() ?? new DOMRect();
|
|
454
|
+
}
|
|
455
|
+
function touchDist(t1, t2) {
|
|
456
|
+
const dx = t1.clientX - t2.clientX;
|
|
457
|
+
const dy = t1.clientY - t2.clientY;
|
|
458
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
459
|
+
}
|
|
460
|
+
function touchCenter(t1, t2, rect) {
|
|
461
|
+
return {
|
|
462
|
+
x: (t1.clientX + t2.clientX) / 2 - rect.left,
|
|
463
|
+
y: (t1.clientY + t2.clientY) / 2 - rect.top
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function cancelEngineGesture() {
|
|
467
|
+
if (gesture.type === "pending-entity" || gesture.type === "entity-dragging") engine.handlePointerUp();
|
|
468
|
+
}
|
|
469
|
+
const noMods = {
|
|
470
|
+
shift: false,
|
|
471
|
+
ctrl: false,
|
|
472
|
+
alt: false,
|
|
473
|
+
meta: false
|
|
474
|
+
};
|
|
475
|
+
function onTouchStart(e) {
|
|
476
|
+
const rect = getRect();
|
|
477
|
+
const touches = e.touches;
|
|
478
|
+
if (touches.length >= 2) {
|
|
479
|
+
e.preventDefault();
|
|
480
|
+
cancelEngineGesture();
|
|
481
|
+
const dist = touchDist(touches[0], touches[1]);
|
|
482
|
+
const center = touchCenter(touches[0], touches[1], rect);
|
|
483
|
+
gesture = {
|
|
484
|
+
type: "pinching",
|
|
485
|
+
lastDist: dist,
|
|
486
|
+
lastCx: center.x,
|
|
487
|
+
lastCy: center.y
|
|
488
|
+
};
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const touch = touches[0];
|
|
492
|
+
const x = touch.clientX - rect.left;
|
|
493
|
+
const y = touch.clientY - rect.top;
|
|
494
|
+
if (isInteractive(e.target)) return;
|
|
495
|
+
e.preventDefault();
|
|
496
|
+
const now = Date.now();
|
|
497
|
+
if (now - lastTapTime < DOUBLE_TAP_MS && Math.abs(x - lastTapX) < DOUBLE_TAP_DIST && Math.abs(y - lastTapY) < DOUBLE_TAP_DIST) {
|
|
498
|
+
lastTapTime = 0;
|
|
499
|
+
const directive = engine.handlePointerDown(x, y, 0, noMods);
|
|
500
|
+
try {
|
|
501
|
+
if (directive.action === "passthrough-track-drag") {
|
|
502
|
+
const selected = engine.getSelectedEntities();
|
|
503
|
+
if (selected.length === 1) engine.enterContainer(selected[0]);
|
|
504
|
+
} else {
|
|
505
|
+
const camera = engine.getCamera();
|
|
506
|
+
const target = camera.zoom < .9 ? 1 : camera.zoom < 1.8 ? 2 : 1;
|
|
507
|
+
engine.zoomAtPoint(x, y, (target - camera.zoom) / camera.zoom);
|
|
508
|
+
}
|
|
509
|
+
} finally {
|
|
510
|
+
engine.handlePointerUp();
|
|
511
|
+
engine.markDirty();
|
|
512
|
+
}
|
|
513
|
+
gesture = { type: "idle" };
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (isOnWidget(e.target)) {
|
|
517
|
+
engine.handlePointerDown(x, y, 0, noMods);
|
|
518
|
+
gesture = {
|
|
519
|
+
type: "pending-entity",
|
|
520
|
+
x,
|
|
521
|
+
y,
|
|
522
|
+
time: now
|
|
523
|
+
};
|
|
524
|
+
} else gesture = {
|
|
525
|
+
type: "pending-pan",
|
|
526
|
+
x,
|
|
527
|
+
y,
|
|
528
|
+
time: now
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function onTouchMove(e) {
|
|
532
|
+
e.preventDefault();
|
|
533
|
+
const rect = getRect();
|
|
534
|
+
const touches = e.touches;
|
|
535
|
+
if (gesture.type === "pinching" && touches.length >= 2) {
|
|
536
|
+
const dist = touchDist(touches[0], touches[1]);
|
|
537
|
+
const center = touchCenter(touches[0], touches[1], rect);
|
|
538
|
+
const scale = dist / gesture.lastDist;
|
|
539
|
+
engine.zoomAtPoint(center.x, center.y, scale - 1);
|
|
540
|
+
engine.panBy(center.x - gesture.lastCx, center.y - gesture.lastCy);
|
|
541
|
+
gesture.lastDist = dist;
|
|
542
|
+
gesture.lastCx = center.x;
|
|
543
|
+
gesture.lastCy = center.y;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (touches.length >= 2) {
|
|
547
|
+
cancelEngineGesture();
|
|
548
|
+
const dist = touchDist(touches[0], touches[1]);
|
|
549
|
+
const center = touchCenter(touches[0], touches[1], rect);
|
|
550
|
+
gesture = {
|
|
551
|
+
type: "pinching",
|
|
552
|
+
lastDist: dist,
|
|
553
|
+
lastCx: center.x,
|
|
554
|
+
lastCy: center.y
|
|
555
|
+
};
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (touches.length < 1) return;
|
|
559
|
+
const touch = touches[0];
|
|
560
|
+
const x = touch.clientX - rect.left;
|
|
561
|
+
const y = touch.clientY - rect.top;
|
|
562
|
+
if (gesture.type === "pending-pan") {
|
|
563
|
+
if (Math.abs(x - gesture.x) > 8 || Math.abs(y - gesture.y) > 8) gesture = {
|
|
564
|
+
type: "panning",
|
|
565
|
+
lastX: x,
|
|
566
|
+
lastY: y
|
|
567
|
+
};
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (gesture.type === "panning") {
|
|
571
|
+
engine.panBy(x - gesture.lastX, y - gesture.lastY);
|
|
572
|
+
gesture.lastX = x;
|
|
573
|
+
gesture.lastY = y;
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (gesture.type === "pending-entity" || gesture.type === "entity-dragging") {
|
|
577
|
+
engine.handlePointerMove(x, y, noMods);
|
|
578
|
+
if (gesture.type === "pending-entity") {
|
|
579
|
+
if (Math.abs(x - gesture.x) > 8 || Math.abs(y - gesture.y) > 8) gesture = { type: "entity-dragging" };
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function onTouchEnd(e) {
|
|
584
|
+
e.preventDefault();
|
|
585
|
+
const remaining = e.touches.length;
|
|
586
|
+
const rect = getRect();
|
|
587
|
+
if (gesture.type === "pinching") {
|
|
588
|
+
if (remaining === 1) {
|
|
589
|
+
const t = e.touches[0];
|
|
590
|
+
gesture = {
|
|
591
|
+
type: "panning",
|
|
592
|
+
lastX: t.clientX - rect.left,
|
|
593
|
+
lastY: t.clientY - rect.top
|
|
594
|
+
};
|
|
595
|
+
} else if (remaining === 0) gesture = { type: "idle" };
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (remaining > 0) return;
|
|
599
|
+
if (gesture.type === "pending-pan") {
|
|
600
|
+
engine.handlePointerDown(gesture.x, gesture.y, 0, noMods);
|
|
601
|
+
engine.handlePointerUp();
|
|
602
|
+
engine.markDirty();
|
|
603
|
+
lastTapTime = Date.now();
|
|
604
|
+
lastTapX = gesture.x;
|
|
605
|
+
lastTapY = gesture.y;
|
|
606
|
+
}
|
|
607
|
+
if (gesture.type === "pending-entity") {
|
|
608
|
+
engine.handlePointerUp();
|
|
609
|
+
engine.markDirty();
|
|
610
|
+
lastTapTime = Date.now();
|
|
611
|
+
lastTapX = gesture.x;
|
|
612
|
+
lastTapY = gesture.y;
|
|
613
|
+
}
|
|
614
|
+
if (gesture.type === "entity-dragging") {
|
|
615
|
+
engine.handlePointerUp();
|
|
616
|
+
engine.markDirty();
|
|
617
|
+
}
|
|
618
|
+
gesture = { type: "idle" };
|
|
619
|
+
}
|
|
620
|
+
function onTouchCancel(_e) {
|
|
621
|
+
gesture = { type: "idle" };
|
|
622
|
+
engine.handlePointerCancel();
|
|
623
|
+
}
|
|
624
|
+
container.addEventListener("touchstart", onTouchStart, { passive: false });
|
|
625
|
+
container.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
626
|
+
container.addEventListener("touchend", onTouchEnd, { passive: false });
|
|
627
|
+
container.addEventListener("touchcancel", onTouchCancel, { passive: true });
|
|
628
|
+
return () => {
|
|
629
|
+
container.removeEventListener("touchstart", onTouchStart);
|
|
630
|
+
container.removeEventListener("touchmove", onTouchMove);
|
|
631
|
+
container.removeEventListener("touchend", onTouchEnd);
|
|
632
|
+
container.removeEventListener("touchcancel", onTouchCancel);
|
|
633
|
+
};
|
|
634
|
+
}, [engine]);
|
|
635
|
+
const onCanvasPointerDown = (0, react.useCallback)((e) => {
|
|
636
|
+
if (e.target?.closest("button, input, textarea, select, [contenteditable]")) return;
|
|
637
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
638
|
+
if (!rect) return;
|
|
639
|
+
const directive = engine.handlePointerDown(e.clientX - rect.left, e.clientY - rect.top, e.button, {
|
|
640
|
+
shift: e.shiftKey,
|
|
641
|
+
ctrl: e.ctrlKey,
|
|
642
|
+
alt: e.altKey,
|
|
643
|
+
meta: e.metaKey
|
|
644
|
+
});
|
|
645
|
+
if (directive.action === "capture-resize" || directive.action === "passthrough-track-drag") containerRef.current?.setPointerCapture(e.pointerId);
|
|
646
|
+
if (directive.action === "capture-resize") e.preventDefault();
|
|
647
|
+
}, [engine]);
|
|
648
|
+
const onCanvasPointerMove = (0, react.useCallback)((e) => {
|
|
649
|
+
const target = e.target;
|
|
650
|
+
if (target.closest?.("[data-widget-slot]") && target !== containerRef.current) return;
|
|
651
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
652
|
+
if (!rect) return;
|
|
653
|
+
engine.handlePointerMove(e.clientX - rect.left, e.clientY - rect.top, {
|
|
654
|
+
shift: e.shiftKey,
|
|
655
|
+
ctrl: e.ctrlKey,
|
|
656
|
+
alt: e.altKey,
|
|
657
|
+
meta: e.metaKey
|
|
658
|
+
});
|
|
659
|
+
}, [engine]);
|
|
660
|
+
const onCanvasPointerUp = (0, react.useCallback)((e) => {
|
|
661
|
+
if (containerRef.current?.hasPointerCapture(e.pointerId)) containerRef.current.releasePointerCapture(e.pointerId);
|
|
662
|
+
engine.handlePointerUp();
|
|
663
|
+
}, [engine]);
|
|
664
|
+
(0, react.useEffect)(() => {
|
|
665
|
+
let rafId;
|
|
666
|
+
let running = true;
|
|
667
|
+
function loop() {
|
|
668
|
+
if (!running) return;
|
|
669
|
+
if (engine.flushIfDirty()) {
|
|
670
|
+
const camera = engine.getCamera();
|
|
671
|
+
const changes = engine.getFrameChanges();
|
|
672
|
+
if (cameraLayerRef.current) cameraLayerRef.current.style.transform = `scale(${camera.zoom}) translate(${-camera.x}px, ${-camera.y}px)`;
|
|
673
|
+
const cursor = engine.world.getResource(require_hooks.CursorResource).cursor;
|
|
674
|
+
if (containerRef.current && containerRef.current.style.cursor !== cursor) containerRef.current.style.cursor = cursor;
|
|
675
|
+
const profiler = engine.profiler;
|
|
676
|
+
const profilerOn = profiler.isEnabled();
|
|
677
|
+
let selectionFramesDrawn = 0;
|
|
678
|
+
let snapGuidesDrawn = 0;
|
|
679
|
+
let spacingIndicatorsDrawn = 0;
|
|
680
|
+
if (gridRendererRef.current) gridRendererRef.current.getWebGLRenderer().info.reset();
|
|
681
|
+
if (gridRendererRef.current) {
|
|
682
|
+
profiler.beginWebGL("grid");
|
|
683
|
+
gridRendererRef.current.render(camera.x, camera.y, camera.zoom);
|
|
684
|
+
profiler.endWebGL("grid");
|
|
685
|
+
}
|
|
686
|
+
if (selectionRendererRef.current && gridRendererRef.current) {
|
|
687
|
+
const selected = engine.getSelectedEntities();
|
|
688
|
+
const selBounds = [];
|
|
689
|
+
for (const id of selected) {
|
|
690
|
+
if (!engine.has(id, require_hooks.SelectionFrame)) continue;
|
|
691
|
+
const wb = engine.get(id, require_hooks.WorldBounds);
|
|
692
|
+
if (wb) selBounds.push({
|
|
693
|
+
x: wb.worldX,
|
|
694
|
+
y: wb.worldY,
|
|
695
|
+
width: wb.worldWidth,
|
|
696
|
+
height: wb.worldHeight
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const hovId = engine.getHoveredEntity();
|
|
700
|
+
let hovBounds = null;
|
|
701
|
+
if (hovId !== null && engine.has(hovId, require_hooks.SelectionFrame)) {
|
|
702
|
+
const wb = engine.get(hovId, require_hooks.WorldBounds);
|
|
703
|
+
if (wb) hovBounds = {
|
|
704
|
+
x: wb.worldX,
|
|
705
|
+
y: wb.worldY,
|
|
706
|
+
width: wb.worldWidth,
|
|
707
|
+
height: wb.worldHeight
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
const snapGuides = engine.getSnapGuides();
|
|
711
|
+
const equalSpacing = engine.getEqualSpacing();
|
|
712
|
+
selectionFramesDrawn = selBounds.length + (hovBounds ? 1 : 0);
|
|
713
|
+
snapGuidesDrawn = snapGuides.length;
|
|
714
|
+
spacingIndicatorsDrawn = equalSpacing.length;
|
|
715
|
+
profiler.beginWebGL("selection");
|
|
716
|
+
selectionRendererRef.current.render(gridRendererRef.current.getWebGLRenderer(), camera.x, camera.y, camera.zoom, selBounds, hovBounds, snapGuides, equalSpacing);
|
|
717
|
+
profiler.endWebGL("selection");
|
|
718
|
+
}
|
|
719
|
+
if (profilerOn && gridRendererRef.current) {
|
|
720
|
+
const info = gridRendererRef.current.getWebGLRenderer().info;
|
|
721
|
+
profiler.recordWebGLStats({
|
|
722
|
+
drawCalls: info.render.calls,
|
|
723
|
+
triangles: info.render.triangles,
|
|
724
|
+
selectionFrames: selectionFramesDrawn,
|
|
725
|
+
snapGuides: snapGuidesDrawn,
|
|
726
|
+
spacingIndicators: spacingIndicatorsDrawn,
|
|
727
|
+
domPositionsUpdated: changes.positionsChanged.length
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
for (const entityId of changes.positionsChanged) {
|
|
731
|
+
const el = slotRefs.current.get(entityId);
|
|
732
|
+
if (!el) continue;
|
|
733
|
+
const wb = engine.get(entityId, require_hooks.WorldBounds);
|
|
734
|
+
if (!wb) continue;
|
|
735
|
+
el.style.transform = `translate(${wb.worldX}px, ${wb.worldY}px)`;
|
|
736
|
+
el.style.width = `${wb.worldWidth}px`;
|
|
737
|
+
el.style.height = `${wb.worldHeight}px`;
|
|
738
|
+
}
|
|
739
|
+
if (changes.entered.length > 0 || changes.exited.length > 0) setVisibleEntities(engine.getVisibleEntities().map((v) => v.entityId));
|
|
740
|
+
if (changes.selectionChanged && onSelectionChangeRef.current) onSelectionChangeRef.current(engine.getSelectedEntities());
|
|
741
|
+
if (changes.cameraChanged && onCameraChangeRef.current) onCameraChangeRef.current({
|
|
742
|
+
x: camera.x,
|
|
743
|
+
y: camera.y,
|
|
744
|
+
zoom: camera.zoom
|
|
745
|
+
});
|
|
746
|
+
if (changes.navigationChanged && onNavigationChangeRef.current) {
|
|
747
|
+
const navStack = engine.world.getResource(require_hooks.NavigationStackResource);
|
|
748
|
+
const depth = navStack.frames.length - 1;
|
|
749
|
+
const containerId = navStack.frames[navStack.frames.length - 1].containerId;
|
|
750
|
+
onNavigationChangeRef.current(depth, containerId);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
rafId = requestAnimationFrame(loop);
|
|
754
|
+
}
|
|
755
|
+
engine.tick();
|
|
756
|
+
const visible = engine.getVisibleEntities();
|
|
757
|
+
setVisibleEntities(visible.map((v) => v.entityId));
|
|
758
|
+
const camera = engine.getCamera();
|
|
759
|
+
if (cameraLayerRef.current) cameraLayerRef.current.style.transform = `scale(${camera.zoom}) translate(${-camera.x}px, ${-camera.y}px)`;
|
|
760
|
+
if (gridRendererRef.current) gridRendererRef.current.render(camera.x, camera.y, camera.zoom);
|
|
761
|
+
for (const v of visible) {
|
|
762
|
+
const el = slotRefs.current.get(v.entityId);
|
|
763
|
+
if (!el) continue;
|
|
764
|
+
el.style.transform = `translate(${v.worldX}px, ${v.worldY}px)`;
|
|
765
|
+
el.style.width = `${v.worldWidth}px`;
|
|
766
|
+
el.style.height = `${v.worldHeight}px`;
|
|
767
|
+
}
|
|
768
|
+
rafId = requestAnimationFrame(loop);
|
|
769
|
+
return () => {
|
|
770
|
+
running = false;
|
|
771
|
+
cancelAnimationFrame(rafId);
|
|
772
|
+
};
|
|
773
|
+
}, [engine]);
|
|
774
|
+
(0, react.useLayoutEffect)(() => {
|
|
775
|
+
for (const entityId of visibleEntities) {
|
|
776
|
+
const el = slotRefs.current.get(entityId);
|
|
777
|
+
if (!el) continue;
|
|
778
|
+
const wb = engine.get(entityId, require_hooks.WorldBounds);
|
|
779
|
+
if (!wb) continue;
|
|
780
|
+
el.style.transform = `translate(${wb.worldX}px, ${wb.worldY}px)`;
|
|
781
|
+
el.style.width = `${wb.worldWidth}px`;
|
|
782
|
+
el.style.height = `${wb.worldHeight}px`;
|
|
783
|
+
}
|
|
784
|
+
}, [visibleEntities, engine]);
|
|
785
|
+
const { domEntities, webglEntities } = (0, react.useMemo)(() => {
|
|
786
|
+
const dom = [];
|
|
787
|
+
const webgl = [];
|
|
788
|
+
for (const id of visibleEntities) if (engine.get(id, require_hooks.Widget)?.surface === "webgl") webgl.push(id);
|
|
789
|
+
else dom.push(id);
|
|
790
|
+
return {
|
|
791
|
+
domEntities: dom,
|
|
792
|
+
webglEntities: webgl
|
|
793
|
+
};
|
|
794
|
+
}, [visibleEntities, engine]);
|
|
795
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_hooks.EngineProvider, {
|
|
796
|
+
value: engine,
|
|
797
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_hooks.ContainerRefProvider, {
|
|
798
|
+
value: containerRef,
|
|
799
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetProvider, {
|
|
800
|
+
engine,
|
|
801
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
802
|
+
ref: containerRef,
|
|
803
|
+
className: `relative overflow-hidden ${className ?? ""}`,
|
|
804
|
+
style: {
|
|
805
|
+
...style,
|
|
806
|
+
touchAction: "none",
|
|
807
|
+
backgroundColor: "var(--canvas-bg, #fafafa)"
|
|
808
|
+
},
|
|
809
|
+
onPointerDown: onCanvasPointerDown,
|
|
810
|
+
onPointerMove: onCanvasPointerMove,
|
|
811
|
+
onPointerUp: onCanvasPointerUp,
|
|
812
|
+
children: [
|
|
813
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
|
|
814
|
+
ref: webglCanvasRef,
|
|
815
|
+
className: "absolute inset-0 pointer-events-none"
|
|
816
|
+
}),
|
|
817
|
+
webglEntities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WebGLWidgetBridge, {
|
|
818
|
+
engine,
|
|
819
|
+
entities: webglEntities
|
|
820
|
+
}),
|
|
821
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "absolute inset-0 pointer-events-none" }),
|
|
822
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
823
|
+
ref: cameraLayerRef,
|
|
824
|
+
className: "absolute left-0 top-0 origin-top-left will-change-transform",
|
|
825
|
+
children: [domEntities.map((entityId) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_WebGLWidgetLayer.WidgetSlot, {
|
|
826
|
+
entityId,
|
|
827
|
+
slotRef: registerSlotRef
|
|
828
|
+
}, entityId)), webglEntities.map((entityId) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_WebGLWidgetLayer.SelectionOverlaySlot, {
|
|
829
|
+
entityId,
|
|
830
|
+
slotRef: registerSlotRef
|
|
831
|
+
}, entityId))]
|
|
832
|
+
}),
|
|
833
|
+
children
|
|
834
|
+
]
|
|
835
|
+
})
|
|
836
|
+
})
|
|
837
|
+
})
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
/** Bridge component — reads widget resolver from context and passes to WebGLWidgetLayer */
|
|
841
|
+
function WebGLWidgetBridge({ engine, entities }) {
|
|
842
|
+
const resolver = require_hooks.useWidgetResolver();
|
|
843
|
+
const resolve = (0, react.useCallback)((entityId) => {
|
|
844
|
+
if (!resolver) return null;
|
|
845
|
+
return resolver(entityId, engine.get(entityId, require_hooks.Widget)?.type ?? "");
|
|
846
|
+
}, [resolver, engine]);
|
|
847
|
+
if (!resolver) return null;
|
|
848
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_WebGLWidgetLayer.WebGLWidgetLayer, {
|
|
849
|
+
engine,
|
|
850
|
+
entities,
|
|
851
|
+
resolve
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
//#endregion
|
|
855
|
+
exports.Active = require_hooks.Active;
|
|
856
|
+
exports.BreakpointConfigResource = require_hooks.BreakpointConfigResource;
|
|
857
|
+
exports.CameraResource = require_hooks.CameraResource;
|
|
858
|
+
exports.Card = require_hooks.Card;
|
|
859
|
+
exports.CardFrame = CardFrame;
|
|
860
|
+
exports.CardPresetsResource = require_hooks.CardPresetsResource;
|
|
861
|
+
exports.Children = require_hooks.Children;
|
|
862
|
+
exports.CommandBuffer = require_WebGLWidgetLayer.CommandBuffer;
|
|
863
|
+
exports.Container = require_hooks.Container;
|
|
864
|
+
exports.CursorHint = require_hooks.CursorHint;
|
|
865
|
+
exports.CursorResource = require_hooks.CursorResource;
|
|
866
|
+
exports.DEFAULT_GRID_CONFIG = require_WebGLWidgetLayer.DEFAULT_GRID_CONFIG;
|
|
867
|
+
exports.DEFAULT_SELECTION_CONFIG = require_WebGLWidgetLayer.DEFAULT_SELECTION_CONFIG;
|
|
868
|
+
exports.Draggable = require_hooks.Draggable;
|
|
869
|
+
exports.Dragging = require_hooks.Dragging;
|
|
870
|
+
exports.HandleSet = require_hooks.HandleSet;
|
|
871
|
+
exports.Hitbox = require_hooks.Hitbox;
|
|
813
872
|
exports.InfiniteCanvas = InfiniteCanvas;
|
|
873
|
+
exports.InteractionRole = require_hooks.InteractionRole;
|
|
874
|
+
exports.Locked = require_hooks.Locked;
|
|
875
|
+
exports.MoveCommand = require_WebGLWidgetLayer.MoveCommand;
|
|
876
|
+
exports.NavigationStackResource = require_hooks.NavigationStackResource;
|
|
877
|
+
exports.Parent = require_hooks.Parent;
|
|
878
|
+
exports.Resizable = require_hooks.Resizable;
|
|
879
|
+
exports.ResizeCommand = require_WebGLWidgetLayer.ResizeCommand;
|
|
880
|
+
exports.Selectable = require_hooks.Selectable;
|
|
881
|
+
exports.Selected = require_hooks.Selected;
|
|
882
|
+
exports.SelectionFrame = require_hooks.SelectionFrame;
|
|
883
|
+
exports.SetComponentCommand = require_WebGLWidgetLayer.SetComponentCommand;
|
|
884
|
+
exports.Transform2D = require_hooks.Transform2D;
|
|
885
|
+
exports.ViewportResource = require_hooks.ViewportResource;
|
|
886
|
+
exports.Visible = require_hooks.Visible;
|
|
887
|
+
exports.Widget = require_hooks.Widget;
|
|
888
|
+
exports.WidgetBreakpoint = require_hooks.WidgetBreakpoint;
|
|
889
|
+
exports.WidgetData = require_hooks.WidgetData;
|
|
814
890
|
exports.WidgetProvider = WidgetProvider;
|
|
891
|
+
exports.WidgetResolverProvider = require_hooks.WidgetResolverProvider;
|
|
892
|
+
exports.WorldBounds = require_hooks.WorldBounds;
|
|
893
|
+
exports.ZIndex = require_hooks.ZIndex;
|
|
894
|
+
exports.ZoomConfigResource = require_hooks.ZoomConfigResource;
|
|
895
|
+
exports.clamp = require_WebGLWidgetLayer.clamp;
|
|
896
|
+
exports.createArchetypeRegistry = require_WebGLWidgetLayer.createArchetypeRegistry;
|
|
897
|
+
exports.createCardWidget = createCardWidget;
|
|
898
|
+
exports.createGeometryCardWidget = createGeometryCardWidget;
|
|
899
|
+
exports.createLayoutEngine = require_WebGLWidgetLayer.createLayoutEngine;
|
|
900
|
+
exports.createWidgetRegistry = require_WebGLWidgetLayer.createWidgetRegistry;
|
|
901
|
+
exports.intersectsAABB = require_WebGLWidgetLayer.intersectsAABB;
|
|
902
|
+
exports.isR3FWidget = require_WebGLWidgetLayer.isR3FWidget;
|
|
903
|
+
exports.pointInAABB = require_WebGLWidgetLayer.pointInAABB;
|
|
904
|
+
exports.screenToWorld = require_WebGLWidgetLayer.screenToWorld;
|
|
905
|
+
exports.useAllEntities = require_hooks.useAllEntities;
|
|
815
906
|
exports.useBreakpoint = useBreakpoint;
|
|
907
|
+
exports.useCamera = require_hooks.useCamera;
|
|
816
908
|
exports.useChildren = useChildren;
|
|
909
|
+
exports.useComponent = require_hooks.useComponent;
|
|
910
|
+
exports.useContainerRef = require_hooks.useContainerRef;
|
|
911
|
+
exports.useEntityComponents = require_hooks.useEntityComponents;
|
|
912
|
+
exports.useEntityTags = require_hooks.useEntityTags;
|
|
817
913
|
exports.useIsSelected = useIsSelected;
|
|
914
|
+
exports.useLayoutEngine = require_hooks.useLayoutEngine;
|
|
915
|
+
exports.useQuery = require_hooks.useQuery;
|
|
916
|
+
exports.useRegisteredComponents = require_hooks.useRegisteredComponents;
|
|
917
|
+
exports.useRegisteredTags = require_hooks.useRegisteredTags;
|
|
918
|
+
exports.useResource = require_hooks.useResource;
|
|
919
|
+
exports.useTag = require_hooks.useTag;
|
|
920
|
+
exports.useTaggedEntities = require_hooks.useTaggedEntities;
|
|
818
921
|
exports.useUpdateWidget = useUpdateWidget;
|
|
819
922
|
exports.useWidgetData = useWidgetData;
|
|
820
|
-
|
|
923
|
+
exports.useWidgetResolver = require_hooks.useWidgetResolver;
|
|
924
|
+
exports.worldBoundsToAABB = require_WebGLWidgetLayer.worldBoundsToAABB;
|
|
925
|
+
exports.worldToScreen = require_WebGLWidgetLayer.worldToScreen;
|
|
926
|
+
|
|
821
927
|
//# sourceMappingURL=index.cjs.map
|