@woven-canvas/core 1.0.14 → 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/build/index.cjs CHANGED
@@ -156,6 +156,7 @@ __export(index_exports, {
156
156
  deselectBlock: () => deselectBlock,
157
157
  detectEmbedProvider: () => detectEmbedProvider,
158
158
  field: () => import_core74.field,
159
+ filterRoots: () => filterRoots,
159
160
  findFrameAtPoint: () => findFrameAtPoint,
160
161
  generateUuidBySeed: () => generateUuidBySeed,
161
162
  getBackrefs: () => import_core74.getBackrefs,
@@ -436,6 +437,12 @@ var ControlsOptions = import_zod.z.object({
436
437
  * @default 'select'
437
438
  */
438
439
  leftMouseTool: import_zod.z.string().max(32).default("select"),
440
+ /**
441
+ * Tool activated by left mouse button while the space bar is held.
442
+ * Set to an empty string to disable the remap.
443
+ * @default 'hand'
444
+ */
445
+ spaceLeftMouseTool: import_zod.z.string().max(32).default("hand"),
439
446
  /**
440
447
  * Tool activated by middle mouse button.
441
448
  * @default 'hand'
@@ -635,6 +642,7 @@ var BlockDef = import_zod.z.object({
635
642
  canScale: import_zod.z.boolean().default(true),
636
643
  snappable: import_zod.z.boolean().default(true),
637
644
  interactable: import_zod.z.boolean().default(true),
645
+ showOperations: import_zod.z.boolean().default(true),
638
646
  excludeFromRankBounds: import_zod.z.boolean().default(false),
639
647
  connectors: BlockDefConnectors.default(BlockDefConnectors.parse({}))
640
648
  });
@@ -2152,25 +2160,335 @@ var CameraDef = class extends import_canvas_store29.CanvasSingletonDef {
2152
2160
  var Camera = new CameraDef();
2153
2161
 
2154
2162
  // src/singletons/Controls.ts
2163
+ var import_canvas_store31 = require("@woven-ecs/canvas-store");
2164
+ var import_core25 = require("@woven-ecs/core");
2165
+
2166
+ // src/singletons/Keyboard.ts
2155
2167
  var import_canvas_store30 = require("@woven-ecs/canvas-store");
2156
2168
  var import_core24 = require("@woven-ecs/core");
2169
+ var KEY_BUFFER_SIZE = 32;
2170
+ var KeyboardSchema = {
2171
+ /**
2172
+ * Buffer where each bit represents whether a key is currently pressed.
2173
+ * Uses field.buffer for zero-allocation subarray views.
2174
+ */
2175
+ keysDown: import_core24.field.buffer(import_core24.field.uint8()).size(KEY_BUFFER_SIZE),
2176
+ /**
2177
+ * Buffer for key-down triggers (true for exactly 1 frame when key is pressed).
2178
+ * Uses field.buffer for zero-allocation subarray views.
2179
+ */
2180
+ keysDownTrigger: import_core24.field.buffer(import_core24.field.uint8()).size(KEY_BUFFER_SIZE),
2181
+ /**
2182
+ * Buffer for key-up triggers (true for exactly 1 frame when key is released).
2183
+ * Uses field.buffer for zero-allocation subarray views.
2184
+ */
2185
+ keysUpTrigger: import_core24.field.buffer(import_core24.field.uint8()).size(KEY_BUFFER_SIZE),
2186
+ /** Common modifier - Shift key is down */
2187
+ shiftDown: import_core24.field.boolean().default(false),
2188
+ /** Common modifier - Alt/Option key is down */
2189
+ altDown: import_core24.field.boolean().default(false),
2190
+ /** Common modifier - Ctrl (Windows/Linux) or Cmd (Mac) is down */
2191
+ modDown: import_core24.field.boolean().default(false)
2192
+ };
2193
+ function getBit(buffer, bitIndex) {
2194
+ if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
2195
+ const byteIndex = Math.floor(bitIndex / 8);
2196
+ const bitOffset = bitIndex % 8;
2197
+ return (buffer[byteIndex] & 1 << bitOffset) !== 0;
2198
+ }
2199
+ var KeyboardDef = class extends import_canvas_store30.CanvasSingletonDef {
2200
+ constructor() {
2201
+ super({ name: "keyboard" }, KeyboardSchema);
2202
+ }
2203
+ /**
2204
+ * Check if a key is currently pressed.
2205
+ * @param ctx - Editor context
2206
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2207
+ */
2208
+ isKeyDown(ctx, key) {
2209
+ return getBit(this.read(ctx).keysDown, key);
2210
+ }
2211
+ /**
2212
+ * Check if a key was just pressed this frame.
2213
+ * @param ctx - Editor context
2214
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2215
+ */
2216
+ isKeyDownTrigger(ctx, key) {
2217
+ return getBit(this.read(ctx).keysDownTrigger, key);
2218
+ }
2219
+ /**
2220
+ * Check if a key was just released this frame.
2221
+ * @param ctx - Editor context
2222
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2223
+ */
2224
+ isKeyUpTrigger(ctx, key) {
2225
+ return getBit(this.read(ctx).keysUpTrigger, key);
2226
+ }
2227
+ /**
2228
+ * Reset all keyboard state.
2229
+ * Clears all key states and modifier flags.
2230
+ */
2231
+ reset(ctx) {
2232
+ const keyboard = this.write(ctx);
2233
+ clearBits(keyboard.keysDown);
2234
+ clearBits(keyboard.keysDownTrigger);
2235
+ clearBits(keyboard.keysUpTrigger);
2236
+ keyboard.shiftDown = false;
2237
+ keyboard.altDown = false;
2238
+ keyboard.modDown = false;
2239
+ }
2240
+ };
2241
+ var Keyboard = new KeyboardDef();
2242
+ function setBit(buffer, bitIndex, value) {
2243
+ if (bitIndex < 0 || bitIndex >= buffer.length * 8) return;
2244
+ const byteIndex = Math.floor(bitIndex / 8);
2245
+ const bitOffset = bitIndex % 8;
2246
+ if (value) {
2247
+ buffer[byteIndex] |= 1 << bitOffset;
2248
+ } else {
2249
+ buffer[byteIndex] &= ~(1 << bitOffset);
2250
+ }
2251
+ }
2252
+ function clearBits(buffer) {
2253
+ for (let i = 0; i < buffer.length; i++) {
2254
+ buffer[i] = 0;
2255
+ }
2256
+ }
2257
+ var codeToIndex = {
2258
+ // Letters (0-25)
2259
+ KeyA: 0,
2260
+ KeyB: 1,
2261
+ KeyC: 2,
2262
+ KeyD: 3,
2263
+ KeyE: 4,
2264
+ KeyF: 5,
2265
+ KeyG: 6,
2266
+ KeyH: 7,
2267
+ KeyI: 8,
2268
+ KeyJ: 9,
2269
+ KeyK: 10,
2270
+ KeyL: 11,
2271
+ KeyM: 12,
2272
+ KeyN: 13,
2273
+ KeyO: 14,
2274
+ KeyP: 15,
2275
+ KeyQ: 16,
2276
+ KeyR: 17,
2277
+ KeyS: 18,
2278
+ KeyT: 19,
2279
+ KeyU: 20,
2280
+ KeyV: 21,
2281
+ KeyW: 22,
2282
+ KeyX: 23,
2283
+ KeyY: 24,
2284
+ KeyZ: 25,
2285
+ // Numbers (26-35)
2286
+ Digit0: 26,
2287
+ Digit1: 27,
2288
+ Digit2: 28,
2289
+ Digit3: 29,
2290
+ Digit4: 30,
2291
+ Digit5: 31,
2292
+ Digit6: 32,
2293
+ Digit7: 33,
2294
+ Digit8: 34,
2295
+ Digit9: 35,
2296
+ // Function keys (36-47)
2297
+ F1: 36,
2298
+ F2: 37,
2299
+ F3: 38,
2300
+ F4: 39,
2301
+ F5: 40,
2302
+ F6: 41,
2303
+ F7: 42,
2304
+ F8: 43,
2305
+ F9: 44,
2306
+ F10: 45,
2307
+ F11: 46,
2308
+ F12: 47,
2309
+ // Modifiers (48-51)
2310
+ ShiftLeft: 48,
2311
+ ShiftRight: 49,
2312
+ ControlLeft: 50,
2313
+ ControlRight: 51,
2314
+ AltLeft: 52,
2315
+ AltRight: 53,
2316
+ MetaLeft: 54,
2317
+ MetaRight: 55,
2318
+ // Navigation (56-71)
2319
+ Escape: 56,
2320
+ Space: 57,
2321
+ Enter: 58,
2322
+ Tab: 59,
2323
+ Backspace: 60,
2324
+ Delete: 61,
2325
+ ArrowLeft: 62,
2326
+ ArrowUp: 63,
2327
+ ArrowRight: 64,
2328
+ ArrowDown: 65,
2329
+ Home: 66,
2330
+ End: 67,
2331
+ PageUp: 68,
2332
+ PageDown: 69,
2333
+ Insert: 70,
2334
+ // Punctuation (72-83)
2335
+ Semicolon: 72,
2336
+ Equal: 73,
2337
+ Comma: 74,
2338
+ Minus: 75,
2339
+ Period: 76,
2340
+ Slash: 77,
2341
+ Backquote: 78,
2342
+ BracketLeft: 79,
2343
+ Backslash: 80,
2344
+ BracketRight: 81,
2345
+ Quote: 82,
2346
+ // Numpad (84-99)
2347
+ Numpad0: 84,
2348
+ Numpad1: 85,
2349
+ Numpad2: 86,
2350
+ Numpad3: 87,
2351
+ Numpad4: 88,
2352
+ Numpad5: 89,
2353
+ Numpad6: 90,
2354
+ Numpad7: 91,
2355
+ Numpad8: 92,
2356
+ Numpad9: 93,
2357
+ NumpadAdd: 94,
2358
+ NumpadSubtract: 95,
2359
+ NumpadMultiply: 96,
2360
+ NumpadDivide: 97,
2361
+ NumpadDecimal: 98,
2362
+ NumpadEnter: 99
2363
+ };
2364
+ var Key = {
2365
+ // Letters
2366
+ A: codeToIndex.KeyA,
2367
+ B: codeToIndex.KeyB,
2368
+ C: codeToIndex.KeyC,
2369
+ D: codeToIndex.KeyD,
2370
+ E: codeToIndex.KeyE,
2371
+ F: codeToIndex.KeyF,
2372
+ G: codeToIndex.KeyG,
2373
+ H: codeToIndex.KeyH,
2374
+ I: codeToIndex.KeyI,
2375
+ J: codeToIndex.KeyJ,
2376
+ K: codeToIndex.KeyK,
2377
+ L: codeToIndex.KeyL,
2378
+ M: codeToIndex.KeyM,
2379
+ N: codeToIndex.KeyN,
2380
+ O: codeToIndex.KeyO,
2381
+ P: codeToIndex.KeyP,
2382
+ Q: codeToIndex.KeyQ,
2383
+ R: codeToIndex.KeyR,
2384
+ S: codeToIndex.KeyS,
2385
+ T: codeToIndex.KeyT,
2386
+ U: codeToIndex.KeyU,
2387
+ V: codeToIndex.KeyV,
2388
+ W: codeToIndex.KeyW,
2389
+ X: codeToIndex.KeyX,
2390
+ Y: codeToIndex.KeyY,
2391
+ Z: codeToIndex.KeyZ,
2392
+ // Numbers
2393
+ Digit0: codeToIndex.Digit0,
2394
+ Digit1: codeToIndex.Digit1,
2395
+ Digit2: codeToIndex.Digit2,
2396
+ Digit3: codeToIndex.Digit3,
2397
+ Digit4: codeToIndex.Digit4,
2398
+ Digit5: codeToIndex.Digit5,
2399
+ Digit6: codeToIndex.Digit6,
2400
+ Digit7: codeToIndex.Digit7,
2401
+ Digit8: codeToIndex.Digit8,
2402
+ Digit9: codeToIndex.Digit9,
2403
+ // Function keys
2404
+ F1: codeToIndex.F1,
2405
+ F2: codeToIndex.F2,
2406
+ F3: codeToIndex.F3,
2407
+ F4: codeToIndex.F4,
2408
+ F5: codeToIndex.F5,
2409
+ F6: codeToIndex.F6,
2410
+ F7: codeToIndex.F7,
2411
+ F8: codeToIndex.F8,
2412
+ F9: codeToIndex.F9,
2413
+ F10: codeToIndex.F10,
2414
+ F11: codeToIndex.F11,
2415
+ F12: codeToIndex.F12,
2416
+ // Modifiers
2417
+ ShiftLeft: codeToIndex.ShiftLeft,
2418
+ ShiftRight: codeToIndex.ShiftRight,
2419
+ ControlLeft: codeToIndex.ControlLeft,
2420
+ ControlRight: codeToIndex.ControlRight,
2421
+ AltLeft: codeToIndex.AltLeft,
2422
+ AltRight: codeToIndex.AltRight,
2423
+ MetaLeft: codeToIndex.MetaLeft,
2424
+ MetaRight: codeToIndex.MetaRight,
2425
+ // Navigation
2426
+ Escape: codeToIndex.Escape,
2427
+ Space: codeToIndex.Space,
2428
+ Enter: codeToIndex.Enter,
2429
+ Tab: codeToIndex.Tab,
2430
+ Backspace: codeToIndex.Backspace,
2431
+ Delete: codeToIndex.Delete,
2432
+ ArrowLeft: codeToIndex.ArrowLeft,
2433
+ ArrowUp: codeToIndex.ArrowUp,
2434
+ ArrowRight: codeToIndex.ArrowRight,
2435
+ ArrowDown: codeToIndex.ArrowDown,
2436
+ Home: codeToIndex.Home,
2437
+ End: codeToIndex.End,
2438
+ PageUp: codeToIndex.PageUp,
2439
+ PageDown: codeToIndex.PageDown,
2440
+ Insert: codeToIndex.Insert,
2441
+ // Punctuation
2442
+ Semicolon: codeToIndex.Semicolon,
2443
+ Equal: codeToIndex.Equal,
2444
+ Comma: codeToIndex.Comma,
2445
+ Minus: codeToIndex.Minus,
2446
+ Period: codeToIndex.Period,
2447
+ Slash: codeToIndex.Slash,
2448
+ Backquote: codeToIndex.Backquote,
2449
+ BracketLeft: codeToIndex.BracketLeft,
2450
+ Backslash: codeToIndex.Backslash,
2451
+ BracketRight: codeToIndex.BracketRight,
2452
+ Quote: codeToIndex.Quote,
2453
+ // Numpad
2454
+ Numpad0: codeToIndex.Numpad0,
2455
+ Numpad1: codeToIndex.Numpad1,
2456
+ Numpad2: codeToIndex.Numpad2,
2457
+ Numpad3: codeToIndex.Numpad3,
2458
+ Numpad4: codeToIndex.Numpad4,
2459
+ Numpad5: codeToIndex.Numpad5,
2460
+ Numpad6: codeToIndex.Numpad6,
2461
+ Numpad7: codeToIndex.Numpad7,
2462
+ Numpad8: codeToIndex.Numpad8,
2463
+ Numpad9: codeToIndex.Numpad9,
2464
+ NumpadAdd: codeToIndex.NumpadAdd,
2465
+ NumpadSubtract: codeToIndex.NumpadSubtract,
2466
+ NumpadMultiply: codeToIndex.NumpadMultiply,
2467
+ NumpadDivide: codeToIndex.NumpadDivide,
2468
+ NumpadDecimal: codeToIndex.NumpadDecimal,
2469
+ NumpadEnter: codeToIndex.NumpadEnter
2470
+ };
2471
+
2472
+ // src/singletons/Controls.ts
2157
2473
  var ControlsSchema = {
2158
2474
  /** Tool activated by left mouse button */
2159
- leftMouseTool: import_core24.field.string().max(32).default("select"),
2475
+ leftMouseTool: import_core25.field.string().max(32).default("select"),
2476
+ /** Tool activated by left mouse button while the space bar is held (empty string = no remap) */
2477
+ spaceLeftMouseTool: import_core25.field.string().max(32).default("hand"),
2160
2478
  /** Tool activated by middle mouse button */
2161
- middleMouseTool: import_core24.field.string().max(32).default("hand"),
2479
+ middleMouseTool: import_core25.field.string().max(32).default("hand"),
2162
2480
  /** Tool activated by right mouse button */
2163
- rightMouseTool: import_core24.field.string().max(32).default("hand"),
2481
+ rightMouseTool: import_core25.field.string().max(32).default("hand"),
2164
2482
  /** Tool activated by mouse wheel */
2165
- wheelTool: import_core24.field.string().max(32).default("scroll"),
2483
+ wheelTool: import_core25.field.string().max(32).default("scroll"),
2166
2484
  /** Tool activated by mouse wheel with modifier key held */
2167
- modWheelTool: import_core24.field.string().max(32).default("zoom"),
2485
+ modWheelTool: import_core25.field.string().max(32).default("zoom"),
2168
2486
  /** JSON snapshot of block to place on next click (empty string = no placement active) */
2169
- heldSnapshot: import_core24.field.string().max(65536).default(""),
2487
+ heldSnapshot: import_core25.field.string().max(65536).default(""),
2170
2488
  /** User-facing tool name for UI highlighting (may differ from leftMouseTool during draw/drag-out) */
2171
- activeToolName: import_core24.field.string().max(32).default("select")
2489
+ activeToolName: import_core25.field.string().max(32).default("select")
2172
2490
  };
2173
- var ControlsDef = class extends import_canvas_store30.CanvasSingletonDef {
2491
+ var ControlsDef = class extends import_canvas_store31.CanvasSingletonDef {
2174
2492
  constructor() {
2175
2493
  super({ name: "controls" }, ControlsSchema);
2176
2494
  }
@@ -2183,7 +2501,9 @@ var ControlsDef = class extends import_canvas_store30.CanvasSingletonDef {
2183
2501
  getButtons(ctx, ...tools) {
2184
2502
  const controls = this.read(ctx);
2185
2503
  const buttons = [];
2186
- if (tools.includes(controls.leftMouseTool)) {
2504
+ const spaceRemap = controls.spaceLeftMouseTool !== "" && Keyboard.isKeyDown(ctx, Key.Space);
2505
+ const leftTool = spaceRemap ? controls.spaceLeftMouseTool : controls.leftMouseTool;
2506
+ if (tools.includes(leftTool)) {
2187
2507
  buttons.push(PointerButton.Left);
2188
2508
  }
2189
2509
  if (tools.includes(controls.middleMouseTool)) {
@@ -2209,19 +2529,19 @@ var ControlsDef = class extends import_canvas_store30.CanvasSingletonDef {
2209
2529
  var Controls = new ControlsDef();
2210
2530
 
2211
2531
  // src/singletons/Cursor.ts
2212
- var import_canvas_store31 = require("@woven-ecs/canvas-store");
2213
- var import_core25 = require("@woven-ecs/core");
2532
+ var import_canvas_store32 = require("@woven-ecs/canvas-store");
2533
+ var import_core26 = require("@woven-ecs/core");
2214
2534
  var CursorSchema = {
2215
2535
  /** Base cursor kind (from current tool) */
2216
- cursorKind: import_core25.field.string().max(64).default("select"),
2536
+ cursorKind: import_core26.field.string().max(64).default("select"),
2217
2537
  /** Base cursor rotation in radians */
2218
- rotation: import_core25.field.float64().default(0),
2538
+ rotation: import_core26.field.float64().default(0),
2219
2539
  /** Context-specific cursor kind (overrides cursorKind when set, e.g., during drag/hover) */
2220
- contextCursorKind: import_core25.field.string().max(64).default(""),
2540
+ contextCursorKind: import_core26.field.string().max(64).default(""),
2221
2541
  /** Context cursor rotation in radians */
2222
- contextRotation: import_core25.field.float64().default(0)
2542
+ contextRotation: import_core26.field.float64().default(0)
2223
2543
  };
2224
- var CursorDef2 = class extends import_canvas_store31.CanvasSingletonDef {
2544
+ var CursorDef2 = class extends import_canvas_store32.CanvasSingletonDef {
2225
2545
  constructor() {
2226
2546
  super({ name: "cursor" }, CursorSchema);
2227
2547
  }
@@ -2266,10 +2586,10 @@ var CursorDef2 = class extends import_canvas_store31.CanvasSingletonDef {
2266
2586
  var Cursor = new CursorDef2();
2267
2587
 
2268
2588
  // src/singletons/FrameContainmentState.ts
2269
- var import_core26 = require("@woven-ecs/core");
2589
+ var import_core27 = require("@woven-ecs/core");
2270
2590
 
2271
2591
  // src/EditorStateDef.ts
2272
- var import_canvas_store32 = require("@woven-ecs/canvas-store");
2592
+ var import_canvas_store33 = require("@woven-ecs/canvas-store");
2273
2593
 
2274
2594
  // src/machine.ts
2275
2595
  var import_xstate = require("xstate");
@@ -2297,7 +2617,7 @@ function runMachine(machine, currentState, context, events) {
2297
2617
  }
2298
2618
 
2299
2619
  // src/EditorStateDef.ts
2300
- var EditorStateDef = class extends import_canvas_store32.CanvasSingletonDef {
2620
+ var EditorStateDef = class extends import_canvas_store33.CanvasSingletonDef {
2301
2621
  constructor(name, schema) {
2302
2622
  super({ name, sync: "none" }, schema);
2303
2623
  }
@@ -2374,29 +2694,29 @@ function defineEditorState(name, schema) {
2374
2694
  // src/singletons/FrameContainmentState.ts
2375
2695
  var FrameContainmentState = defineEditorState("frameContainmentState", {
2376
2696
  /** Current state machine state */
2377
- state: import_core26.field.string().max(16).default(FrameContainmentStateEnum.Idle),
2697
+ state: import_core27.field.string().max(16).default(FrameContainmentStateEnum.Idle),
2378
2698
  /** EntityId of the frame currently highlighted as a drop target, or null */
2379
- highlightedFrame: import_core26.field.ref()
2699
+ highlightedFrame: import_core27.field.ref()
2380
2700
  });
2381
2701
 
2382
2702
  // src/singletons/Grid.ts
2383
- var import_canvas_store33 = require("@woven-ecs/canvas-store");
2384
- var import_core27 = require("@woven-ecs/core");
2703
+ var import_canvas_store34 = require("@woven-ecs/canvas-store");
2704
+ var import_core28 = require("@woven-ecs/core");
2385
2705
  var GridSchema = {
2386
2706
  /** Whether grid snapping is enabled */
2387
- enabled: import_core27.field.boolean().default(false),
2707
+ enabled: import_core28.field.boolean().default(false),
2388
2708
  /** Whether resized/rotated objects must stay aligned to the grid */
2389
- strict: import_core27.field.boolean().default(false),
2709
+ strict: import_core28.field.boolean().default(false),
2390
2710
  /** Width of each grid column in world units */
2391
- colWidth: import_core27.field.float64().default(20),
2711
+ colWidth: import_core28.field.float64().default(20),
2392
2712
  /** Height of each grid row in world units */
2393
- rowHeight: import_core27.field.float64().default(20),
2713
+ rowHeight: import_core28.field.float64().default(20),
2394
2714
  /** Angular snap increment in radians when grid is enabled */
2395
- snapAngleRad: import_core27.field.float64().default(Math.PI / 36),
2715
+ snapAngleRad: import_core28.field.float64().default(Math.PI / 36),
2396
2716
  /** Angular snap increment in radians when shift key is held */
2397
- shiftSnapAngleRad: import_core27.field.float64().default(Math.PI / 12)
2717
+ shiftSnapAngleRad: import_core28.field.float64().default(Math.PI / 12)
2398
2718
  };
2399
- var GridDef = class extends import_canvas_store33.CanvasSingletonDef {
2719
+ var GridDef = class extends import_canvas_store34.CanvasSingletonDef {
2400
2720
  constructor() {
2401
2721
  super({ name: "grid" }, GridSchema);
2402
2722
  }
@@ -2456,17 +2776,17 @@ var GridDef = class extends import_canvas_store33.CanvasSingletonDef {
2456
2776
  var Grid = new GridDef();
2457
2777
 
2458
2778
  // src/singletons/Intersect.ts
2459
- var import_canvas_store34 = require("@woven-ecs/canvas-store");
2460
- var import_core28 = require("@woven-ecs/core");
2779
+ var import_canvas_store35 = require("@woven-ecs/canvas-store");
2780
+ var import_core29 = require("@woven-ecs/core");
2461
2781
  var IntersectSchema = {
2462
2782
  // Store up to 5 intersected entity IDs
2463
- entity1: import_core28.field.ref(),
2464
- entity2: import_core28.field.ref(),
2465
- entity3: import_core28.field.ref(),
2466
- entity4: import_core28.field.ref(),
2467
- entity5: import_core28.field.ref()
2783
+ entity1: import_core29.field.ref(),
2784
+ entity2: import_core29.field.ref(),
2785
+ entity3: import_core29.field.ref(),
2786
+ entity4: import_core29.field.ref(),
2787
+ entity5: import_core29.field.ref()
2468
2788
  };
2469
- var IntersectDef = class extends import_canvas_store34.CanvasSingletonDef {
2789
+ var IntersectDef = class extends import_canvas_store35.CanvasSingletonDef {
2470
2790
  constructor() {
2471
2791
  super({ name: "intersect" }, IntersectSchema);
2472
2792
  }
@@ -2485,340 +2805,34 @@ var IntersectDef = class extends import_canvas_store34.CanvasSingletonDef {
2485
2805
  if (intersect.entity1 !== null) result.push(intersect.entity1);
2486
2806
  if (intersect.entity2 !== null) result.push(intersect.entity2);
2487
2807
  if (intersect.entity3 !== null) result.push(intersect.entity3);
2488
- if (intersect.entity4 !== null) result.push(intersect.entity4);
2489
- if (intersect.entity5 !== null) result.push(intersect.entity5);
2490
- return result;
2491
- }
2492
- /**
2493
- * Set intersected entities from an array.
2494
- */
2495
- setAll(ctx, entities) {
2496
- const intersect = this.write(ctx);
2497
- intersect.entity1 = entities[0] ?? null;
2498
- intersect.entity2 = entities[1] ?? null;
2499
- intersect.entity3 = entities[2] ?? null;
2500
- intersect.entity4 = entities[3] ?? null;
2501
- intersect.entity5 = entities[4] ?? null;
2502
- }
2503
- /**
2504
- * Clear all intersections.
2505
- */
2506
- clear(ctx) {
2507
- const intersect = this.write(ctx);
2508
- intersect.entity1 = null;
2509
- intersect.entity2 = null;
2510
- intersect.entity3 = null;
2511
- intersect.entity4 = null;
2512
- intersect.entity5 = null;
2513
- }
2514
- };
2515
- var Intersect = new IntersectDef();
2516
-
2517
- // src/singletons/Keyboard.ts
2518
- var import_canvas_store35 = require("@woven-ecs/canvas-store");
2519
- var import_core29 = require("@woven-ecs/core");
2520
- var KEY_BUFFER_SIZE = 32;
2521
- var KeyboardSchema = {
2522
- /**
2523
- * Buffer where each bit represents whether a key is currently pressed.
2524
- * Uses field.buffer for zero-allocation subarray views.
2525
- */
2526
- keysDown: import_core29.field.buffer(import_core29.field.uint8()).size(KEY_BUFFER_SIZE),
2527
- /**
2528
- * Buffer for key-down triggers (true for exactly 1 frame when key is pressed).
2529
- * Uses field.buffer for zero-allocation subarray views.
2530
- */
2531
- keysDownTrigger: import_core29.field.buffer(import_core29.field.uint8()).size(KEY_BUFFER_SIZE),
2532
- /**
2533
- * Buffer for key-up triggers (true for exactly 1 frame when key is released).
2534
- * Uses field.buffer for zero-allocation subarray views.
2535
- */
2536
- keysUpTrigger: import_core29.field.buffer(import_core29.field.uint8()).size(KEY_BUFFER_SIZE),
2537
- /** Common modifier - Shift key is down */
2538
- shiftDown: import_core29.field.boolean().default(false),
2539
- /** Common modifier - Alt/Option key is down */
2540
- altDown: import_core29.field.boolean().default(false),
2541
- /** Common modifier - Ctrl (Windows/Linux) or Cmd (Mac) is down */
2542
- modDown: import_core29.field.boolean().default(false)
2543
- };
2544
- function getBit(buffer, bitIndex) {
2545
- if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
2546
- const byteIndex = Math.floor(bitIndex / 8);
2547
- const bitOffset = bitIndex % 8;
2548
- return (buffer[byteIndex] & 1 << bitOffset) !== 0;
2549
- }
2550
- var KeyboardDef = class extends import_canvas_store35.CanvasSingletonDef {
2551
- constructor() {
2552
- super({ name: "keyboard" }, KeyboardSchema);
2553
- }
2554
- /**
2555
- * Check if a key is currently pressed.
2556
- * @param ctx - Editor context
2557
- * @param key - The key index to check (use Key.A, Key.Space, etc.)
2558
- */
2559
- isKeyDown(ctx, key) {
2560
- return getBit(this.read(ctx).keysDown, key);
2561
- }
2562
- /**
2563
- * Check if a key was just pressed this frame.
2564
- * @param ctx - Editor context
2565
- * @param key - The key index to check (use Key.A, Key.Space, etc.)
2566
- */
2567
- isKeyDownTrigger(ctx, key) {
2568
- return getBit(this.read(ctx).keysDownTrigger, key);
2569
- }
2570
- /**
2571
- * Check if a key was just released this frame.
2572
- * @param ctx - Editor context
2573
- * @param key - The key index to check (use Key.A, Key.Space, etc.)
2574
- */
2575
- isKeyUpTrigger(ctx, key) {
2576
- return getBit(this.read(ctx).keysUpTrigger, key);
2577
- }
2578
- /**
2579
- * Reset all keyboard state.
2580
- * Clears all key states and modifier flags.
2581
- */
2582
- reset(ctx) {
2583
- const keyboard = this.write(ctx);
2584
- clearBits(keyboard.keysDown);
2585
- clearBits(keyboard.keysDownTrigger);
2586
- clearBits(keyboard.keysUpTrigger);
2587
- keyboard.shiftDown = false;
2588
- keyboard.altDown = false;
2589
- keyboard.modDown = false;
2590
- }
2591
- };
2592
- var Keyboard = new KeyboardDef();
2593
- function setBit(buffer, bitIndex, value) {
2594
- if (bitIndex < 0 || bitIndex >= buffer.length * 8) return;
2595
- const byteIndex = Math.floor(bitIndex / 8);
2596
- const bitOffset = bitIndex % 8;
2597
- if (value) {
2598
- buffer[byteIndex] |= 1 << bitOffset;
2599
- } else {
2600
- buffer[byteIndex] &= ~(1 << bitOffset);
2808
+ if (intersect.entity4 !== null) result.push(intersect.entity4);
2809
+ if (intersect.entity5 !== null) result.push(intersect.entity5);
2810
+ return result;
2601
2811
  }
2602
- }
2603
- function clearBits(buffer) {
2604
- for (let i = 0; i < buffer.length; i++) {
2605
- buffer[i] = 0;
2812
+ /**
2813
+ * Set intersected entities from an array.
2814
+ */
2815
+ setAll(ctx, entities) {
2816
+ const intersect = this.write(ctx);
2817
+ intersect.entity1 = entities[0] ?? null;
2818
+ intersect.entity2 = entities[1] ?? null;
2819
+ intersect.entity3 = entities[2] ?? null;
2820
+ intersect.entity4 = entities[3] ?? null;
2821
+ intersect.entity5 = entities[4] ?? null;
2822
+ }
2823
+ /**
2824
+ * Clear all intersections.
2825
+ */
2826
+ clear(ctx) {
2827
+ const intersect = this.write(ctx);
2828
+ intersect.entity1 = null;
2829
+ intersect.entity2 = null;
2830
+ intersect.entity3 = null;
2831
+ intersect.entity4 = null;
2832
+ intersect.entity5 = null;
2606
2833
  }
2607
- }
2608
- var codeToIndex = {
2609
- // Letters (0-25)
2610
- KeyA: 0,
2611
- KeyB: 1,
2612
- KeyC: 2,
2613
- KeyD: 3,
2614
- KeyE: 4,
2615
- KeyF: 5,
2616
- KeyG: 6,
2617
- KeyH: 7,
2618
- KeyI: 8,
2619
- KeyJ: 9,
2620
- KeyK: 10,
2621
- KeyL: 11,
2622
- KeyM: 12,
2623
- KeyN: 13,
2624
- KeyO: 14,
2625
- KeyP: 15,
2626
- KeyQ: 16,
2627
- KeyR: 17,
2628
- KeyS: 18,
2629
- KeyT: 19,
2630
- KeyU: 20,
2631
- KeyV: 21,
2632
- KeyW: 22,
2633
- KeyX: 23,
2634
- KeyY: 24,
2635
- KeyZ: 25,
2636
- // Numbers (26-35)
2637
- Digit0: 26,
2638
- Digit1: 27,
2639
- Digit2: 28,
2640
- Digit3: 29,
2641
- Digit4: 30,
2642
- Digit5: 31,
2643
- Digit6: 32,
2644
- Digit7: 33,
2645
- Digit8: 34,
2646
- Digit9: 35,
2647
- // Function keys (36-47)
2648
- F1: 36,
2649
- F2: 37,
2650
- F3: 38,
2651
- F4: 39,
2652
- F5: 40,
2653
- F6: 41,
2654
- F7: 42,
2655
- F8: 43,
2656
- F9: 44,
2657
- F10: 45,
2658
- F11: 46,
2659
- F12: 47,
2660
- // Modifiers (48-51)
2661
- ShiftLeft: 48,
2662
- ShiftRight: 49,
2663
- ControlLeft: 50,
2664
- ControlRight: 51,
2665
- AltLeft: 52,
2666
- AltRight: 53,
2667
- MetaLeft: 54,
2668
- MetaRight: 55,
2669
- // Navigation (56-71)
2670
- Escape: 56,
2671
- Space: 57,
2672
- Enter: 58,
2673
- Tab: 59,
2674
- Backspace: 60,
2675
- Delete: 61,
2676
- ArrowLeft: 62,
2677
- ArrowUp: 63,
2678
- ArrowRight: 64,
2679
- ArrowDown: 65,
2680
- Home: 66,
2681
- End: 67,
2682
- PageUp: 68,
2683
- PageDown: 69,
2684
- Insert: 70,
2685
- // Punctuation (72-83)
2686
- Semicolon: 72,
2687
- Equal: 73,
2688
- Comma: 74,
2689
- Minus: 75,
2690
- Period: 76,
2691
- Slash: 77,
2692
- Backquote: 78,
2693
- BracketLeft: 79,
2694
- Backslash: 80,
2695
- BracketRight: 81,
2696
- Quote: 82,
2697
- // Numpad (84-99)
2698
- Numpad0: 84,
2699
- Numpad1: 85,
2700
- Numpad2: 86,
2701
- Numpad3: 87,
2702
- Numpad4: 88,
2703
- Numpad5: 89,
2704
- Numpad6: 90,
2705
- Numpad7: 91,
2706
- Numpad8: 92,
2707
- Numpad9: 93,
2708
- NumpadAdd: 94,
2709
- NumpadSubtract: 95,
2710
- NumpadMultiply: 96,
2711
- NumpadDivide: 97,
2712
- NumpadDecimal: 98,
2713
- NumpadEnter: 99
2714
- };
2715
- var Key = {
2716
- // Letters
2717
- A: codeToIndex.KeyA,
2718
- B: codeToIndex.KeyB,
2719
- C: codeToIndex.KeyC,
2720
- D: codeToIndex.KeyD,
2721
- E: codeToIndex.KeyE,
2722
- F: codeToIndex.KeyF,
2723
- G: codeToIndex.KeyG,
2724
- H: codeToIndex.KeyH,
2725
- I: codeToIndex.KeyI,
2726
- J: codeToIndex.KeyJ,
2727
- K: codeToIndex.KeyK,
2728
- L: codeToIndex.KeyL,
2729
- M: codeToIndex.KeyM,
2730
- N: codeToIndex.KeyN,
2731
- O: codeToIndex.KeyO,
2732
- P: codeToIndex.KeyP,
2733
- Q: codeToIndex.KeyQ,
2734
- R: codeToIndex.KeyR,
2735
- S: codeToIndex.KeyS,
2736
- T: codeToIndex.KeyT,
2737
- U: codeToIndex.KeyU,
2738
- V: codeToIndex.KeyV,
2739
- W: codeToIndex.KeyW,
2740
- X: codeToIndex.KeyX,
2741
- Y: codeToIndex.KeyY,
2742
- Z: codeToIndex.KeyZ,
2743
- // Numbers
2744
- Digit0: codeToIndex.Digit0,
2745
- Digit1: codeToIndex.Digit1,
2746
- Digit2: codeToIndex.Digit2,
2747
- Digit3: codeToIndex.Digit3,
2748
- Digit4: codeToIndex.Digit4,
2749
- Digit5: codeToIndex.Digit5,
2750
- Digit6: codeToIndex.Digit6,
2751
- Digit7: codeToIndex.Digit7,
2752
- Digit8: codeToIndex.Digit8,
2753
- Digit9: codeToIndex.Digit9,
2754
- // Function keys
2755
- F1: codeToIndex.F1,
2756
- F2: codeToIndex.F2,
2757
- F3: codeToIndex.F3,
2758
- F4: codeToIndex.F4,
2759
- F5: codeToIndex.F5,
2760
- F6: codeToIndex.F6,
2761
- F7: codeToIndex.F7,
2762
- F8: codeToIndex.F8,
2763
- F9: codeToIndex.F9,
2764
- F10: codeToIndex.F10,
2765
- F11: codeToIndex.F11,
2766
- F12: codeToIndex.F12,
2767
- // Modifiers
2768
- ShiftLeft: codeToIndex.ShiftLeft,
2769
- ShiftRight: codeToIndex.ShiftRight,
2770
- ControlLeft: codeToIndex.ControlLeft,
2771
- ControlRight: codeToIndex.ControlRight,
2772
- AltLeft: codeToIndex.AltLeft,
2773
- AltRight: codeToIndex.AltRight,
2774
- MetaLeft: codeToIndex.MetaLeft,
2775
- MetaRight: codeToIndex.MetaRight,
2776
- // Navigation
2777
- Escape: codeToIndex.Escape,
2778
- Space: codeToIndex.Space,
2779
- Enter: codeToIndex.Enter,
2780
- Tab: codeToIndex.Tab,
2781
- Backspace: codeToIndex.Backspace,
2782
- Delete: codeToIndex.Delete,
2783
- ArrowLeft: codeToIndex.ArrowLeft,
2784
- ArrowUp: codeToIndex.ArrowUp,
2785
- ArrowRight: codeToIndex.ArrowRight,
2786
- ArrowDown: codeToIndex.ArrowDown,
2787
- Home: codeToIndex.Home,
2788
- End: codeToIndex.End,
2789
- PageUp: codeToIndex.PageUp,
2790
- PageDown: codeToIndex.PageDown,
2791
- Insert: codeToIndex.Insert,
2792
- // Punctuation
2793
- Semicolon: codeToIndex.Semicolon,
2794
- Equal: codeToIndex.Equal,
2795
- Comma: codeToIndex.Comma,
2796
- Minus: codeToIndex.Minus,
2797
- Period: codeToIndex.Period,
2798
- Slash: codeToIndex.Slash,
2799
- Backquote: codeToIndex.Backquote,
2800
- BracketLeft: codeToIndex.BracketLeft,
2801
- Backslash: codeToIndex.Backslash,
2802
- BracketRight: codeToIndex.BracketRight,
2803
- Quote: codeToIndex.Quote,
2804
- // Numpad
2805
- Numpad0: codeToIndex.Numpad0,
2806
- Numpad1: codeToIndex.Numpad1,
2807
- Numpad2: codeToIndex.Numpad2,
2808
- Numpad3: codeToIndex.Numpad3,
2809
- Numpad4: codeToIndex.Numpad4,
2810
- Numpad5: codeToIndex.Numpad5,
2811
- Numpad6: codeToIndex.Numpad6,
2812
- Numpad7: codeToIndex.Numpad7,
2813
- Numpad8: codeToIndex.Numpad8,
2814
- Numpad9: codeToIndex.Numpad9,
2815
- NumpadAdd: codeToIndex.NumpadAdd,
2816
- NumpadSubtract: codeToIndex.NumpadSubtract,
2817
- NumpadMultiply: codeToIndex.NumpadMultiply,
2818
- NumpadDivide: codeToIndex.NumpadDivide,
2819
- NumpadDecimal: codeToIndex.NumpadDecimal,
2820
- NumpadEnter: codeToIndex.NumpadEnter
2821
2834
  };
2835
+ var Intersect = new IntersectDef();
2822
2836
 
2823
2837
  // src/singletons/Mouse.ts
2824
2838
  var import_canvas_store36 = require("@woven-ecs/canvas-store");
@@ -3311,6 +3325,21 @@ function getDescendants(ctx, parentId) {
3311
3325
  }
3312
3326
  return result;
3313
3327
  }
3328
+ function filterRoots(ctx, entityIds) {
3329
+ const idSet = new Set(entityIds);
3330
+ return entityIds.filter((entityId) => {
3331
+ if (!(0, import_core41.hasComponent)(ctx, entityId, Block)) return false;
3332
+ let current = Block.read(ctx, entityId).parentId;
3333
+ const visited = /* @__PURE__ */ new Set();
3334
+ while (current !== null && (0, import_core41.hasComponent)(ctx, current, Block)) {
3335
+ if (idSet.has(current)) return false;
3336
+ if (visited.has(current)) break;
3337
+ visited.add(current);
3338
+ current = Block.read(ctx, current).parentId;
3339
+ }
3340
+ return true;
3341
+ });
3342
+ }
3314
3343
  function cascadeDelete(ctx, entityId) {
3315
3344
  const children = (0, import_core41.getBackrefs)(ctx, entityId, Block, "parentId");
3316
3345
  for (const childId of children) {
@@ -5747,6 +5776,9 @@ var selectSystem = defineEditorSystem({ phase: "capture", priority: 100 }, (ctx)
5747
5776
  buttons = ["left"];
5748
5777
  }
5749
5778
  const events = getPointerInput(ctx, buttons);
5779
+ if (buttons.length === 0 && (state.state === SelectionState.SelectionBoxPointing || state.state === SelectionState.SelectionBoxDragging)) {
5780
+ events.push({ type: "cancel", ctx });
5781
+ }
5750
5782
  if (events.length === 0) return;
5751
5783
  SelectionStateSingleton.run(ctx, selectionMachine, events);
5752
5784
  });
@@ -5910,6 +5942,7 @@ var blockSystem = defineEditorSystem({ phase: "update" }, (ctx) => {
5910
5942
  }
5911
5943
  }
5912
5944
  });
5945
+ on(ctx, DuplicateSelected, duplicateSelected);
5913
5946
  on(ctx, BringForwardSelected, bringForwardSelected);
5914
5947
  on(ctx, SendBackwardSelected, sendBackwardSelected);
5915
5948
  on(ctx, SetCursor, (ctx2, payload) => {
@@ -5935,6 +5968,23 @@ function deselectAllBlocks(ctx) {
5935
5968
  deselectBlock(ctx, entityId);
5936
5969
  }
5937
5970
  }
5971
+ var DUPLICATE_OFFSET = 20;
5972
+ function duplicateSelected(ctx) {
5973
+ const selectedBlocks = [...selectedBlocksQuery4.current(ctx)];
5974
+ if (selectedBlocks.length === 0) return;
5975
+ cloneEntities(ctx, selectedBlocks, [0, 0], crypto.randomUUID());
5976
+ const grid = Grid.read(ctx);
5977
+ const offset = grid.enabled && grid.colWidth > 0 && grid.rowHeight > 0 ? [grid.colWidth, grid.rowHeight] : [DUPLICATE_OFFSET, DUPLICATE_OFFSET];
5978
+ for (const entityId of filterRoots(ctx, selectedBlocks)) {
5979
+ const worldPos = Block.getWorldPosition(ctx, entityId);
5980
+ import_math12.Vec2.add(worldPos, offset);
5981
+ Block.setWorldPosition(ctx, entityId, worldPos);
5982
+ }
5983
+ const { transformBoxId } = TransformBoxStateSingleton.read(ctx);
5984
+ if (transformBoxId !== null) {
5985
+ UpdateTransformBox.spawn(ctx, { transformBoxId });
5986
+ }
5987
+ }
5938
5988
  function bringForwardSelected(ctx) {
5939
5989
  const selectedBlocks = [...selectedBlocksQuery4.current(ctx)];
5940
5990
  if (selectedBlocks.length === 0) return;
@@ -5972,7 +6022,6 @@ function sendBackwardSelected(ctx) {
5972
6022
  }
5973
6023
  }
5974
6024
  function cloneEntities(ctx, entityIds, offset, seed) {
5975
- const rootSet = new Set(entityIds);
5976
6025
  const allEntityIdsSet = new Set(entityIds);
5977
6026
  for (const entityId of entityIds) {
5978
6027
  for (const descendant of getDescendants(ctx, entityId)) {
@@ -5980,6 +6029,7 @@ function cloneEntities(ctx, entityIds, offset, seed) {
5980
6029
  }
5981
6030
  }
5982
6031
  entityIds = [...allEntityIdsSet];
6032
+ const rootSet = new Set(filterRoots(ctx, entityIds));
5983
6033
  const { componentsById } = (0, import_core67.getResources)(ctx);
5984
6034
  const documentComponents = new Map([...componentsById].filter(([, def]) => def.sync === "document"));
5985
6035
  const syncedComponentId = import_canvas_store46.Synced._getComponentId(ctx);
@@ -7391,6 +7441,7 @@ var Editor = class {
7391
7441
  deselectBlock,
7392
7442
  detectEmbedProvider,
7393
7443
  field,
7444
+ filterRoots,
7394
7445
  findFrameAtPoint,
7395
7446
  generateUuidBySeed,
7396
7447
  getBackrefs,