@jamesyong42/infinite-canvas 1.2.0 → 1.3.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 +65 -0
- package/dist/advanced.cjs +61 -24
- package/dist/advanced.cjs.map +1 -1
- package/dist/advanced.d.cts +180 -64
- package/dist/advanced.d.cts.map +1 -1
- package/dist/advanced.d.mts +180 -64
- package/dist/advanced.d.mts.map +1 -1
- package/dist/advanced.mjs +29 -12
- package/dist/advanced.mjs.map +1 -1
- package/dist/devtools.cjs +22 -22
- package/dist/devtools.cjs.map +1 -1
- package/dist/devtools.d.cts +2 -2
- package/dist/devtools.d.cts.map +1 -1
- package/dist/devtools.d.mts +2 -2
- package/dist/devtools.d.mts.map +1 -1
- package/dist/devtools.mjs +2 -2
- package/dist/devtools.mjs.map +1 -1
- package/dist/{hooks-BwY7rRHg.mjs → ecs-3kimUV5Z.mjs} +238 -74
- package/dist/ecs-3kimUV5Z.mjs.map +1 -0
- package/dist/{hooks-DHShH86C.cjs → ecs-B4QrqfvQ.cjs} +320 -108
- package/dist/ecs-B4QrqfvQ.cjs.map +1 -0
- package/dist/hooks-CtP02JNt.cjs +3762 -0
- package/dist/hooks-CtP02JNt.cjs.map +1 -0
- package/dist/hooks-gsQDDE56.mjs +3494 -0
- package/dist/hooks-gsQDDE56.mjs.map +1 -0
- package/dist/index-3GY7T8JM.d.mts +480 -0
- package/dist/index-3GY7T8JM.d.mts.map +1 -0
- package/dist/index-B7B1tRPl.d.cts +480 -0
- package/dist/index-B7B1tRPl.d.cts.map +1 -0
- package/dist/index-DSdbSQ_t.d.cts +1451 -0
- package/dist/index-DSdbSQ_t.d.cts.map +1 -0
- package/dist/index-Dj9odADH.d.mts +1451 -0
- package/dist/index-Dj9odADH.d.mts.map +1 -0
- package/dist/index.cjs +3865 -643
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +315 -138
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +315 -138
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3767 -571
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/SelectionRenderer-CR2PBQwx.d.cts +0 -105
- package/dist/SelectionRenderer-CR2PBQwx.d.cts.map +0 -1
- package/dist/SelectionRenderer-DlsBstAq.d.mts +0 -105
- package/dist/SelectionRenderer-DlsBstAq.d.mts.map +0 -1
- package/dist/WebGLWidgetLayer-BBMuwzHq.cjs +0 -3560
- package/dist/WebGLWidgetLayer-BBMuwzHq.cjs.map +0 -1
- package/dist/WebGLWidgetLayer-C3p1tnpm.mjs +0 -3375
- package/dist/WebGLWidgetLayer-C3p1tnpm.mjs.map +0 -1
- package/dist/engine-BfbvWXSk.d.mts +0 -982
- package/dist/engine-BfbvWXSk.d.mts.map +0 -1
- package/dist/engine-CCjuFMC-.d.cts +0 -982
- package/dist/engine-CCjuFMC-.d.cts.map +0 -1
- package/dist/hooks-BwY7rRHg.mjs.map +0 -1
- package/dist/hooks-DHShH86C.cjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineComponent, defineResource, defineTag } from "@jamesyong42/reactive-ecs";
|
|
2
2
|
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
|
3
|
-
//#region src/components.ts
|
|
3
|
+
//#region src/ecs/components.ts
|
|
4
4
|
/** Position, size, and rotation of an entity in local coordinates (world units). */
|
|
5
5
|
const Transform2D = defineComponent("Transform2D", {
|
|
6
6
|
x: 0,
|
|
@@ -9,19 +9,57 @@ const Transform2D = defineComponent("Transform2D", {
|
|
|
9
9
|
height: 100,
|
|
10
10
|
rotation: 0
|
|
11
11
|
});
|
|
12
|
-
/** Computed world-space bounding box. Read-only -- updated by the transform propagation system. */
|
|
13
|
-
const WorldBounds = defineComponent("WorldBounds", {
|
|
14
|
-
worldX: 0,
|
|
15
|
-
worldY: 0,
|
|
16
|
-
worldWidth: 0,
|
|
17
|
-
worldHeight: 0
|
|
18
|
-
});
|
|
19
12
|
/** Rendering and hit-test ordering. Higher values render on top. */
|
|
20
13
|
const ZIndex = defineComponent("ZIndex", { value: 0 });
|
|
21
|
-
/**
|
|
22
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Generic animated transition of `Transform2D.x` / `.y` over time.
|
|
16
|
+
* Runtime-only: the tween component is auto-removed on completion,
|
|
17
|
+
* and in-flight tweens are not serialized (the destination Transform2D
|
|
18
|
+
* is what survives a save/load).
|
|
19
|
+
*
|
|
20
|
+
* Introduced for RFC-004 Phase 4 fly-back but designed to be reusable
|
|
21
|
+
* for any future position-animation need (snap, drop-in, consume-pop).
|
|
22
|
+
* Starting a new tween on an entity that already has one overwrites.
|
|
23
|
+
*/
|
|
24
|
+
const TransformTween = defineComponent("TransformTween", {
|
|
25
|
+
fromX: 0,
|
|
26
|
+
fromY: 0,
|
|
27
|
+
toX: 0,
|
|
28
|
+
toY: 0,
|
|
29
|
+
/** `performance.now()`-ish timestamp captured at tween start. */
|
|
30
|
+
startMs: 0,
|
|
31
|
+
/** Total duration in ms. */
|
|
32
|
+
durationMs: 250,
|
|
33
|
+
easing: "ease-out",
|
|
34
|
+
/**
|
|
35
|
+
* Discriminator so downstream systems can react per kind
|
|
36
|
+
* (e.g. `'flyback'`, `'snap'`, `'spawn'`). The tween system
|
|
37
|
+
* itself is kind-agnostic.
|
|
38
|
+
*/
|
|
39
|
+
kind: "generic"
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* Container-hierarchy parenthood: the entity lives inside another
|
|
43
|
+
* entity's sub-canvas. Read by `navigationFilterSystem` to filter the
|
|
44
|
+
* active widget set by the current navigation frame.
|
|
45
|
+
*
|
|
46
|
+
* Container children have their own `Transform2D` in the container's
|
|
47
|
+
* local coord space — **no coord accumulation** through this reference.
|
|
48
|
+
* Replaces the old `Parent` component's container-hierarchy usage
|
|
49
|
+
* (RFC-005).
|
|
50
|
+
*/
|
|
51
|
+
const ParentFrame = defineComponent("ParentFrame", { id: 0 });
|
|
23
52
|
/** Child entity IDs. Used for nested containers and handle sync. */
|
|
24
53
|
const Children = defineComponent("Children", { ids: [] });
|
|
54
|
+
/**
|
|
55
|
+
* Ordered list of the entities a container owns (RFC-004 § Phase 5).
|
|
56
|
+
* Redundant with `ParentFrame` (the inverse relation) but materialised
|
|
57
|
+
* so UI / compositor paths can cheaply read "give me this container's
|
|
58
|
+
* children" without a reverse-index scan. `applyMutation` /
|
|
59
|
+
* `revertMutation` keep the two in sync. Serialized; IDs are remapped
|
|
60
|
+
* alongside `ParentFrame` on load.
|
|
61
|
+
*/
|
|
62
|
+
const ContainerChildren = defineComponent("ContainerChildren", { ids: [] });
|
|
25
63
|
/** Marks an entity as a renderable widget with a type identifier and rendering surface. */
|
|
26
64
|
const Widget = defineComponent("Widget", {
|
|
27
65
|
surface: "dom",
|
|
@@ -36,38 +74,100 @@ const WidgetBreakpoint = defineComponent("WidgetBreakpoint", {
|
|
|
36
74
|
screenHeight: 0
|
|
37
75
|
});
|
|
38
76
|
/**
|
|
39
|
-
* Marks an entity as an iOS-style card with a fixed preset size
|
|
40
|
-
*
|
|
41
|
-
*
|
|
77
|
+
* Marks an entity as an iOS-style card with a fixed preset size, AND
|
|
78
|
+
* opts the entity into the full card-shaped behavior bundle:
|
|
79
|
+
*
|
|
80
|
+
* - DOM `<CardChrome>` slot (rounded body, hairline ring, shadow,
|
|
81
|
+
* CSS lift transition on Dragging)
|
|
82
|
+
* - drag-promote to the 'overlay' layer for DOM cards (so dragged
|
|
83
|
+
* DOM cards visually pop above the R3F canvas)
|
|
84
|
+
* - composition discard rect for R3F cards (so other R3F widgets
|
|
85
|
+
* are clipped out of the dragged card's screen rect — defends
|
|
86
|
+
* the chrome from being painted over)
|
|
87
|
+
* - drop-to-consume contracts (`accepts` / `provides`) — RFC-004.
|
|
88
|
+
*
|
|
89
|
+
* Widgets *without* `Card` get none of this — they render bare (no
|
|
90
|
+
* chrome, no lift transition), and on drag they only get the
|
|
91
|
+
* compositor's renderOrder bump if they're R3F (so they stack on
|
|
92
|
+
* top of other R3F widgets they overlap).
|
|
93
|
+
*
|
|
94
|
+
* The `cardSystem` reconciles `Transform2D.width/height` from the
|
|
95
|
+
* preset each tick, so cards cannot be resized freely — change
|
|
96
|
+
* `preset` instead.
|
|
97
|
+
*/
|
|
98
|
+
const Card = defineComponent("Card", {
|
|
99
|
+
preset: "small",
|
|
100
|
+
/**
|
|
101
|
+
* CSS background for the chrome's surface (any valid background value;
|
|
102
|
+
* defaults to the dark iOS card colour). Picked up by the
|
|
103
|
+
* `<CardChrome>` component rendered for this entity.
|
|
104
|
+
*/
|
|
105
|
+
background: "#1C1C1E",
|
|
106
|
+
/**
|
|
107
|
+
* Drop-to-consume contract — what this card accepts as a *parent*.
|
|
108
|
+
* An incoming dragged card's `provides` must intersect this list
|
|
109
|
+
* for the consume mechanic to fire (RFC-004 § Phase 3). Empty
|
|
110
|
+
* array = card never consumes anything.
|
|
111
|
+
*/
|
|
112
|
+
accepts: [],
|
|
113
|
+
/**
|
|
114
|
+
* Drop-to-consume contract — what this card offers when dropped
|
|
115
|
+
* as a *child*. Empty array = card is never consumed by anything.
|
|
116
|
+
*/
|
|
117
|
+
provides: []
|
|
118
|
+
});
|
|
119
|
+
/**
|
|
120
|
+
* Transient — set by the card-overlap pass on every card whose AABB
|
|
121
|
+
* intersects the dragged card's AABB during drag. Layer 1 of the
|
|
122
|
+
* two-layer overlap visual state (RFC-004 § Phase 3). Cleared on drag
|
|
123
|
+
* end. Runtime-only — not serialized.
|
|
124
|
+
*/
|
|
125
|
+
const OverlapCandidate = defineTag("OverlapCandidate");
|
|
126
|
+
/**
|
|
127
|
+
* Transient — set on the single primary candidate (closest centre
|
|
128
|
+
* distance) iff its `accepts` contract intersects the dragged card's
|
|
129
|
+
* `provides` contract AND the optional `canAccept` gate passes.
|
|
130
|
+
* Layer 2 of the two-layer overlap visual state. Cleared on drag end
|
|
131
|
+
* or when match becomes false. Runtime-only — not serialized.
|
|
132
|
+
*/
|
|
133
|
+
const OverlapTarget = defineTag("OverlapTarget");
|
|
134
|
+
/**
|
|
135
|
+
* Per-candidate "hot point" for the position-dependent radial glow
|
|
136
|
+
* (RFC-004 § Phase 3). `x` and `y` are normalized [0..1] local coords
|
|
137
|
+
* within the overlapped card, pointing at the intersection centroid.
|
|
138
|
+
* `strength` ramps between 0 and 1 for the fade-in / fade-out.
|
|
139
|
+
* Runtime-only — not serialized.
|
|
42
140
|
*/
|
|
43
|
-
const
|
|
141
|
+
const CardOverlapHotPoint = defineComponent("CardOverlapHotPoint", {
|
|
142
|
+
x: .5,
|
|
143
|
+
y: .5,
|
|
144
|
+
strength: 0
|
|
145
|
+
});
|
|
44
146
|
/** Marks an entity as an enterable container (double-click/double-tap to enter). */
|
|
45
147
|
const Container = defineComponent("Container", { enterable: true });
|
|
46
148
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
149
|
+
* Per-container persistent camera state. When the user navigates out of
|
|
150
|
+
* a container, their current pan/zoom is snapshotted here; when they
|
|
151
|
+
* navigate back in, it's restored. Serialized — containers remember
|
|
152
|
+
* their view across save/load (RFC-004 § Phase 0c).
|
|
51
153
|
*/
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
height: 0
|
|
154
|
+
const ContainerCamera = defineComponent("ContainerCamera", {
|
|
155
|
+
x: 0,
|
|
156
|
+
y: 0,
|
|
157
|
+
zoom: 1
|
|
57
158
|
});
|
|
58
159
|
/**
|
|
59
160
|
* Declares what happens when this entity is hit, plus its hit-test priority.
|
|
60
|
-
* Canonical layers: 0=canvas, 5=widget body,
|
|
161
|
+
* Canonical layers: 0=canvas, 5=widget body, 20=reserved.
|
|
162
|
+
*
|
|
163
|
+
* Resize corner/edge roles are emitted inline by `interaction.ts` from the
|
|
164
|
+
* selected `Resizable` widget's Transform2D; they are NOT stored as
|
|
165
|
+
* per-handle entities (RFC-005).
|
|
61
166
|
*/
|
|
62
167
|
const InteractionRole = defineComponent("InteractionRole", {
|
|
63
168
|
layer: 0,
|
|
64
169
|
role: { type: "canvas" }
|
|
65
170
|
});
|
|
66
|
-
/**
|
|
67
|
-
* Component on the parent entity listing the EntityIds of its spawned handle children.
|
|
68
|
-
* Enables O(1) cascade destroy without a reverse-index scan of Parent components.
|
|
69
|
-
*/
|
|
70
|
-
const HandleSet = defineComponent("HandleSet", { ids: [] });
|
|
71
171
|
/** Declares the cursor this entity requests when hovered and when active. */
|
|
72
172
|
const CursorHint = defineComponent("CursorHint", {
|
|
73
173
|
hover: "default",
|
|
@@ -79,6 +179,32 @@ const Selectable = defineTag("Selectable");
|
|
|
79
179
|
const Draggable = defineTag("Draggable");
|
|
80
180
|
/** Marks an entity as resizable via edge/corner handles. */
|
|
81
181
|
const Resizable = defineTag("Resizable");
|
|
182
|
+
/**
|
|
183
|
+
* Marks an entity that, when dragged, runs alignment-snap math against
|
|
184
|
+
* the set of `SnapTarget` entities. Without this tag, dragging proceeds
|
|
185
|
+
* with raw pointer deltas (no snapping). Independent of `SnapTarget`:
|
|
186
|
+
* an entity can be a source without being a target (rare) or a target
|
|
187
|
+
* without being a source (e.g. cards — they participate as references
|
|
188
|
+
* for other widgets but never snap themselves).
|
|
189
|
+
*
|
|
190
|
+
* Multi-select drag: only the first selected entity's bounds drive the
|
|
191
|
+
* snap calculation; followers receive the same correction delta. Tag a
|
|
192
|
+
* follower-only entity with `SnapSource` if you also want it to lead a
|
|
193
|
+
* single-entity drag, but be aware its geometry is ignored when it is
|
|
194
|
+
* being dragged as part of a multi-select group.
|
|
195
|
+
*/
|
|
196
|
+
const SnapSource = defineTag("SnapSource");
|
|
197
|
+
/**
|
|
198
|
+
* Marks an entity whose bounds are usable as a snap reference when
|
|
199
|
+
* another `SnapSource` entity is being dragged. References are further
|
|
200
|
+
* filtered by `Active`, so only entities in the current navigation
|
|
201
|
+
* frame can pull a drag — a `SnapTarget` inside a closed container
|
|
202
|
+
* does not leak into a root-level drag. Visibility of the resulting
|
|
203
|
+
* guide lines is controlled separately by the engine's
|
|
204
|
+
* `snap.guidesVisible` config — this tag only governs participation
|
|
205
|
+
* in the math.
|
|
206
|
+
*/
|
|
207
|
+
const SnapTarget = defineTag("SnapTarget");
|
|
82
208
|
/** Prevents an entity from being moved or resized. */
|
|
83
209
|
const Locked = defineTag("Locked");
|
|
84
210
|
/** Indicates the entity is currently selected. */
|
|
@@ -100,18 +226,41 @@ const SelectionFrame = defineTag("SelectionFrame");
|
|
|
100
226
|
const Active = defineTag("Active");
|
|
101
227
|
/** Indicates the entity is within the visible viewport. Set by the cull system. */
|
|
102
228
|
const Visible = defineTag("Visible");
|
|
229
|
+
/**
|
|
230
|
+
* Indicates the entity is `Active` but **outside** the visible viewport
|
|
231
|
+
* (+overscan). The complement of `Visible` for Active entities — the cull
|
|
232
|
+
* system maintains the invariant that every Active entity carries exactly
|
|
233
|
+
* one of `Visible` or `Culled`.
|
|
234
|
+
*
|
|
235
|
+
* Render layers consume this to keep state cached without rendering: DOM
|
|
236
|
+
* widgets may stay mounted-but-hidden for fast re-reveal, and the R3F
|
|
237
|
+
* compositor (RFC-002) holds widget render targets in its Cold pool.
|
|
238
|
+
*/
|
|
239
|
+
const Culled = defineTag("Culled");
|
|
240
|
+
const Layer = defineComponent("Layer", { name: "base" });
|
|
241
|
+
const PreDragLayer = defineComponent("PreDragLayer", { name: "base" });
|
|
103
242
|
//#endregion
|
|
104
|
-
//#region src/resources.ts
|
|
243
|
+
//#region src/ecs/resources.ts
|
|
105
244
|
/**
|
|
106
245
|
* Output sink for the cursor system. Written by cursorSystem each tick;
|
|
107
246
|
* read by the RAF loop to apply style.cursor on the root container div.
|
|
108
247
|
*/
|
|
109
248
|
const CursorResource = defineResource("Cursor", { cursor: "default" });
|
|
110
|
-
/**
|
|
249
|
+
/**
|
|
250
|
+
* Camera state: world-space position (x, y) and zoom level.
|
|
251
|
+
*
|
|
252
|
+
* `gesturing` is true while the user is actively manipulating the camera
|
|
253
|
+
* (continuous wheel zoom, pinch, two-finger pan). Set/cleared by gesture
|
|
254
|
+
* handlers via {@link LayoutEngine.setGesturing}; render layers can use it
|
|
255
|
+
* to defer expensive work (e.g. the R3F compositor skips zoom-band
|
|
256
|
+
* repaints while gesturing so a continuous pinch doesn't trigger a
|
|
257
|
+
* repaint storm across every visible widget).
|
|
258
|
+
*/
|
|
111
259
|
const CameraResource = defineResource("Camera", {
|
|
112
260
|
x: 0,
|
|
113
261
|
y: 0,
|
|
114
|
-
zoom: 1
|
|
262
|
+
zoom: 1,
|
|
263
|
+
gesturing: false
|
|
115
264
|
});
|
|
116
265
|
/** Viewport dimensions in CSS pixels and device pixel ratio. Updated on resize. */
|
|
117
266
|
const ViewportResource = defineResource("Viewport", {
|
|
@@ -131,57 +280,77 @@ const BreakpointConfigResource = defineResource("BreakpointConfig", {
|
|
|
131
280
|
normal: 500,
|
|
132
281
|
expanded: 1200
|
|
133
282
|
});
|
|
134
|
-
/**
|
|
283
|
+
/**
|
|
284
|
+
* Navigation stack for hierarchical container traversal. Always has at
|
|
285
|
+
* least one frame (the root). The last element is the current frame;
|
|
286
|
+
* `containerId === null` means the user is at the root canvas.
|
|
287
|
+
*
|
|
288
|
+
* Runtime-only view state — deliberately not serialized, so reloading a
|
|
289
|
+
* saved canvas always drops the user at the root frame (RFC-004 § Phase 0c).
|
|
290
|
+
*/
|
|
135
291
|
const NavigationStackResource = defineResource("NavigationStack", {
|
|
136
|
-
frames: [{
|
|
137
|
-
containerId: null,
|
|
138
|
-
camera: {
|
|
139
|
-
x: 0,
|
|
140
|
-
y: 0,
|
|
141
|
-
zoom: 1
|
|
142
|
-
}
|
|
143
|
-
}],
|
|
292
|
+
frames: [{ containerId: null }],
|
|
144
293
|
changed: false
|
|
145
294
|
});
|
|
146
295
|
/**
|
|
296
|
+
* Camera state for the root canvas. Persisted (serialized) so the root
|
|
297
|
+
* view returns to its previous pan/zoom across navigation push/pop and
|
|
298
|
+
* across save/load. Container frames use the `ContainerCamera` component
|
|
299
|
+
* on the container entity instead (RFC-004 § Phase 0c).
|
|
300
|
+
*/
|
|
301
|
+
const RootCameraResource = defineResource("RootCamera", {
|
|
302
|
+
x: 0,
|
|
303
|
+
y: 0,
|
|
304
|
+
zoom: 1
|
|
305
|
+
});
|
|
306
|
+
/**
|
|
307
|
+
* Built-in card preset sizes (iOS widget conventions — 155×155 tile).
|
|
308
|
+
* Single source of truth consumed by:
|
|
309
|
+
* - {@link CardPresetsResource} — runtime lookup by the cardSystem.
|
|
310
|
+
* - `createCardWidget` / `createGeometryCardWidget` — widget-registration-
|
|
311
|
+
* time `defaultSize`, which must be known before the engine is built.
|
|
312
|
+
*/
|
|
313
|
+
const DEFAULT_CARD_PRESET_SIZES = {
|
|
314
|
+
small: {
|
|
315
|
+
width: 155,
|
|
316
|
+
height: 155
|
|
317
|
+
},
|
|
318
|
+
medium: {
|
|
319
|
+
width: 329,
|
|
320
|
+
height: 155
|
|
321
|
+
},
|
|
322
|
+
large: {
|
|
323
|
+
width: 329,
|
|
324
|
+
height: 345
|
|
325
|
+
},
|
|
326
|
+
xl: {
|
|
327
|
+
width: 329,
|
|
328
|
+
height: 535
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
147
332
|
* iOS-style card preset size map. Lookup happens by `Card.preset`; the
|
|
148
333
|
* `cardSystem` stamps `Transform2D.width/height` from the resolved size.
|
|
149
334
|
*
|
|
150
|
-
* Defaults mirror iOS widget conventions — 155×155 tile + 19px gap.
|
|
151
335
|
* Override at `createLayoutEngine({ cardPresets })` for tablet-scale or
|
|
152
336
|
* custom design systems.
|
|
153
337
|
*/
|
|
154
338
|
const CardPresetsResource = defineResource("CardPresets", {
|
|
155
|
-
presets: {
|
|
156
|
-
small: {
|
|
157
|
-
width: 155,
|
|
158
|
-
height: 155
|
|
159
|
-
},
|
|
160
|
-
medium: {
|
|
161
|
-
width: 329,
|
|
162
|
-
height: 155
|
|
163
|
-
},
|
|
164
|
-
large: {
|
|
165
|
-
width: 329,
|
|
166
|
-
height: 345
|
|
167
|
-
},
|
|
168
|
-
xl: {
|
|
169
|
-
width: 329,
|
|
170
|
-
height: 535
|
|
171
|
-
}
|
|
172
|
-
},
|
|
339
|
+
presets: { ...DEFAULT_CARD_PRESET_SIZES },
|
|
173
340
|
/** Gap between adjacent tiles (future tile-snap system reads this). */
|
|
174
341
|
gap: 19
|
|
175
342
|
});
|
|
343
|
+
/** ECS resource holding the SpatialIndex instance for viewport culling and hit testing. */
|
|
344
|
+
const SpatialIndexResource = defineResource("SpatialIndex", { instance: null });
|
|
345
|
+
const LayerOrderResource = defineResource("LayerOrder", { layers: [
|
|
346
|
+
"background",
|
|
347
|
+
"base",
|
|
348
|
+
"overlay"
|
|
349
|
+
] });
|
|
176
350
|
//#endregion
|
|
177
|
-
//#region src/react/context.ts
|
|
351
|
+
//#region src/react/context/engine-context.ts
|
|
178
352
|
const EngineContext = createContext(null);
|
|
179
353
|
const EngineProvider = EngineContext.Provider;
|
|
180
|
-
const ContainerRefContext = createContext(null);
|
|
181
|
-
const ContainerRefProvider = ContainerRefContext.Provider;
|
|
182
|
-
function useContainerRef() {
|
|
183
|
-
return useContext(ContainerRefContext);
|
|
184
|
-
}
|
|
185
354
|
/**
|
|
186
355
|
* Returns the LayoutEngine instance from the nearest InfiniteCanvas context.
|
|
187
356
|
* Throws if used outside an InfiniteCanvas provider.
|
|
@@ -191,13 +360,8 @@ function useLayoutEngine() {
|
|
|
191
360
|
if (!engine) throw new Error("useLayoutEngine must be used within an <InfiniteCanvas>");
|
|
192
361
|
return engine;
|
|
193
362
|
}
|
|
194
|
-
const WidgetResolverContext = createContext(null);
|
|
195
|
-
const WidgetResolverProvider = WidgetResolverContext.Provider;
|
|
196
|
-
function useWidgetResolver() {
|
|
197
|
-
return useContext(WidgetResolverContext);
|
|
198
|
-
}
|
|
199
363
|
//#endregion
|
|
200
|
-
//#region src/react/hooks.ts
|
|
364
|
+
//#region src/react/hooks/ecs.ts
|
|
201
365
|
function shallowEqual(a, b) {
|
|
202
366
|
const keysA = Object.keys(a);
|
|
203
367
|
const keysB = Object.keys(b);
|
|
@@ -420,6 +584,6 @@ function useRegisteredTags() {
|
|
|
420
584
|
return types;
|
|
421
585
|
}
|
|
422
586
|
//#endregion
|
|
423
|
-
export {
|
|
587
|
+
export { WidgetBreakpoint as $, ContainerCamera as A, OverlapTarget as B, ViewportResource as C, CardOverlapHotPoint as D, Card as E, Dragging as F, Selected as G, PreDragLayer as H, InteractionRole as I, SnapTarget as J, SelectionFrame as K, Layer as L, Culled as M, CursorHint as N, Children as O, Draggable as P, Widget as Q, Locked as R, SpatialIndexResource as S, Active as T, Resizable as U, ParentFrame as V, Selectable as W, TransformTween as X, Transform2D as Y, Visible as Z, CursorResource as _, useEntityTags as a, NavigationStackResource as b, useRegisteredTags as c, useTaggedEntities as d, WidgetData as et, EngineProvider as f, CardPresetsResource as g, CameraResource as h, useEntityComponents as i, ContainerChildren as j, Container as k, useResource as l, BreakpointConfigResource as m, useCamera as n, useQuery as o, useLayoutEngine as p, SnapSource as q, useComponent as r, useRegisteredComponents as s, useAllEntities as t, ZIndex as tt, useTag as u, DEFAULT_CARD_PRESET_SIZES as v, ZoomConfigResource as w, RootCameraResource as x, LayerOrderResource as y, OverlapCandidate as z };
|
|
424
588
|
|
|
425
|
-
//# sourceMappingURL=
|
|
589
|
+
//# sourceMappingURL=ecs-3kimUV5Z.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ecs-3kimUV5Z.mjs","names":[],"sources":["../src/ecs/components.ts","../src/ecs/resources.ts","../src/react/context/engine-context.ts","../src/react/hooks/ecs.ts"],"sourcesContent":["import type { EntityId } from '@jamesyong42/reactive-ecs';\nimport { defineComponent, defineTag } from '@jamesyong42/reactive-ecs';\n\n// === Spatial ===\n\n/** Position, size, and rotation of an entity in local coordinates (world units). */\nexport const Transform2D = defineComponent('Transform2D', {\n\tx: 0,\n\ty: 0,\n\twidth: 100,\n\theight: 100,\n\trotation: 0,\n});\n\n/** Rendering and hit-test ordering. Higher values render on top. */\nexport const ZIndex = defineComponent('ZIndex', { value: 0 });\n\n/** Easing curves supported by `transformTweenSystem` (RFC-004 § Phase 2). */\nexport type TweenEasing = 'linear' | 'ease-out' | 'ease-in-out' | 'spring';\n\n/**\n * Generic animated transition of `Transform2D.x` / `.y` over time.\n * Runtime-only: the tween component is auto-removed on completion,\n * and in-flight tweens are not serialized (the destination Transform2D\n * is what survives a save/load).\n *\n * Introduced for RFC-004 Phase 4 fly-back but designed to be reusable\n * for any future position-animation need (snap, drop-in, consume-pop).\n * Starting a new tween on an entity that already has one overwrites.\n */\nexport const TransformTween = defineComponent('TransformTween', {\n\tfromX: 0,\n\tfromY: 0,\n\ttoX: 0,\n\ttoY: 0,\n\t/** `performance.now()`-ish timestamp captured at tween start. */\n\tstartMs: 0,\n\t/** Total duration in ms. */\n\tdurationMs: 250,\n\teasing: 'ease-out' as TweenEasing,\n\t/**\n\t * Discriminator so downstream systems can react per kind\n\t * (e.g. `'flyback'`, `'snap'`, `'spawn'`). The tween system\n\t * itself is kind-agnostic.\n\t */\n\tkind: 'generic',\n});\n\n// === Hierarchy ===\n\n/**\n * Container-hierarchy parenthood: the entity lives inside another\n * entity's sub-canvas. Read by `navigationFilterSystem` to filter the\n * active widget set by the current navigation frame.\n *\n * Container children have their own `Transform2D` in the container's\n * local coord space — **no coord accumulation** through this reference.\n * Replaces the old `Parent` component's container-hierarchy usage\n * (RFC-005).\n */\nexport const ParentFrame = defineComponent('ParentFrame', { id: 0 as EntityId });\n\n/** Child entity IDs. Used for nested containers and handle sync. */\nexport const Children = defineComponent('Children', { ids: [] as EntityId[] });\n\n/**\n * Ordered list of the entities a container owns (RFC-004 § Phase 5).\n * Redundant with `ParentFrame` (the inverse relation) but materialised\n * so UI / compositor paths can cheaply read \"give me this container's\n * children\" without a reverse-index scan. `applyMutation` /\n * `revertMutation` keep the two in sync. Serialized; IDs are remapped\n * alongside `ParentFrame` on load.\n */\nexport const ContainerChildren = defineComponent('ContainerChildren', {\n\tids: [] as EntityId[],\n});\n\n// === Widget ===\n\n/** Marks an entity as a renderable widget with a type identifier and rendering surface. */\nexport const Widget = defineComponent('Widget', {\n\tsurface: 'dom' as 'dom' | 'webgl' | 'webview',\n\ttype: '' as string,\n});\n\n/** Arbitrary application data attached to a widget entity. Access via useWidgetData(). */\nexport const WidgetData = defineComponent('WidgetData', {\n\tdata: {} as Record<string, unknown>,\n});\n\n/** Computed responsive breakpoint based on screen-space size. Read-only. */\nexport const WidgetBreakpoint = defineComponent('WidgetBreakpoint', {\n\tcurrent: 'normal' as 'micro' | 'compact' | 'normal' | 'expanded' | 'detailed',\n\tscreenWidth: 0,\n\tscreenHeight: 0,\n});\n\n// === Card ===\n\n/** iOS-style card size presets. Actual dimensions live in CardPresetsResource. */\nexport type CardPreset = 'small' | 'medium' | 'large' | 'xl';\n\n/**\n * Marks an entity as an iOS-style card with a fixed preset size, AND\n * opts the entity into the full card-shaped behavior bundle:\n *\n * - DOM `<CardChrome>` slot (rounded body, hairline ring, shadow,\n * CSS lift transition on Dragging)\n * - drag-promote to the 'overlay' layer for DOM cards (so dragged\n * DOM cards visually pop above the R3F canvas)\n * - composition discard rect for R3F cards (so other R3F widgets\n * are clipped out of the dragged card's screen rect — defends\n * the chrome from being painted over)\n * - drop-to-consume contracts (`accepts` / `provides`) — RFC-004.\n *\n * Widgets *without* `Card` get none of this — they render bare (no\n * chrome, no lift transition), and on drag they only get the\n * compositor's renderOrder bump if they're R3F (so they stack on\n * top of other R3F widgets they overlap).\n *\n * The `cardSystem` reconciles `Transform2D.width/height` from the\n * preset each tick, so cards cannot be resized freely — change\n * `preset` instead.\n */\nexport const Card = defineComponent('Card', {\n\tpreset: 'small' as CardPreset,\n\t/**\n\t * CSS background for the chrome's surface (any valid background value;\n\t * defaults to the dark iOS card colour). Picked up by the\n\t * `<CardChrome>` component rendered for this entity.\n\t */\n\tbackground: '#1C1C1E',\n\t/**\n\t * Drop-to-consume contract — what this card accepts as a *parent*.\n\t * An incoming dragged card's `provides` must intersect this list\n\t * for the consume mechanic to fire (RFC-004 § Phase 3). Empty\n\t * array = card never consumes anything.\n\t */\n\taccepts: [] as readonly string[],\n\t/**\n\t * Drop-to-consume contract — what this card offers when dropped\n\t * as a *child*. Empty array = card is never consumed by anything.\n\t */\n\tprovides: [] as readonly string[],\n});\n\n/**\n * Transient — set by the card-overlap pass on every card whose AABB\n * intersects the dragged card's AABB during drag. Layer 1 of the\n * two-layer overlap visual state (RFC-004 § Phase 3). Cleared on drag\n * end. Runtime-only — not serialized.\n */\nexport const OverlapCandidate = defineTag('OverlapCandidate');\n\n/**\n * Transient — set on the single primary candidate (closest centre\n * distance) iff its `accepts` contract intersects the dragged card's\n * `provides` contract AND the optional `canAccept` gate passes.\n * Layer 2 of the two-layer overlap visual state. Cleared on drag end\n * or when match becomes false. Runtime-only — not serialized.\n */\nexport const OverlapTarget = defineTag('OverlapTarget');\n\n/**\n * Per-candidate \"hot point\" for the position-dependent radial glow\n * (RFC-004 § Phase 3). `x` and `y` are normalized [0..1] local coords\n * within the overlapped card, pointing at the intersection centroid.\n * `strength` ramps between 0 and 1 for the fade-in / fade-out.\n * Runtime-only — not serialized.\n */\nexport const CardOverlapHotPoint = defineComponent('CardOverlapHotPoint', {\n\tx: 0.5,\n\ty: 0.5,\n\tstrength: 0,\n});\n\n// === Container ===\n\n/** Marks an entity as an enterable container (double-click/double-tap to enter). */\nexport const Container = defineComponent('Container', { enterable: true });\n\n/**\n * Per-container persistent camera state. When the user navigates out of\n * a container, their current pan/zoom is snapshotted here; when they\n * navigate back in, it's restored. Serialized — containers remember\n * their view across save/load (RFC-004 § Phase 0c).\n */\nexport const ContainerCamera = defineComponent('ContainerCamera', {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n});\n\n// === Interaction ===\n\n/** Resize handle positions — 4 edges + 4 corners. */\nexport type ResizeHandlePos = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';\n\n/** Discriminated union of interaction roles an entity can fulfil. */\nexport type InteractionRoleType =\n\t| { type: 'drag' }\n\t| { type: 'select' }\n\t| { type: 'resize'; handle: ResizeHandlePos }\n\t| { type: 'rotate' }\n\t| { type: 'connect' }\n\t| { type: 'canvas' };\n\nexport type InteractionRoleData = {\n\t/** Hit-test priority — higher wins when multiple entities contain the point. */\n\tlayer: number;\n\t/** Discriminated role + role-specific data. */\n\trole: InteractionRoleType;\n};\n\n/**\n * Declares what happens when this entity is hit, plus its hit-test priority.\n * Canonical layers: 0=canvas, 5=widget body, 20=reserved.\n *\n * Resize corner/edge roles are emitted inline by `interaction.ts` from the\n * selected `Resizable` widget's Transform2D; they are NOT stored as\n * per-handle entities (RFC-005).\n */\nexport const InteractionRole = defineComponent<InteractionRoleData>('InteractionRole', {\n\tlayer: 0,\n\trole: { type: 'canvas' },\n});\n\n/** CSS cursor values the canvas may request. */\nexport type CSSCursor =\n\t| 'default'\n\t| 'grab'\n\t| 'grabbing'\n\t| 'crosshair'\n\t| 'n-resize'\n\t| 's-resize'\n\t| 'e-resize'\n\t| 'w-resize'\n\t| 'ne-resize'\n\t| 'nw-resize'\n\t| 'se-resize'\n\t| 'sw-resize';\n\nexport type CursorHintData = {\n\t/** Cursor when this entity is hovered in idle state. */\n\thover: CSSCursor;\n\t/** Cursor while this entity is being dragged/resized. */\n\tactive: CSSCursor;\n};\n\n/** Declares the cursor this entity requests when hovered and when active. */\nexport const CursorHint = defineComponent<CursorHintData>('CursorHint', {\n\thover: 'default',\n\tactive: 'default',\n});\n\n// === Tags ===\n\n/** Marks an entity as selectable by click or marquee. */\nexport const Selectable = defineTag('Selectable');\n/** Marks an entity as draggable via pointer interaction. */\nexport const Draggable = defineTag('Draggable');\n/** Marks an entity as resizable via edge/corner handles. */\nexport const Resizable = defineTag('Resizable');\n/**\n * Marks an entity that, when dragged, runs alignment-snap math against\n * the set of `SnapTarget` entities. Without this tag, dragging proceeds\n * with raw pointer deltas (no snapping). Independent of `SnapTarget`:\n * an entity can be a source without being a target (rare) or a target\n * without being a source (e.g. cards — they participate as references\n * for other widgets but never snap themselves).\n *\n * Multi-select drag: only the first selected entity's bounds drive the\n * snap calculation; followers receive the same correction delta. Tag a\n * follower-only entity with `SnapSource` if you also want it to lead a\n * single-entity drag, but be aware its geometry is ignored when it is\n * being dragged as part of a multi-select group.\n */\nexport const SnapSource = defineTag('SnapSource');\n/**\n * Marks an entity whose bounds are usable as a snap reference when\n * another `SnapSource` entity is being dragged. References are further\n * filtered by `Active`, so only entities in the current navigation\n * frame can pull a drag — a `SnapTarget` inside a closed container\n * does not leak into a root-level drag. Visibility of the resulting\n * guide lines is controlled separately by the engine's\n * `snap.guidesVisible` config — this tag only governs participation\n * in the math.\n */\nexport const SnapTarget = defineTag('SnapTarget');\n/** Prevents an entity from being moved or resized. */\nexport const Locked = defineTag('Locked');\n/** Indicates the entity is currently selected. */\nexport const Selected = defineTag('Selected');\n/**\n * Indicates the entity is currently being dragged by the user.\n * Added after the drag dead-zone is crossed; removed on pointer up/cancel.\n * Renderers read this to apply transient drag affordances (e.g. scale/shadow lift).\n */\nexport const Dragging = defineTag('Dragging');\n/**\n * Entities with this tag get the engine-drawn selection + hover outline frame.\n * Granted automatically to Selectable entities unless explicitly disabled via\n * `Archetype.interactive.selectionFrame: false`. Widgets that render their own\n * selected/hover chrome (e.g. iOS-style cards) opt out.\n */\nexport const SelectionFrame = defineTag('SelectionFrame');\n/** Indicates the entity is currently being interacted with (drag, resize). */\nexport const Active = defineTag('Active');\n/** Indicates the entity is within the visible viewport. Set by the cull system. */\nexport const Visible = defineTag('Visible');\n/**\n * Indicates the entity is `Active` but **outside** the visible viewport\n * (+overscan). The complement of `Visible` for Active entities — the cull\n * system maintains the invariant that every Active entity carries exactly\n * one of `Visible` or `Culled`.\n *\n * Render layers consume this to keep state cached without rendering: DOM\n * widgets may stay mounted-but-hidden for fast re-reveal, and the R3F\n * compositor (RFC-002) holds widget render targets in its Cold pool.\n */\nexport const Culled = defineTag('Culled');\n\n// === Layer system (RFC-003) ===\n\n/**\n * Named DOM stacking layer a widget renders into. Three layers are\n * rendered out of the box by `<InfiniteCanvas>`:\n *\n * `'background'` — DOM widgets behind everything user-content.\n * `'base'` — default; DOM widgets and R3F card chrome.\n * `'overlay'` — DOM widgets and R3F chrome promoted above the R3F\n * canvas (e.g. dragged widget, future tooltips).\n *\n * Per-widget `ZIndex` continues to control intra-layer ordering;\n * `Layer.name` picks which layer container the widget mounts into.\n *\n * R3F widgets always render through the R3F canvas regardless of layer\n * — `Layer.name` only controls where their CSS chrome / interaction\n * surface mounts.\n */\nexport type LayerName = 'background' | 'base' | 'overlay';\n\nexport type LayerData = {\n\tname: LayerName;\n};\n\nexport const Layer = defineComponent<LayerData>('Layer', { name: 'base' });\n\n/**\n * Sidecar component on a widget that has been promoted to a higher\n * layer by `dragPromoteSystem`; stores the widget's pre-drag\n * `Layer.name` so it can be restored when `Dragging` is removed.\n *\n * Internal — not part of the public API. Serialization-skipped because\n * it only carries transient interaction state.\n */\nexport type PreDragLayerData = {\n\tname: LayerName;\n};\n\nexport const PreDragLayer = defineComponent<PreDragLayerData>('PreDragLayer', {\n\tname: 'base',\n});\n","import type { EntityId } from '@jamesyong42/reactive-ecs';\nimport { defineResource } from '@jamesyong42/reactive-ecs';\nimport type { CardPreset, CSSCursor } from './components.js';\nimport type { SpatialIndex } from './spatial/SpatialIndex.js';\n\n/**\n * A single frame in the navigation stack. `containerId === null` is the\n * root canvas; any other value is the entity id of the container whose\n * sub-canvas the user is currently inside. Camera state lives on a\n * `ContainerCamera` component per container (or `RootCameraResource`\n * for the root frame), not in the stack itself — see RFC-004 § Phase 0c.\n */\nexport interface NavigationFrame {\n\tcontainerId: EntityId | null;\n}\n\n/** Data shape for the CursorResource. */\nexport type CursorResourceData = {\n\tcursor: CSSCursor;\n};\n\n/**\n * Output sink for the cursor system. Written by cursorSystem each tick;\n * read by the RAF loop to apply style.cursor on the root container div.\n */\nexport const CursorResource = defineResource<CursorResourceData>('Cursor', {\n\tcursor: 'default',\n});\n\n/**\n * Camera state: world-space position (x, y) and zoom level.\n *\n * `gesturing` is true while the user is actively manipulating the camera\n * (continuous wheel zoom, pinch, two-finger pan). Set/cleared by gesture\n * handlers via {@link LayoutEngine.setGesturing}; render layers can use it\n * to defer expensive work (e.g. the R3F compositor skips zoom-band\n * repaints while gesturing so a continuous pinch doesn't trigger a\n * repaint storm across every visible widget).\n */\nexport const CameraResource = defineResource('Camera', {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n\tgesturing: false,\n});\n\n/** Viewport dimensions in CSS pixels and device pixel ratio. Updated on resize. */\nexport const ViewportResource = defineResource('Viewport', {\n\twidth: 0,\n\theight: 0,\n\tdpr: 1,\n});\n\n/** Minimum and maximum zoom levels. */\nexport const ZoomConfigResource = defineResource('ZoomConfig', {\n\tmin: 0.1,\n\tmax: 5.0,\n});\n\n/** Screen-space pixel thresholds for responsive breakpoints (micro/compact/normal/expanded/detailed). */\nexport const BreakpointConfigResource = defineResource('BreakpointConfig', {\n\tmicro: 40,\n\tcompact: 120,\n\tnormal: 500,\n\texpanded: 1200,\n});\n\n/**\n * Navigation stack for hierarchical container traversal. Always has at\n * least one frame (the root). The last element is the current frame;\n * `containerId === null` means the user is at the root canvas.\n *\n * Runtime-only view state — deliberately not serialized, so reloading a\n * saved canvas always drops the user at the root frame (RFC-004 § Phase 0c).\n */\nexport const NavigationStackResource = defineResource('NavigationStack', {\n\tframes: [{ containerId: null }] as NavigationFrame[],\n\tchanged: false,\n});\n\n/** Shape shared by `ContainerCamera` components and `RootCameraResource`. */\nexport type FrameCameraState = { x: number; y: number; zoom: number };\n\n/**\n * Camera state for the root canvas. Persisted (serialized) so the root\n * view returns to its previous pan/zoom across navigation push/pop and\n * across save/load. Container frames use the `ContainerCamera` component\n * on the container entity instead (RFC-004 § Phase 0c).\n */\nexport const RootCameraResource = defineResource<FrameCameraState>('RootCamera', {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n});\n\n/** Responsive breakpoint name derived from a widget's screen-space size. */\nexport type Breakpoint = 'micro' | 'compact' | 'normal' | 'expanded' | 'detailed';\n\n/**\n * Built-in card preset sizes (iOS widget conventions — 155×155 tile).\n * Single source of truth consumed by:\n * - {@link CardPresetsResource} — runtime lookup by the cardSystem.\n * - `createCardWidget` / `createGeometryCardWidget` — widget-registration-\n * time `defaultSize`, which must be known before the engine is built.\n */\nexport const DEFAULT_CARD_PRESET_SIZES: Record<CardPreset, { width: number; height: number }> = {\n\tsmall: { width: 155, height: 155 },\n\tmedium: { width: 329, height: 155 },\n\tlarge: { width: 329, height: 345 },\n\txl: { width: 329, height: 535 },\n};\n\n/**\n * iOS-style card preset size map. Lookup happens by `Card.preset`; the\n * `cardSystem` stamps `Transform2D.width/height` from the resolved size.\n *\n * Override at `createLayoutEngine({ cardPresets })` for tablet-scale or\n * custom design systems.\n */\nexport const CardPresetsResource = defineResource('CardPresets', {\n\tpresets: { ...DEFAULT_CARD_PRESET_SIZES },\n\t/** Gap between adjacent tiles (future tile-snap system reads this). */\n\tgap: 19,\n});\n\n/** ECS resource holding the SpatialIndex instance for viewport culling and hit testing. */\nexport const SpatialIndexResource = defineResource('SpatialIndex', {\n\tinstance: null as SpatialIndex | null,\n});\n\n/**\n * Render-layer order, low → high. `<InfiniteCanvas>` mounts a DOM\n * container for each entry; widgets render into the container that\n * matches their `Layer.name`. Per-widget `ZIndex` controls intra-layer\n * ordering. RFC-003.\n *\n * Default: `['background', 'base', 'overlay']`. Out-of-the-box the\n * three names map to fixed DOM positions in `<InfiniteCanvas>`'s\n * stacking sandwich: background and base sit beneath the R3F canvas\n * (zIndex < 1), overlay sits above it (zIndex 2). Custom layer names\n * are not yet rendered by the default `<InfiniteCanvas>`.\n */\nexport type LayerOrderData = {\n\tlayers: import('./components.js').LayerName[];\n};\n\nexport const LayerOrderResource = defineResource<LayerOrderData>('LayerOrder', {\n\tlayers: ['background', 'base', 'overlay'],\n});\n","import { createContext, useContext } from 'react';\nimport type { LayoutEngine } from '../../ecs/engine/index.js';\n\nconst EngineContext = createContext<LayoutEngine | null>(null);\n\nexport const EngineProvider = EngineContext.Provider;\n\n/**\n * Returns the LayoutEngine instance from the nearest InfiniteCanvas context.\n * Throws if used outside an InfiniteCanvas provider.\n */\nexport function useLayoutEngine(): LayoutEngine {\n\tconst engine = useContext(EngineContext);\n\tif (!engine) {\n\t\tthrow new Error('useLayoutEngine must be used within an <InfiniteCanvas>');\n\t}\n\treturn engine;\n}\n","import type { ComponentType, EntityId, ResourceType, TagType } from '@jamesyong42/reactive-ecs';\nimport { useEffect, useRef, useState } from 'react';\nimport { CameraResource } from '../../ecs/resources.js';\nimport { useLayoutEngine } from '../context/engine-context.js';\n\nfunction shallowEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\n\tconst keysA = Object.keys(a);\n\tconst keysB = Object.keys(b);\n\tif (keysA.length !== keysB.length) return false;\n\tfor (const key of keysA) {\n\t\tif (a[key] !== b[key]) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Reactively reads an ECS component from an entity.\n * Returns undefined if the entity doesn't have the component. Re-renders when the component changes.\n */\nexport function useComponent<T>(entity: EntityId, type: ComponentType<T>): T | undefined {\n\tconst engine = useLayoutEngine();\n\tconst [value, setValue] = useState<T | undefined>(() => engine.get(entity, type));\n\n\tuseEffect(() => {\n\t\tconst current = engine.get(entity, type);\n\t\tsetValue(current === undefined ? undefined : { ...current });\n\n\t\tconst unsub = engine.world.onComponentChanged(\n\t\t\ttype,\n\t\t\t(_id, _prev, next) => {\n\t\t\t\tsetValue(next === undefined ? undefined : { ...next });\n\t\t\t},\n\t\t\tentity,\n\t\t);\n\n\t\treturn unsub;\n\t}, [engine, entity, type]);\n\n\treturn value;\n}\n\n/**\n * Reactively checks whether an entity has a tag.\n * Re-renders when the tag is added or removed.\n */\nexport function useTag(entity: EntityId, type: TagType): boolean {\n\tconst engine = useLayoutEngine();\n\tconst [has, setHas] = useState(() => engine.world.hasTag(entity, type));\n\n\tuseEffect(() => {\n\t\tsetHas(engine.world.hasTag(entity, type));\n\n\t\tconst unsub1 = engine.world.onTagAdded(type, () => setHas(true), entity);\n\t\tconst unsub2 = engine.world.onTagRemoved(type, () => setHas(false), entity);\n\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t}, [engine, entity, type]);\n\n\treturn has;\n}\n\n/**\n * Reactively reads an ECS resource (singleton data).\n * Re-renders when any field of the resource changes (shallow comparison).\n */\nexport function useResource<T>(type: ResourceType<T>): T {\n\tconst engine = useLayoutEngine();\n\tconst [value, setValue] = useState<T>(() => ({ ...engine.world.getResource(type) }));\n\tconst prevRef = useRef<T | undefined>(undefined);\n\n\tuseEffect(() => {\n\t\t// Immediately sync to current value on (re-)subscription\n\t\tconst current = engine.world.getResource(type);\n\t\tif (current !== undefined) {\n\t\t\tprevRef.current = current;\n\t\t\tsetValue({ ...current });\n\t\t}\n\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst current = engine.world.getResource(type);\n\t\t\tif (\n\t\t\t\tprevRef.current === undefined ||\n\t\t\t\t!shallowEqual(\n\t\t\t\t\tcurrent as unknown as Record<string, unknown>,\n\t\t\t\t\tprevRef.current as unknown as Record<string, unknown>,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tprevRef.current = current;\n\t\t\t\tsetValue({ ...current });\n\t\t\t}\n\t\t});\n\t\treturn unsub;\n\t}, [engine, type]);\n\n\treturn value;\n}\n\n/**\n * Returns entity IDs matching all specified component/tag types.\n * Re-renders when the result set changes.\n */\nexport function useQuery(...types: (ComponentType | TagType)[]): EntityId[] {\n\tconst engine = useLayoutEngine();\n\tconst typesRef = useRef(types);\n\ttypesRef.current = types;\n\tconst typesKey = types.map((t) => t.name).join('\\0');\n\n\tconst [result, setResult] = useState<EntityId[]>(() => engine.world.query(...types));\n\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: typesKey is a stable proxy for the types array\n\tuseEffect(() => {\n\t\tsetResult(engine.world.query(...typesRef.current));\n\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.query(...typesRef.current);\n\t\t\tsetResult((prev) => {\n\t\t\t\tif (prev.length !== next.length) return next;\n\t\t\t\tfor (let i = 0; i < prev.length; i++) {\n\t\t\t\t\tif (prev[i] !== next[i]) return next;\n\t\t\t\t}\n\t\t\t\treturn prev;\n\t\t\t});\n\t\t});\n\t\treturn unsub;\n\t}, [engine, typesKey]);\n\n\treturn result;\n}\n\n/**\n * Returns all entity IDs that have the specified tag.\n * Re-renders when entities are tagged or untagged.\n */\nexport function useTaggedEntities(type: TagType): EntityId[] {\n\tconst engine = useLayoutEngine();\n\tconst [result, setResult] = useState<EntityId[]>(() => engine.world.queryTagged(type));\n\n\tuseEffect(() => {\n\t\tsetResult([...engine.world.queryTagged(type)]);\n\n\t\tconst update = () => setResult([...engine.world.queryTagged(type)]);\n\t\tconst unsub1 = engine.world.onTagAdded(type, update);\n\t\tconst unsub2 = engine.world.onTagRemoved(type, update);\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t}, [engine, type]);\n\n\treturn result;\n}\n\n/**\n * Returns the current camera state {x, y, zoom}.\n * Shorthand for useResource(CameraResource).\n */\nexport function useCamera(): { x: number; y: number; zoom: number } {\n\tconst cam = useResource(CameraResource);\n\treturn { x: cam?.x ?? 0, y: cam?.y ?? 0, zoom: cam?.zoom ?? 1 };\n}\n\nfunction sameIdList(a: readonly number[], b: readonly number[]): boolean {\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n\treturn true;\n}\n\nfunction sameTypeList<T extends { name: string }>(a: readonly T[], b: readonly T[]): boolean {\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n\treturn true;\n}\n\n/**\n * Reactively returns the IDs of every live entity in the world.\n * Updates on entity create and destroy.\n */\nexport function useAllEntities(): EntityId[] {\n\tconst engine = useLayoutEngine();\n\tconst [entities, setEntities] = useState<EntityId[]>(() => engine.world.getAllEntities());\n\n\tuseEffect(() => {\n\t\tconst refresh = () => {\n\t\t\tconst next = engine.world.getAllEntities();\n\t\t\tsetEntities((prev) => (sameIdList(prev, next) ? prev : next));\n\t\t};\n\t\trefresh();\n\t\tconst unsub1 = engine.world.onEntityCreated(refresh);\n\t\tconst unsub2 = engine.world.onEntityDestroyed(refresh);\n\t\treturn () => {\n\t\t\tunsub1();\n\t\t\tunsub2();\n\t\t};\n\t}, [engine]);\n\n\treturn entities;\n}\n\n/**\n * Reactively returns the ComponentTypes currently attached to an entity.\n * Polls per frame (engine only ticks when dirty), but never re-renders unless the set changes.\n */\nexport function useEntityComponents(entity: EntityId): ComponentType[] {\n\tconst engine = useLayoutEngine();\n\tconst [types, setTypes] = useState<ComponentType[]>(() => engine.world.getComponentsOf(entity));\n\n\tuseEffect(() => {\n\t\tsetTypes(engine.world.getComponentsOf(entity));\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.getComponentsOf(entity);\n\t\t\tsetTypes((prev) => (sameTypeList(prev, next) ? prev : next));\n\t\t});\n\t\treturn unsub;\n\t}, [engine, entity]);\n\n\treturn types;\n}\n\n/**\n * Reactively returns the TagTypes currently attached to an entity.\n */\nexport function useEntityTags(entity: EntityId): TagType[] {\n\tconst engine = useLayoutEngine();\n\tconst [types, setTypes] = useState<TagType[]>(() => engine.world.getTagsOf(entity));\n\n\tuseEffect(() => {\n\t\tsetTypes(engine.world.getTagsOf(entity));\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.getTagsOf(entity);\n\t\t\tsetTypes((prev) => (sameTypeList(prev, next) ? prev : next));\n\t\t});\n\t\treturn unsub;\n\t}, [engine, entity]);\n\n\treturn types;\n}\n\n/**\n * Reactively returns every ComponentType the world has observed.\n * Grows over time as new component types are first used.\n */\nexport function useRegisteredComponents(): ComponentType[] {\n\tconst engine = useLayoutEngine();\n\tconst [types, setTypes] = useState<ComponentType[]>(() => engine.world.getRegisteredComponents());\n\n\tuseEffect(() => {\n\t\tsetTypes(engine.world.getRegisteredComponents());\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.getRegisteredComponents();\n\t\t\tsetTypes((prev) => (sameTypeList(prev, next) ? prev : next));\n\t\t});\n\t\treturn unsub;\n\t}, [engine]);\n\n\treturn types;\n}\n\n/**\n * Reactively returns every TagType the world has observed.\n */\nexport function useRegisteredTags(): TagType[] {\n\tconst engine = useLayoutEngine();\n\tconst [types, setTypes] = useState<TagType[]>(() => engine.world.getRegisteredTags());\n\n\tuseEffect(() => {\n\t\tsetTypes(engine.world.getRegisteredTags());\n\t\tconst unsub = engine.onFrame(() => {\n\t\t\tconst next = engine.world.getRegisteredTags();\n\t\t\tsetTypes((prev) => (sameTypeList(prev, next) ? prev : next));\n\t\t});\n\t\treturn unsub;\n\t}, [engine]);\n\n\treturn types;\n}\n"],"mappings":"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"}
|