@jamesyong42/infinite-canvas 0.1.0 → 1.1.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 +186 -44
- package/dist/SelectionRenderer-DRtwHWJ0.d.cts +102 -0
- package/dist/SelectionRenderer-rGYPadnn.d.ts +102 -0
- package/dist/advanced.cjs +26 -26
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +23 -27
- package/dist/advanced.d.ts +23 -27
- package/dist/advanced.js +3 -3
- package/dist/advanced.js.map +1 -1
- package/dist/chunk-2KBYGER3.cjs +336 -0
- package/dist/chunk-2KBYGER3.cjs.map +1 -0
- package/dist/{chunk-OFQ75B4X.cjs → chunk-FUPKRQB2.cjs} +1653 -1799
- package/dist/chunk-FUPKRQB2.cjs.map +1 -0
- package/dist/chunk-NILAZG6O.js +292 -0
- package/dist/chunk-NILAZG6O.js.map +1 -0
- package/dist/{chunk-JT3KDQYX.js → chunk-W2ZNA7HP.js} +1411 -1522
- package/dist/chunk-W2ZNA7HP.js.map +1 -0
- package/dist/devtools.cjs +628 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +19 -0
- package/dist/devtools.d.ts +19 -0
- package/dist/devtools.js +626 -0
- package/dist/devtools.js.map +1 -0
- package/dist/{context-BI_YakHi.d.ts → engine-DqgJ82tq.d.cts} +335 -221
- package/dist/{context-C6VM7KNh.d.cts → engine-DqgJ82tq.d.ts} +335 -221
- package/dist/index.cjs +149 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -50
- package/dist/index.d.ts +77 -50
- package/dist/index.js +14 -35
- package/dist/index.js.map +1 -1
- package/package.json +13 -8
- package/dist/chunk-CH3TR4LF.js +0 -475
- package/dist/chunk-CH3TR4LF.js.map +0 -1
- package/dist/chunk-JT3KDQYX.js.map +0 -1
- package/dist/chunk-OFQ75B4X.cjs.map +0 -1
- package/dist/chunk-TX3ZABAK.cjs +0 -482
- package/dist/chunk-TX3ZABAK.cjs.map +0 -1
- package/dist/ecs.cjs +0 -32
- package/dist/ecs.cjs.map +0 -1
- package/dist/ecs.d.cts +0 -49
- package/dist/ecs.d.ts +0 -49
- package/dist/ecs.js +0 -3
- package/dist/ecs.js.map +0 -1
- package/dist/profiler-DnHBllDV.d.cts +0 -165
- package/dist/profiler-DnHBllDV.d.ts +0 -165
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Build Figma-style infinite canvases in React -- drag, resize, snap, zoom, nested
|
|
|
18
18
|
- **Hierarchical navigation** -- Enter and exit nested containers with camera state preservation
|
|
19
19
|
- **ECS architecture** -- Extensible via custom components, tags, and systems with topologically-sorted scheduling
|
|
20
20
|
- **Performance** -- SDF shaders for grid and selection chrome, RBush spatial indexing, viewport culling, per-system profiling
|
|
21
|
+
- **Live ECS editor** -- Drop-in `<EcsDevtools>` panel for spawning, inspecting, and editing components and tags at runtime
|
|
21
22
|
- **Dark mode** -- Full dark mode support across canvas, widgets, and UI chrome
|
|
22
23
|
|
|
23
24
|
## Quick Start
|
|
@@ -30,39 +31,42 @@ npm install three @react-three/fiber
|
|
|
30
31
|
|
|
31
32
|
```tsx
|
|
32
33
|
import { useMemo } from 'react';
|
|
34
|
+
import type { DomWidget, EntityId } from '@jamesyong42/infinite-canvas';
|
|
33
35
|
import { createLayoutEngine, InfiniteCanvas, useWidgetData } from '@jamesyong42/infinite-canvas';
|
|
36
|
+
import { z } from 'zod';
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
const schema = z.object({ title: z.string().default('Card') });
|
|
39
|
+
type CardData = z.infer<typeof schema>;
|
|
40
|
+
|
|
41
|
+
function MyCardView({ entityId }: { entityId: EntityId }) {
|
|
42
|
+
const data = useWidgetData<CardData>(entityId);
|
|
37
43
|
return <div style={{ padding: 16, background: 'white', borderRadius: 8 }}>{data.title}</div>;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
const MyCard: DomWidget<CardData> = {
|
|
47
|
+
type: 'card',
|
|
48
|
+
schema,
|
|
49
|
+
defaultData: { title: 'Card' },
|
|
50
|
+
defaultSize: { width: 250, height: 180 },
|
|
51
|
+
component: MyCardView,
|
|
52
|
+
};
|
|
43
53
|
|
|
44
54
|
export default function App() {
|
|
45
55
|
const engine = useMemo(() => {
|
|
46
|
-
const e = createLayoutEngine({
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
position: { x: 100, y: 100 },
|
|
50
|
-
size: { width: 250, height: 180 },
|
|
51
|
-
data: { title: 'Hello World' },
|
|
56
|
+
const e = createLayoutEngine({
|
|
57
|
+
zoom: { min: 0.05, max: 8 },
|
|
58
|
+
widgets: [MyCard],
|
|
52
59
|
});
|
|
60
|
+
e.spawn('card', { at: { x: 100, y: 100 }, data: { title: 'Hello World' } });
|
|
53
61
|
return e;
|
|
54
62
|
}, []);
|
|
55
63
|
|
|
56
|
-
return
|
|
57
|
-
<InfiniteCanvas
|
|
58
|
-
engine={engine}
|
|
59
|
-
widgets={widgets}
|
|
60
|
-
style={{ width: '100vw', height: '100vh' }}
|
|
61
|
-
/>
|
|
62
|
-
);
|
|
64
|
+
return <InfiniteCanvas engine={engine} style={{ width: '100vw', height: '100vh' }} />;
|
|
63
65
|
}
|
|
64
66
|
```
|
|
65
67
|
|
|
68
|
+
Widgets declare a **schema** (any [Standard Schema v1](https://standardschema.dev)-compatible validator — Zod 3.24+, Valibot, ArkType) and **default data**. Entities are spawned by `engine.spawn(archetypeOrWidgetId, options)`. For widgets without a custom archetype, the engine synthesizes a default one that makes the entity selectable, draggable, and resizable.
|
|
69
|
+
|
|
66
70
|
## Package
|
|
67
71
|
|
|
68
72
|
Everything ships in a single package: **`@jamesyong42/infinite-canvas`**. It exposes three entry points:
|
|
@@ -70,8 +74,10 @@ Everything ships in a single package: **`@jamesyong42/infinite-canvas`**. It exp
|
|
|
70
74
|
| Import | Purpose |
|
|
71
75
|
|--------|---------|
|
|
72
76
|
| `@jamesyong42/infinite-canvas` | Main API -- `<InfiniteCanvas>`, `createLayoutEngine`, hooks, built-in components |
|
|
73
|
-
| `@jamesyong42/infinite-canvas/ecs` | ECS primitives for advanced users (`defineComponent`, `defineSystem`, `World`) |
|
|
74
77
|
| `@jamesyong42/infinite-canvas/advanced` | WebGL renderers, serialization, profiler, spatial index |
|
|
78
|
+
| `@jamesyong42/infinite-canvas/devtools` | `<EcsDevtools>` live ECS editor (see [Devtools](#devtools)) |
|
|
79
|
+
|
|
80
|
+
The underlying ECS primitives (`defineComponent`, `defineSystem`, `World`, `SystemScheduler`) live in a separate package: [**`@jamesyong42/reactive-ecs`**](https://github.com/jamesyong-42/reactive-ecs).
|
|
75
81
|
|
|
76
82
|
## Why This Library?
|
|
77
83
|
|
|
@@ -99,14 +105,18 @@ Everything ships in a single package: **`@jamesyong42/infinite-canvas`**. It exp
|
|
|
99
105
|
| `useQuery(...types)` | Entity IDs matching component/tag types |
|
|
100
106
|
| `useTaggedEntities(type)` | All entity IDs with a specific tag |
|
|
101
107
|
| `useResource<T>(type)` | Read an ECS resource reactively |
|
|
108
|
+
| `useAllEntities()` | Every live entity ID (reactive on create/destroy) |
|
|
109
|
+
| `useEntityComponents(entityId)` | `ComponentType[]` currently on an entity |
|
|
110
|
+
| `useEntityTags(entityId)` | `TagType[]` currently on an entity |
|
|
111
|
+
| `useRegisteredComponents()` | Every `ComponentType` the world has observed |
|
|
112
|
+
| `useRegisteredTags()` | Every `TagType` the world has observed |
|
|
102
113
|
| `useLayoutEngine()` | Access the `LayoutEngine` instance from context |
|
|
103
114
|
|
|
104
115
|
### InfiniteCanvas Props
|
|
105
116
|
|
|
106
117
|
| Prop | Type | Description |
|
|
107
118
|
|------|------|-------------|
|
|
108
|
-
| `engine` | `LayoutEngine` | Engine instance (required) |
|
|
109
|
-
| `widgets` | `WidgetDef[]` | Widget type definitions |
|
|
119
|
+
| `engine` | `LayoutEngine` | Engine instance (required) -- widgets and archetypes are registered on the engine, not passed as props |
|
|
110
120
|
| `grid` | `Partial<GridConfig> \| false` | Grid configuration, or `false` to disable |
|
|
111
121
|
| `selection` | `Partial<SelectionConfig>` | Selection style overrides |
|
|
112
122
|
| `onSelectionChange` | `(ids: EntityId[]) => void` | Called when selected entities change |
|
|
@@ -118,10 +128,10 @@ Everything ships in a single package: **`@jamesyong42/infinite-canvas`**. It exp
|
|
|
118
128
|
|
|
119
129
|
## Widget Development
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
A widget is a self-contained plugin: a schema describing its data, a default data object, and a React view. Export it as a `DomWidget<T>` (or `R3FWidget<T>` — see below) and register it on the engine.
|
|
122
132
|
|
|
123
133
|
```tsx
|
|
124
|
-
import type {
|
|
134
|
+
import type { DomWidget, EntityId } from '@jamesyong42/infinite-canvas';
|
|
125
135
|
import {
|
|
126
136
|
Transform2D,
|
|
127
137
|
useBreakpoint,
|
|
@@ -130,33 +140,103 @@ import {
|
|
|
130
140
|
useUpdateWidget,
|
|
131
141
|
useWidgetData,
|
|
132
142
|
} from '@jamesyong42/infinite-canvas';
|
|
143
|
+
import { z } from 'zod';
|
|
133
144
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
const schema = z.object({
|
|
146
|
+
title: z.string().default('Widget'),
|
|
147
|
+
note: z.string().default(''),
|
|
148
|
+
});
|
|
149
|
+
type MyWidgetData = z.infer<typeof schema>;
|
|
150
|
+
|
|
151
|
+
function MyWidgetView({ entityId }: { entityId: EntityId }) {
|
|
152
|
+
const data = useWidgetData<MyWidgetData>(entityId); // typed custom data
|
|
153
|
+
const breakpoint = useBreakpoint(entityId); // 'micro' | 'compact' | 'normal' | 'expanded' | 'detailed'
|
|
154
|
+
const isSelected = useIsSelected(entityId); // selection state
|
|
155
|
+
const transform = useComponent(entityId, Transform2D); // position/size
|
|
156
|
+
const updateWidget = useUpdateWidget(entityId); // patch widget data
|
|
140
157
|
|
|
141
158
|
if (breakpoint === 'micro') return <div>...</div>; // minimal view
|
|
142
159
|
if (breakpoint === 'compact') return <div>...</div>; // condensed view
|
|
143
160
|
return <div>...</div>; // full view
|
|
144
161
|
}
|
|
162
|
+
|
|
163
|
+
export const MyWidget: DomWidget<MyWidgetData> = {
|
|
164
|
+
type: 'my-widget',
|
|
165
|
+
schema,
|
|
166
|
+
defaultData: { title: 'Widget', note: '' },
|
|
167
|
+
defaultSize: { width: 280, height: 200 },
|
|
168
|
+
component: MyWidgetView,
|
|
169
|
+
};
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
DOM widget components receive only `{ entityId }`. The outer slot div is sized by CSS, so read `Transform2D` via `useComponent` if you need width/height. Register the widget via `createLayoutEngine({ widgets: [MyWidget] })` or imperatively with `engine.registerWidget(MyWidget)`.
|
|
173
|
+
|
|
174
|
+
## Archetypes
|
|
175
|
+
|
|
176
|
+
An **archetype** is a recipe for spawning an entity — a bundle of components and tags, optionally referencing a widget type. Every widget you register gets a default archetype automatically (with `Selectable`, `Draggable`, `Resizable`). Write a custom archetype when you need to attach extra behaviour like `Container` + `Children` for an enterable container, or skip the interactive defaults for a locked decoration.
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import type { Archetype, DomWidget, EntityId } from '@jamesyong42/infinite-canvas';
|
|
180
|
+
import { Children, Container } from '@jamesyong42/infinite-canvas';
|
|
181
|
+
|
|
182
|
+
export const MyContainer: DomWidget<{ title: string }> = {
|
|
183
|
+
type: 'my-container',
|
|
184
|
+
schema,
|
|
185
|
+
defaultData: { title: 'Container' },
|
|
186
|
+
defaultSize: { width: 500, height: 350 },
|
|
187
|
+
component: MyContainerView,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const MyContainerArchetype: Archetype = {
|
|
191
|
+
id: 'my-container',
|
|
192
|
+
widget: 'my-container',
|
|
193
|
+
components: [
|
|
194
|
+
[Container, { enterable: true }],
|
|
195
|
+
[Children, { ids: [] as EntityId[] }],
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Use it:
|
|
200
|
+
const engine = createLayoutEngine({
|
|
201
|
+
widgets: [MyContainer],
|
|
202
|
+
archetypes: [MyContainerArchetype],
|
|
203
|
+
});
|
|
204
|
+
const id = engine.spawn('my-container', { at: { x: 50, y: 50 }, data: { title: 'Hello' } });
|
|
145
205
|
```
|
|
146
206
|
|
|
147
|
-
|
|
207
|
+
Spawning is uniform: `engine.spawn(id, options)`. If `id` matches an archetype, that archetype is used; otherwise the engine synthesizes a default from the widget. Options:
|
|
208
|
+
|
|
209
|
+
| Option | Description |
|
|
210
|
+
|--------|-------------|
|
|
211
|
+
| `at` | World-space position. Defaults to `{ x: 0, y: 0 }`. |
|
|
212
|
+
| `size` | Overrides the widget's `defaultSize`. |
|
|
213
|
+
| `data` | Patch merged into the widget's `defaultData`. |
|
|
214
|
+
| `zIndex` | Rendering + hit-test order. |
|
|
215
|
+
| `parent` | Parent entity id for nesting. |
|
|
216
|
+
| `rotation` | Initial rotation in radians. |
|
|
148
217
|
|
|
149
218
|
## WebGL Widgets (R3F)
|
|
150
219
|
|
|
151
|
-
|
|
220
|
+
Define an `R3FWidget<T>` with `surface: 'webgl'` to render 3D content via React Three Fiber. R3F widget views receive `{ entityId, width, height }` and render in local coordinates (origin at widget centre):
|
|
152
221
|
|
|
153
222
|
```tsx
|
|
223
|
+
import type { EntityId, R3FWidget } from '@jamesyong42/infinite-canvas';
|
|
154
224
|
import { useFrame } from '@react-three/fiber';
|
|
155
225
|
import { useRef } from 'react';
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
226
|
+
import { z } from 'zod';
|
|
227
|
+
import type { Mesh } from 'three';
|
|
228
|
+
|
|
229
|
+
const schema = z.object({ color: z.string().default('hotpink') });
|
|
230
|
+
|
|
231
|
+
function My3DView({
|
|
232
|
+
entityId,
|
|
233
|
+
width,
|
|
234
|
+
height,
|
|
235
|
+
}: { entityId: EntityId; width: number; height: number }) {
|
|
236
|
+
const meshRef = useRef<Mesh>(null);
|
|
237
|
+
useFrame((_, delta) => {
|
|
238
|
+
if (meshRef.current) meshRef.current.rotation.y += delta;
|
|
239
|
+
});
|
|
160
240
|
return (
|
|
161
241
|
<mesh ref={meshRef}>
|
|
162
242
|
<boxGeometry args={[width * 0.5, height * 0.5, 50]} />
|
|
@@ -165,15 +245,20 @@ function My3DWidget({ entityId, width, height }) {
|
|
|
165
245
|
);
|
|
166
246
|
}
|
|
167
247
|
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
248
|
+
export const My3D: R3FWidget<z.infer<typeof schema>> = {
|
|
249
|
+
type: 'my-3d',
|
|
250
|
+
surface: 'webgl',
|
|
251
|
+
schema,
|
|
252
|
+
defaultData: { color: 'hotpink' },
|
|
253
|
+
defaultSize: { width: 250, height: 250 },
|
|
254
|
+
component: My3DView,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const engine = createLayoutEngine({ widgets: [My3D] });
|
|
258
|
+
engine.spawn('my-3d', { at: { x: 100, y: 100 } });
|
|
174
259
|
```
|
|
175
260
|
|
|
176
|
-
WebGL widgets get a transparent R3F canvas layered between the grid and DOM layers. The R3F camera is synced with the engine camera every frame.
|
|
261
|
+
WebGL widgets get a transparent R3F canvas layered between the grid and DOM layers. The R3F camera is synced with the engine camera every frame.
|
|
177
262
|
|
|
178
263
|
## Configuration
|
|
179
264
|
|
|
@@ -255,6 +340,42 @@ deserializeWorld(engine.world, saved, componentTypes, tagTypes);
|
|
|
255
340
|
engine.markDirty();
|
|
256
341
|
```
|
|
257
342
|
|
|
343
|
+
## Devtools
|
|
344
|
+
|
|
345
|
+
A live ECS editor ships in `@jamesyong42/infinite-canvas/devtools`. Drop it in during development to spawn widgets, inspect entities, edit components, and toggle tags at runtime — FLECS Explorer-style, but driven by the live React tree.
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
import { InfiniteCanvas } from '@jamesyong42/infinite-canvas';
|
|
349
|
+
import { EcsDevtools } from '@jamesyong42/infinite-canvas/devtools';
|
|
350
|
+
|
|
351
|
+
function App() {
|
|
352
|
+
const engine = useMemo(() => createLayoutEngine({ widgets: [MyWidget] }), []);
|
|
353
|
+
const [showDevtools, setShowDevtools] = useState(false);
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<>
|
|
357
|
+
<InfiniteCanvas engine={engine} />
|
|
358
|
+
{showDevtools && <EcsDevtools engine={engine} onClose={() => setShowDevtools(false)} />}
|
|
359
|
+
</>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
What the panel does:
|
|
365
|
+
|
|
366
|
+
- **Spawn** any registered widget at the current viewport centre.
|
|
367
|
+
- **List** all widget entities (or all entities with `show all`).
|
|
368
|
+
- **Inspect** the canvas-selected entity: its components and tags.
|
|
369
|
+
- **Edit** component fields inline — primitive types get typed inputs, everything else falls back to a JSON input. `WidgetData.data` is edited field-by-field.
|
|
370
|
+
- **Add / remove** components and toggle tags without leaving the canvas.
|
|
371
|
+
- **Destroy** entities.
|
|
372
|
+
|
|
373
|
+
Pass `engine` as a prop when the devtools render outside the `<InfiniteCanvas>` subtree (the usual case, since the panel is typically absolute-positioned above the canvas). If rendered inside, the prop is optional — it reads from context.
|
|
374
|
+
|
|
375
|
+
Styling is self-contained (a single scoped `<style>` injected once, classnames prefixed `ic-ecs-`). Dark mode is auto via `prefers-color-scheme` or an ancestor `.dark` class. No stylesheet import required.
|
|
376
|
+
|
|
377
|
+
The devtools consume the same introspection primitives (`useAllEntities`, `useEntityComponents`, `useRegisteredComponents`, etc.) that are exported from the main entry point, so you can build your own inspector UI on top of them if you need something bespoke.
|
|
378
|
+
|
|
258
379
|
## Programmatic Control
|
|
259
380
|
|
|
260
381
|
### Camera
|
|
@@ -275,6 +396,27 @@ engine.redo();
|
|
|
275
396
|
engine.markDirty();
|
|
276
397
|
```
|
|
277
398
|
|
|
399
|
+
### Spawning & ECS mutation
|
|
400
|
+
|
|
401
|
+
Runtime spawning and component edits go through the engine so it can cascade handles and mark dirty in one step:
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
// Spawn at the viewport centre (sized from the widget/archetype default)
|
|
405
|
+
const id = engine.spawnAtCameraCenter('my-widget');
|
|
406
|
+
|
|
407
|
+
// Component mutation
|
|
408
|
+
engine.addComponent(id, Container, { enterable: true });
|
|
409
|
+
engine.removeComponent(id, Container);
|
|
410
|
+
engine.set(id, Transform2D, { x: 200 }); // partial merge
|
|
411
|
+
engine.addTag(id, Selected);
|
|
412
|
+
engine.removeTag(id, Draggable);
|
|
413
|
+
|
|
414
|
+
// Widget-aware introspection
|
|
415
|
+
engine.getSchemaFor(id); // Standard Schema for the widget's data, if declared
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
All of these mark the engine dirty internally — no separate `markDirty` call needed.
|
|
419
|
+
|
|
278
420
|
### Imperative Handle
|
|
279
421
|
|
|
280
422
|
Use a ref on `<InfiniteCanvas>` for imperative control from outside:
|
|
@@ -282,7 +424,7 @@ Use a ref on `<InfiniteCanvas>` for imperative control from outside:
|
|
|
282
424
|
```tsx
|
|
283
425
|
const canvasRef = useRef<InfiniteCanvasHandle>(null);
|
|
284
426
|
|
|
285
|
-
<InfiniteCanvas ref={canvasRef} engine={engine}
|
|
427
|
+
<InfiniteCanvas ref={canvasRef} engine={engine} />
|
|
286
428
|
|
|
287
429
|
// Later:
|
|
288
430
|
canvasRef.current?.zoomToFit();
|
|
@@ -318,7 +460,7 @@ useEffect(() => {
|
|
|
318
460
|
Define custom components and systems to extend the canvas:
|
|
319
461
|
|
|
320
462
|
```tsx
|
|
321
|
-
import { defineComponent, defineSystem } from '@jamesyong42/
|
|
463
|
+
import { defineComponent, defineSystem } from '@jamesyong42/reactive-ecs';
|
|
322
464
|
import { Visible } from '@jamesyong42/infinite-canvas';
|
|
323
465
|
|
|
324
466
|
const Health = defineComponent('Health', { hp: 100, maxHp: 100 });
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { EntityId } from '@jamesyong42/reactive-ecs';
|
|
3
|
+
import { L as LayoutEngine, q as DomWidgetProps, R as R3FWidgetProps, _ as SnapGuide, s as EqualSpacingIndicator } from './engine-DqgJ82tq.cjs';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
declare const EngineProvider: react.Provider<LayoutEngine | null>;
|
|
7
|
+
declare const ContainerRefProvider: react.Provider<react.RefObject<HTMLDivElement | null> | null>;
|
|
8
|
+
declare function useContainerRef(): React.RefObject<HTMLDivElement | null> | null;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the LayoutEngine instance from the nearest InfiniteCanvas context.
|
|
11
|
+
* Throws if used outside an InfiniteCanvas provider.
|
|
12
|
+
*/
|
|
13
|
+
declare function useLayoutEngine(): LayoutEngine;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Discriminated resolution of a widget by type. The surface determines which
|
|
17
|
+
* layer renders the component and with what prop shape.
|
|
18
|
+
*/
|
|
19
|
+
type ResolvedWidget = {
|
|
20
|
+
surface: 'dom';
|
|
21
|
+
component: React.ComponentType<DomWidgetProps>;
|
|
22
|
+
} | {
|
|
23
|
+
surface: 'webgl';
|
|
24
|
+
component: React.ComponentType<R3FWidgetProps>;
|
|
25
|
+
};
|
|
26
|
+
type WidgetResolver = (entityId: EntityId, widgetType: string) => ResolvedWidget | null;
|
|
27
|
+
declare const WidgetResolverProvider: react.Provider<WidgetResolver | null>;
|
|
28
|
+
declare function useWidgetResolver(): WidgetResolver | null;
|
|
29
|
+
|
|
30
|
+
interface GridConfig {
|
|
31
|
+
/** World-unit spacings for up to 3 grid levels [fine, medium, coarse]. */
|
|
32
|
+
spacings: [number, number, number];
|
|
33
|
+
/** Dot RGB color as [r, g, b] in 0–1 range. */
|
|
34
|
+
dotColor: [number, number, number];
|
|
35
|
+
/** Base dot opacity multiplier (0–1). */
|
|
36
|
+
dotAlpha: number;
|
|
37
|
+
/** CSS-pixel range where a grid level fades in: [start, end]. */
|
|
38
|
+
fadeIn: [number, number];
|
|
39
|
+
/** CSS-pixel range where a grid level fades out: [start, end]. */
|
|
40
|
+
fadeOut: [number, number];
|
|
41
|
+
/** Dot radius range in CSS pixels [min, max]. Scaled by DPR internally. */
|
|
42
|
+
dotRadius: [number, number];
|
|
43
|
+
/** Per-level opacity weight: level i gets (base + i * step). */
|
|
44
|
+
levelWeight: [number, number];
|
|
45
|
+
}
|
|
46
|
+
declare const DEFAULT_GRID_CONFIG: GridConfig;
|
|
47
|
+
declare class GridRenderer {
|
|
48
|
+
private renderer;
|
|
49
|
+
private scene;
|
|
50
|
+
private camera;
|
|
51
|
+
private material;
|
|
52
|
+
private mesh;
|
|
53
|
+
constructor(canvas: HTMLCanvasElement);
|
|
54
|
+
/** Apply a (partial) grid config. Only provided fields are updated. */
|
|
55
|
+
setConfig(config: Partial<GridConfig>): void;
|
|
56
|
+
setSize(width: number, height: number, dpr?: number): void;
|
|
57
|
+
render(cameraX: number, cameraY: number, zoom: number): void;
|
|
58
|
+
dispose(): void;
|
|
59
|
+
/** Expose for future WebGL widget rendering */
|
|
60
|
+
getWebGLRenderer(): THREE.WebGLRenderer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface SelectionConfig {
|
|
64
|
+
/** Selection outline color [r,g,b] 0-1. Default: Figma blue. */
|
|
65
|
+
outlineColor: [number, number, number];
|
|
66
|
+
/** Selection outline width in screen px. */
|
|
67
|
+
outlineWidth: number;
|
|
68
|
+
/** Hover outline color [r,g,b] 0-1. */
|
|
69
|
+
hoverColor: [number, number, number];
|
|
70
|
+
/** Hover outline width in screen px. */
|
|
71
|
+
hoverWidth: number;
|
|
72
|
+
/** Handle size in screen px. */
|
|
73
|
+
handleSize: number;
|
|
74
|
+
/** Handle fill color [r,g,b] 0-1 (white). */
|
|
75
|
+
handleFill: [number, number, number];
|
|
76
|
+
/** Handle border color [r,g,b] 0-1 (same as outline). */
|
|
77
|
+
handleBorder: [number, number, number];
|
|
78
|
+
/** Handle border width in screen px. */
|
|
79
|
+
handleBorderWidth: number;
|
|
80
|
+
/** Group bbox dash length in screen px (0 = solid). */
|
|
81
|
+
groupDash: number;
|
|
82
|
+
}
|
|
83
|
+
declare const DEFAULT_SELECTION_CONFIG: SelectionConfig;
|
|
84
|
+
interface SelectionBounds {
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
}
|
|
90
|
+
declare class SelectionRenderer {
|
|
91
|
+
private material;
|
|
92
|
+
private mesh;
|
|
93
|
+
private scene;
|
|
94
|
+
private camera;
|
|
95
|
+
constructor();
|
|
96
|
+
setConfig(config: Partial<SelectionConfig>): void;
|
|
97
|
+
setSize(resolution: THREE.Vector2, dpr: number): void;
|
|
98
|
+
render(renderer: THREE.WebGLRenderer, cameraX: number, cameraY: number, zoom: number, selected: SelectionBounds[], hovered: SelectionBounds | null, guides?: SnapGuide[], spacings?: EqualSpacingIndicator[]): void;
|
|
99
|
+
dispose(): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { ContainerRefProvider as C, DEFAULT_GRID_CONFIG as D, EngineProvider as E, GridRenderer as G, type ResolvedWidget as R, SelectionRenderer as S, WidgetResolverProvider as W, type GridConfig as a, type SelectionConfig as b, DEFAULT_SELECTION_CONFIG as c, type SelectionBounds as d, useLayoutEngine as e, useWidgetResolver as f, useContainerRef as u };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { EntityId } from '@jamesyong42/reactive-ecs';
|
|
3
|
+
import { L as LayoutEngine, q as DomWidgetProps, R as R3FWidgetProps, _ as SnapGuide, s as EqualSpacingIndicator } from './engine-DqgJ82tq.js';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
declare const EngineProvider: react.Provider<LayoutEngine | null>;
|
|
7
|
+
declare const ContainerRefProvider: react.Provider<react.RefObject<HTMLDivElement | null> | null>;
|
|
8
|
+
declare function useContainerRef(): React.RefObject<HTMLDivElement | null> | null;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the LayoutEngine instance from the nearest InfiniteCanvas context.
|
|
11
|
+
* Throws if used outside an InfiniteCanvas provider.
|
|
12
|
+
*/
|
|
13
|
+
declare function useLayoutEngine(): LayoutEngine;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Discriminated resolution of a widget by type. The surface determines which
|
|
17
|
+
* layer renders the component and with what prop shape.
|
|
18
|
+
*/
|
|
19
|
+
type ResolvedWidget = {
|
|
20
|
+
surface: 'dom';
|
|
21
|
+
component: React.ComponentType<DomWidgetProps>;
|
|
22
|
+
} | {
|
|
23
|
+
surface: 'webgl';
|
|
24
|
+
component: React.ComponentType<R3FWidgetProps>;
|
|
25
|
+
};
|
|
26
|
+
type WidgetResolver = (entityId: EntityId, widgetType: string) => ResolvedWidget | null;
|
|
27
|
+
declare const WidgetResolverProvider: react.Provider<WidgetResolver | null>;
|
|
28
|
+
declare function useWidgetResolver(): WidgetResolver | null;
|
|
29
|
+
|
|
30
|
+
interface GridConfig {
|
|
31
|
+
/** World-unit spacings for up to 3 grid levels [fine, medium, coarse]. */
|
|
32
|
+
spacings: [number, number, number];
|
|
33
|
+
/** Dot RGB color as [r, g, b] in 0–1 range. */
|
|
34
|
+
dotColor: [number, number, number];
|
|
35
|
+
/** Base dot opacity multiplier (0–1). */
|
|
36
|
+
dotAlpha: number;
|
|
37
|
+
/** CSS-pixel range where a grid level fades in: [start, end]. */
|
|
38
|
+
fadeIn: [number, number];
|
|
39
|
+
/** CSS-pixel range where a grid level fades out: [start, end]. */
|
|
40
|
+
fadeOut: [number, number];
|
|
41
|
+
/** Dot radius range in CSS pixels [min, max]. Scaled by DPR internally. */
|
|
42
|
+
dotRadius: [number, number];
|
|
43
|
+
/** Per-level opacity weight: level i gets (base + i * step). */
|
|
44
|
+
levelWeight: [number, number];
|
|
45
|
+
}
|
|
46
|
+
declare const DEFAULT_GRID_CONFIG: GridConfig;
|
|
47
|
+
declare class GridRenderer {
|
|
48
|
+
private renderer;
|
|
49
|
+
private scene;
|
|
50
|
+
private camera;
|
|
51
|
+
private material;
|
|
52
|
+
private mesh;
|
|
53
|
+
constructor(canvas: HTMLCanvasElement);
|
|
54
|
+
/** Apply a (partial) grid config. Only provided fields are updated. */
|
|
55
|
+
setConfig(config: Partial<GridConfig>): void;
|
|
56
|
+
setSize(width: number, height: number, dpr?: number): void;
|
|
57
|
+
render(cameraX: number, cameraY: number, zoom: number): void;
|
|
58
|
+
dispose(): void;
|
|
59
|
+
/** Expose for future WebGL widget rendering */
|
|
60
|
+
getWebGLRenderer(): THREE.WebGLRenderer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface SelectionConfig {
|
|
64
|
+
/** Selection outline color [r,g,b] 0-1. Default: Figma blue. */
|
|
65
|
+
outlineColor: [number, number, number];
|
|
66
|
+
/** Selection outline width in screen px. */
|
|
67
|
+
outlineWidth: number;
|
|
68
|
+
/** Hover outline color [r,g,b] 0-1. */
|
|
69
|
+
hoverColor: [number, number, number];
|
|
70
|
+
/** Hover outline width in screen px. */
|
|
71
|
+
hoverWidth: number;
|
|
72
|
+
/** Handle size in screen px. */
|
|
73
|
+
handleSize: number;
|
|
74
|
+
/** Handle fill color [r,g,b] 0-1 (white). */
|
|
75
|
+
handleFill: [number, number, number];
|
|
76
|
+
/** Handle border color [r,g,b] 0-1 (same as outline). */
|
|
77
|
+
handleBorder: [number, number, number];
|
|
78
|
+
/** Handle border width in screen px. */
|
|
79
|
+
handleBorderWidth: number;
|
|
80
|
+
/** Group bbox dash length in screen px (0 = solid). */
|
|
81
|
+
groupDash: number;
|
|
82
|
+
}
|
|
83
|
+
declare const DEFAULT_SELECTION_CONFIG: SelectionConfig;
|
|
84
|
+
interface SelectionBounds {
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
}
|
|
90
|
+
declare class SelectionRenderer {
|
|
91
|
+
private material;
|
|
92
|
+
private mesh;
|
|
93
|
+
private scene;
|
|
94
|
+
private camera;
|
|
95
|
+
constructor();
|
|
96
|
+
setConfig(config: Partial<SelectionConfig>): void;
|
|
97
|
+
setSize(resolution: THREE.Vector2, dpr: number): void;
|
|
98
|
+
render(renderer: THREE.WebGLRenderer, cameraX: number, cameraY: number, zoom: number, selected: SelectionBounds[], hovered: SelectionBounds | null, guides?: SnapGuide[], spacings?: EqualSpacingIndicator[]): void;
|
|
99
|
+
dispose(): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { ContainerRefProvider as C, DEFAULT_GRID_CONFIG as D, EngineProvider as E, GridRenderer as G, type ResolvedWidget as R, SelectionRenderer as S, WidgetResolverProvider as W, type GridConfig as a, type SelectionConfig as b, DEFAULT_SELECTION_CONFIG as c, type SelectionBounds as d, useLayoutEngine as e, useWidgetResolver as f, useContainerRef as u };
|
package/dist/advanced.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('./chunk-
|
|
3
|
+
var chunkFUPKRQB2_cjs = require('./chunk-FUPKRQB2.cjs');
|
|
4
|
+
var chunk2KBYGER3_cjs = require('./chunk-2KBYGER3.cjs');
|
|
5
5
|
|
|
6
6
|
// src/serialization.ts
|
|
7
7
|
function serializeWorld(world, componentTypes, tagTypes, camera, navigationFrames) {
|
|
@@ -65,22 +65,22 @@ function deserializeWorld(world, doc, componentTypes, tagTypes) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
for (const [_oldId, newId] of idMap) {
|
|
68
|
-
const parent = world.getComponent(newId,
|
|
68
|
+
const parent = world.getComponent(newId, chunk2KBYGER3_cjs.Parent);
|
|
69
69
|
if (parent && idMap.has(parent.id)) {
|
|
70
70
|
const mappedId = idMap.get(parent.id);
|
|
71
71
|
if (mappedId !== void 0) {
|
|
72
|
-
world.setComponent(newId,
|
|
72
|
+
world.setComponent(newId, chunk2KBYGER3_cjs.Parent, { id: mappedId });
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
const children = world.getComponent(newId,
|
|
75
|
+
const children = world.getComponent(newId, chunk2KBYGER3_cjs.Children);
|
|
76
76
|
if (children) {
|
|
77
|
-
world.setComponent(newId,
|
|
77
|
+
world.setComponent(newId, chunk2KBYGER3_cjs.Children, {
|
|
78
78
|
ids: children.ids.map((id) => idMap.get(id) ?? id)
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
-
const handleSet = world.getComponent(newId,
|
|
81
|
+
const handleSet = world.getComponent(newId, chunk2KBYGER3_cjs.HandleSet);
|
|
82
82
|
if (handleSet) {
|
|
83
|
-
world.setComponent(newId,
|
|
83
|
+
world.setComponent(newId, chunk2KBYGER3_cjs.HandleSet, {
|
|
84
84
|
ids: handleSet.ids.map((id) => idMap.get(id) ?? id)
|
|
85
85
|
});
|
|
86
86
|
}
|
|
@@ -121,53 +121,53 @@ function serializeEntities(world, entityIds, componentTypes, tagTypes) {
|
|
|
121
121
|
return result;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
Object.defineProperty(exports, "ContainerRefProvider", {
|
|
125
|
-
enumerable: true,
|
|
126
|
-
get: function () { return chunkOFQ75B4X_cjs.ContainerRefProvider; }
|
|
127
|
-
});
|
|
128
|
-
Object.defineProperty(exports, "EngineProvider", {
|
|
129
|
-
enumerable: true,
|
|
130
|
-
get: function () { return chunkOFQ75B4X_cjs.EngineProvider; }
|
|
131
|
-
});
|
|
132
124
|
Object.defineProperty(exports, "GridRenderer", {
|
|
133
125
|
enumerable: true,
|
|
134
|
-
get: function () { return
|
|
126
|
+
get: function () { return chunkFUPKRQB2_cjs.GridRenderer; }
|
|
135
127
|
});
|
|
136
128
|
Object.defineProperty(exports, "Profiler", {
|
|
137
129
|
enumerable: true,
|
|
138
|
-
get: function () { return
|
|
130
|
+
get: function () { return chunkFUPKRQB2_cjs.Profiler; }
|
|
139
131
|
});
|
|
140
132
|
Object.defineProperty(exports, "SelectionOverlaySlot", {
|
|
141
133
|
enumerable: true,
|
|
142
|
-
get: function () { return
|
|
134
|
+
get: function () { return chunkFUPKRQB2_cjs.SelectionOverlaySlot; }
|
|
143
135
|
});
|
|
144
136
|
Object.defineProperty(exports, "SelectionRenderer", {
|
|
145
137
|
enumerable: true,
|
|
146
|
-
get: function () { return
|
|
138
|
+
get: function () { return chunkFUPKRQB2_cjs.SelectionRenderer; }
|
|
147
139
|
});
|
|
148
140
|
Object.defineProperty(exports, "SpatialIndex", {
|
|
149
141
|
enumerable: true,
|
|
150
|
-
get: function () { return
|
|
142
|
+
get: function () { return chunkFUPKRQB2_cjs.SpatialIndex; }
|
|
151
143
|
});
|
|
152
144
|
Object.defineProperty(exports, "SpatialIndexResource", {
|
|
153
145
|
enumerable: true,
|
|
154
|
-
get: function () { return
|
|
146
|
+
get: function () { return chunkFUPKRQB2_cjs.SpatialIndexResource; }
|
|
155
147
|
});
|
|
156
148
|
Object.defineProperty(exports, "WebGLWidgetLayer", {
|
|
157
149
|
enumerable: true,
|
|
158
|
-
get: function () { return
|
|
150
|
+
get: function () { return chunkFUPKRQB2_cjs.WebGLWidgetLayer; }
|
|
159
151
|
});
|
|
160
152
|
Object.defineProperty(exports, "WebGLWidgetSlot", {
|
|
161
153
|
enumerable: true,
|
|
162
|
-
get: function () { return
|
|
154
|
+
get: function () { return chunkFUPKRQB2_cjs.WebGLWidgetSlot; }
|
|
163
155
|
});
|
|
164
156
|
Object.defineProperty(exports, "WidgetSlot", {
|
|
165
157
|
enumerable: true,
|
|
166
|
-
get: function () { return
|
|
158
|
+
get: function () { return chunkFUPKRQB2_cjs.WidgetSlot; }
|
|
167
159
|
});
|
|
168
160
|
Object.defineProperty(exports, "computeSnapGuides", {
|
|
169
161
|
enumerable: true,
|
|
170
|
-
get: function () { return
|
|
162
|
+
get: function () { return chunkFUPKRQB2_cjs.computeSnapGuides; }
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(exports, "ContainerRefProvider", {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
get: function () { return chunk2KBYGER3_cjs.ContainerRefProvider; }
|
|
167
|
+
});
|
|
168
|
+
Object.defineProperty(exports, "EngineProvider", {
|
|
169
|
+
enumerable: true,
|
|
170
|
+
get: function () { return chunk2KBYGER3_cjs.EngineProvider; }
|
|
171
171
|
});
|
|
172
172
|
exports.deserializeWorld = deserializeWorld;
|
|
173
173
|
exports.serializeEntities = serializeEntities;
|