@woven-canvas/core 1.0.13 → 1.0.15

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
@@ -436,6 +436,12 @@ var ControlsOptions = import_zod.z.object({
436
436
  * @default 'select'
437
437
  */
438
438
  leftMouseTool: import_zod.z.string().max(32).default("select"),
439
+ /**
440
+ * Tool activated by left mouse button while the space bar is held.
441
+ * Set to an empty string to disable the remap.
442
+ * @default 'hand'
443
+ */
444
+ spaceLeftMouseTool: import_zod.z.string().max(32).default("hand"),
439
445
  /**
440
446
  * Tool activated by middle mouse button.
441
447
  * @default 'hand'
@@ -2152,25 +2158,335 @@ var CameraDef = class extends import_canvas_store29.CanvasSingletonDef {
2152
2158
  var Camera = new CameraDef();
2153
2159
 
2154
2160
  // src/singletons/Controls.ts
2161
+ var import_canvas_store31 = require("@woven-ecs/canvas-store");
2162
+ var import_core25 = require("@woven-ecs/core");
2163
+
2164
+ // src/singletons/Keyboard.ts
2155
2165
  var import_canvas_store30 = require("@woven-ecs/canvas-store");
2156
2166
  var import_core24 = require("@woven-ecs/core");
2167
+ var KEY_BUFFER_SIZE = 32;
2168
+ var KeyboardSchema = {
2169
+ /**
2170
+ * Buffer where each bit represents whether a key is currently pressed.
2171
+ * Uses field.buffer for zero-allocation subarray views.
2172
+ */
2173
+ keysDown: import_core24.field.buffer(import_core24.field.uint8()).size(KEY_BUFFER_SIZE),
2174
+ /**
2175
+ * Buffer for key-down triggers (true for exactly 1 frame when key is pressed).
2176
+ * Uses field.buffer for zero-allocation subarray views.
2177
+ */
2178
+ keysDownTrigger: import_core24.field.buffer(import_core24.field.uint8()).size(KEY_BUFFER_SIZE),
2179
+ /**
2180
+ * Buffer for key-up triggers (true for exactly 1 frame when key is released).
2181
+ * Uses field.buffer for zero-allocation subarray views.
2182
+ */
2183
+ keysUpTrigger: import_core24.field.buffer(import_core24.field.uint8()).size(KEY_BUFFER_SIZE),
2184
+ /** Common modifier - Shift key is down */
2185
+ shiftDown: import_core24.field.boolean().default(false),
2186
+ /** Common modifier - Alt/Option key is down */
2187
+ altDown: import_core24.field.boolean().default(false),
2188
+ /** Common modifier - Ctrl (Windows/Linux) or Cmd (Mac) is down */
2189
+ modDown: import_core24.field.boolean().default(false)
2190
+ };
2191
+ function getBit(buffer, bitIndex) {
2192
+ if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
2193
+ const byteIndex = Math.floor(bitIndex / 8);
2194
+ const bitOffset = bitIndex % 8;
2195
+ return (buffer[byteIndex] & 1 << bitOffset) !== 0;
2196
+ }
2197
+ var KeyboardDef = class extends import_canvas_store30.CanvasSingletonDef {
2198
+ constructor() {
2199
+ super({ name: "keyboard" }, KeyboardSchema);
2200
+ }
2201
+ /**
2202
+ * Check if a key is currently pressed.
2203
+ * @param ctx - Editor context
2204
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2205
+ */
2206
+ isKeyDown(ctx, key) {
2207
+ return getBit(this.read(ctx).keysDown, key);
2208
+ }
2209
+ /**
2210
+ * Check if a key was just pressed this frame.
2211
+ * @param ctx - Editor context
2212
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2213
+ */
2214
+ isKeyDownTrigger(ctx, key) {
2215
+ return getBit(this.read(ctx).keysDownTrigger, key);
2216
+ }
2217
+ /**
2218
+ * Check if a key was just released this frame.
2219
+ * @param ctx - Editor context
2220
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2221
+ */
2222
+ isKeyUpTrigger(ctx, key) {
2223
+ return getBit(this.read(ctx).keysUpTrigger, key);
2224
+ }
2225
+ /**
2226
+ * Reset all keyboard state.
2227
+ * Clears all key states and modifier flags.
2228
+ */
2229
+ reset(ctx) {
2230
+ const keyboard = this.write(ctx);
2231
+ clearBits(keyboard.keysDown);
2232
+ clearBits(keyboard.keysDownTrigger);
2233
+ clearBits(keyboard.keysUpTrigger);
2234
+ keyboard.shiftDown = false;
2235
+ keyboard.altDown = false;
2236
+ keyboard.modDown = false;
2237
+ }
2238
+ };
2239
+ var Keyboard = new KeyboardDef();
2240
+ function setBit(buffer, bitIndex, value) {
2241
+ if (bitIndex < 0 || bitIndex >= buffer.length * 8) return;
2242
+ const byteIndex = Math.floor(bitIndex / 8);
2243
+ const bitOffset = bitIndex % 8;
2244
+ if (value) {
2245
+ buffer[byteIndex] |= 1 << bitOffset;
2246
+ } else {
2247
+ buffer[byteIndex] &= ~(1 << bitOffset);
2248
+ }
2249
+ }
2250
+ function clearBits(buffer) {
2251
+ for (let i = 0; i < buffer.length; i++) {
2252
+ buffer[i] = 0;
2253
+ }
2254
+ }
2255
+ var codeToIndex = {
2256
+ // Letters (0-25)
2257
+ KeyA: 0,
2258
+ KeyB: 1,
2259
+ KeyC: 2,
2260
+ KeyD: 3,
2261
+ KeyE: 4,
2262
+ KeyF: 5,
2263
+ KeyG: 6,
2264
+ KeyH: 7,
2265
+ KeyI: 8,
2266
+ KeyJ: 9,
2267
+ KeyK: 10,
2268
+ KeyL: 11,
2269
+ KeyM: 12,
2270
+ KeyN: 13,
2271
+ KeyO: 14,
2272
+ KeyP: 15,
2273
+ KeyQ: 16,
2274
+ KeyR: 17,
2275
+ KeyS: 18,
2276
+ KeyT: 19,
2277
+ KeyU: 20,
2278
+ KeyV: 21,
2279
+ KeyW: 22,
2280
+ KeyX: 23,
2281
+ KeyY: 24,
2282
+ KeyZ: 25,
2283
+ // Numbers (26-35)
2284
+ Digit0: 26,
2285
+ Digit1: 27,
2286
+ Digit2: 28,
2287
+ Digit3: 29,
2288
+ Digit4: 30,
2289
+ Digit5: 31,
2290
+ Digit6: 32,
2291
+ Digit7: 33,
2292
+ Digit8: 34,
2293
+ Digit9: 35,
2294
+ // Function keys (36-47)
2295
+ F1: 36,
2296
+ F2: 37,
2297
+ F3: 38,
2298
+ F4: 39,
2299
+ F5: 40,
2300
+ F6: 41,
2301
+ F7: 42,
2302
+ F8: 43,
2303
+ F9: 44,
2304
+ F10: 45,
2305
+ F11: 46,
2306
+ F12: 47,
2307
+ // Modifiers (48-51)
2308
+ ShiftLeft: 48,
2309
+ ShiftRight: 49,
2310
+ ControlLeft: 50,
2311
+ ControlRight: 51,
2312
+ AltLeft: 52,
2313
+ AltRight: 53,
2314
+ MetaLeft: 54,
2315
+ MetaRight: 55,
2316
+ // Navigation (56-71)
2317
+ Escape: 56,
2318
+ Space: 57,
2319
+ Enter: 58,
2320
+ Tab: 59,
2321
+ Backspace: 60,
2322
+ Delete: 61,
2323
+ ArrowLeft: 62,
2324
+ ArrowUp: 63,
2325
+ ArrowRight: 64,
2326
+ ArrowDown: 65,
2327
+ Home: 66,
2328
+ End: 67,
2329
+ PageUp: 68,
2330
+ PageDown: 69,
2331
+ Insert: 70,
2332
+ // Punctuation (72-83)
2333
+ Semicolon: 72,
2334
+ Equal: 73,
2335
+ Comma: 74,
2336
+ Minus: 75,
2337
+ Period: 76,
2338
+ Slash: 77,
2339
+ Backquote: 78,
2340
+ BracketLeft: 79,
2341
+ Backslash: 80,
2342
+ BracketRight: 81,
2343
+ Quote: 82,
2344
+ // Numpad (84-99)
2345
+ Numpad0: 84,
2346
+ Numpad1: 85,
2347
+ Numpad2: 86,
2348
+ Numpad3: 87,
2349
+ Numpad4: 88,
2350
+ Numpad5: 89,
2351
+ Numpad6: 90,
2352
+ Numpad7: 91,
2353
+ Numpad8: 92,
2354
+ Numpad9: 93,
2355
+ NumpadAdd: 94,
2356
+ NumpadSubtract: 95,
2357
+ NumpadMultiply: 96,
2358
+ NumpadDivide: 97,
2359
+ NumpadDecimal: 98,
2360
+ NumpadEnter: 99
2361
+ };
2362
+ var Key = {
2363
+ // Letters
2364
+ A: codeToIndex.KeyA,
2365
+ B: codeToIndex.KeyB,
2366
+ C: codeToIndex.KeyC,
2367
+ D: codeToIndex.KeyD,
2368
+ E: codeToIndex.KeyE,
2369
+ F: codeToIndex.KeyF,
2370
+ G: codeToIndex.KeyG,
2371
+ H: codeToIndex.KeyH,
2372
+ I: codeToIndex.KeyI,
2373
+ J: codeToIndex.KeyJ,
2374
+ K: codeToIndex.KeyK,
2375
+ L: codeToIndex.KeyL,
2376
+ M: codeToIndex.KeyM,
2377
+ N: codeToIndex.KeyN,
2378
+ O: codeToIndex.KeyO,
2379
+ P: codeToIndex.KeyP,
2380
+ Q: codeToIndex.KeyQ,
2381
+ R: codeToIndex.KeyR,
2382
+ S: codeToIndex.KeyS,
2383
+ T: codeToIndex.KeyT,
2384
+ U: codeToIndex.KeyU,
2385
+ V: codeToIndex.KeyV,
2386
+ W: codeToIndex.KeyW,
2387
+ X: codeToIndex.KeyX,
2388
+ Y: codeToIndex.KeyY,
2389
+ Z: codeToIndex.KeyZ,
2390
+ // Numbers
2391
+ Digit0: codeToIndex.Digit0,
2392
+ Digit1: codeToIndex.Digit1,
2393
+ Digit2: codeToIndex.Digit2,
2394
+ Digit3: codeToIndex.Digit3,
2395
+ Digit4: codeToIndex.Digit4,
2396
+ Digit5: codeToIndex.Digit5,
2397
+ Digit6: codeToIndex.Digit6,
2398
+ Digit7: codeToIndex.Digit7,
2399
+ Digit8: codeToIndex.Digit8,
2400
+ Digit9: codeToIndex.Digit9,
2401
+ // Function keys
2402
+ F1: codeToIndex.F1,
2403
+ F2: codeToIndex.F2,
2404
+ F3: codeToIndex.F3,
2405
+ F4: codeToIndex.F4,
2406
+ F5: codeToIndex.F5,
2407
+ F6: codeToIndex.F6,
2408
+ F7: codeToIndex.F7,
2409
+ F8: codeToIndex.F8,
2410
+ F9: codeToIndex.F9,
2411
+ F10: codeToIndex.F10,
2412
+ F11: codeToIndex.F11,
2413
+ F12: codeToIndex.F12,
2414
+ // Modifiers
2415
+ ShiftLeft: codeToIndex.ShiftLeft,
2416
+ ShiftRight: codeToIndex.ShiftRight,
2417
+ ControlLeft: codeToIndex.ControlLeft,
2418
+ ControlRight: codeToIndex.ControlRight,
2419
+ AltLeft: codeToIndex.AltLeft,
2420
+ AltRight: codeToIndex.AltRight,
2421
+ MetaLeft: codeToIndex.MetaLeft,
2422
+ MetaRight: codeToIndex.MetaRight,
2423
+ // Navigation
2424
+ Escape: codeToIndex.Escape,
2425
+ Space: codeToIndex.Space,
2426
+ Enter: codeToIndex.Enter,
2427
+ Tab: codeToIndex.Tab,
2428
+ Backspace: codeToIndex.Backspace,
2429
+ Delete: codeToIndex.Delete,
2430
+ ArrowLeft: codeToIndex.ArrowLeft,
2431
+ ArrowUp: codeToIndex.ArrowUp,
2432
+ ArrowRight: codeToIndex.ArrowRight,
2433
+ ArrowDown: codeToIndex.ArrowDown,
2434
+ Home: codeToIndex.Home,
2435
+ End: codeToIndex.End,
2436
+ PageUp: codeToIndex.PageUp,
2437
+ PageDown: codeToIndex.PageDown,
2438
+ Insert: codeToIndex.Insert,
2439
+ // Punctuation
2440
+ Semicolon: codeToIndex.Semicolon,
2441
+ Equal: codeToIndex.Equal,
2442
+ Comma: codeToIndex.Comma,
2443
+ Minus: codeToIndex.Minus,
2444
+ Period: codeToIndex.Period,
2445
+ Slash: codeToIndex.Slash,
2446
+ Backquote: codeToIndex.Backquote,
2447
+ BracketLeft: codeToIndex.BracketLeft,
2448
+ Backslash: codeToIndex.Backslash,
2449
+ BracketRight: codeToIndex.BracketRight,
2450
+ Quote: codeToIndex.Quote,
2451
+ // Numpad
2452
+ Numpad0: codeToIndex.Numpad0,
2453
+ Numpad1: codeToIndex.Numpad1,
2454
+ Numpad2: codeToIndex.Numpad2,
2455
+ Numpad3: codeToIndex.Numpad3,
2456
+ Numpad4: codeToIndex.Numpad4,
2457
+ Numpad5: codeToIndex.Numpad5,
2458
+ Numpad6: codeToIndex.Numpad6,
2459
+ Numpad7: codeToIndex.Numpad7,
2460
+ Numpad8: codeToIndex.Numpad8,
2461
+ Numpad9: codeToIndex.Numpad9,
2462
+ NumpadAdd: codeToIndex.NumpadAdd,
2463
+ NumpadSubtract: codeToIndex.NumpadSubtract,
2464
+ NumpadMultiply: codeToIndex.NumpadMultiply,
2465
+ NumpadDivide: codeToIndex.NumpadDivide,
2466
+ NumpadDecimal: codeToIndex.NumpadDecimal,
2467
+ NumpadEnter: codeToIndex.NumpadEnter
2468
+ };
2469
+
2470
+ // src/singletons/Controls.ts
2157
2471
  var ControlsSchema = {
2158
2472
  /** Tool activated by left mouse button */
2159
- leftMouseTool: import_core24.field.string().max(32).default("select"),
2473
+ leftMouseTool: import_core25.field.string().max(32).default("select"),
2474
+ /** Tool activated by left mouse button while the space bar is held (empty string = no remap) */
2475
+ spaceLeftMouseTool: import_core25.field.string().max(32).default("hand"),
2160
2476
  /** Tool activated by middle mouse button */
2161
- middleMouseTool: import_core24.field.string().max(32).default("hand"),
2477
+ middleMouseTool: import_core25.field.string().max(32).default("hand"),
2162
2478
  /** Tool activated by right mouse button */
2163
- rightMouseTool: import_core24.field.string().max(32).default("hand"),
2479
+ rightMouseTool: import_core25.field.string().max(32).default("hand"),
2164
2480
  /** Tool activated by mouse wheel */
2165
- wheelTool: import_core24.field.string().max(32).default("scroll"),
2481
+ wheelTool: import_core25.field.string().max(32).default("scroll"),
2166
2482
  /** Tool activated by mouse wheel with modifier key held */
2167
- modWheelTool: import_core24.field.string().max(32).default("zoom"),
2483
+ modWheelTool: import_core25.field.string().max(32).default("zoom"),
2168
2484
  /** JSON snapshot of block to place on next click (empty string = no placement active) */
2169
- heldSnapshot: import_core24.field.string().max(65536).default(""),
2485
+ heldSnapshot: import_core25.field.string().max(65536).default(""),
2170
2486
  /** 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")
2487
+ activeToolName: import_core25.field.string().max(32).default("select")
2172
2488
  };
2173
- var ControlsDef = class extends import_canvas_store30.CanvasSingletonDef {
2489
+ var ControlsDef = class extends import_canvas_store31.CanvasSingletonDef {
2174
2490
  constructor() {
2175
2491
  super({ name: "controls" }, ControlsSchema);
2176
2492
  }
@@ -2183,7 +2499,9 @@ var ControlsDef = class extends import_canvas_store30.CanvasSingletonDef {
2183
2499
  getButtons(ctx, ...tools) {
2184
2500
  const controls = this.read(ctx);
2185
2501
  const buttons = [];
2186
- if (tools.includes(controls.leftMouseTool)) {
2502
+ const spaceRemap = controls.spaceLeftMouseTool !== "" && Keyboard.isKeyDown(ctx, Key.Space);
2503
+ const leftTool = spaceRemap ? controls.spaceLeftMouseTool : controls.leftMouseTool;
2504
+ if (tools.includes(leftTool)) {
2187
2505
  buttons.push(PointerButton.Left);
2188
2506
  }
2189
2507
  if (tools.includes(controls.middleMouseTool)) {
@@ -2209,19 +2527,19 @@ var ControlsDef = class extends import_canvas_store30.CanvasSingletonDef {
2209
2527
  var Controls = new ControlsDef();
2210
2528
 
2211
2529
  // src/singletons/Cursor.ts
2212
- var import_canvas_store31 = require("@woven-ecs/canvas-store");
2213
- var import_core25 = require("@woven-ecs/core");
2530
+ var import_canvas_store32 = require("@woven-ecs/canvas-store");
2531
+ var import_core26 = require("@woven-ecs/core");
2214
2532
  var CursorSchema = {
2215
2533
  /** Base cursor kind (from current tool) */
2216
- cursorKind: import_core25.field.string().max(64).default("select"),
2534
+ cursorKind: import_core26.field.string().max(64).default("select"),
2217
2535
  /** Base cursor rotation in radians */
2218
- rotation: import_core25.field.float64().default(0),
2536
+ rotation: import_core26.field.float64().default(0),
2219
2537
  /** Context-specific cursor kind (overrides cursorKind when set, e.g., during drag/hover) */
2220
- contextCursorKind: import_core25.field.string().max(64).default(""),
2538
+ contextCursorKind: import_core26.field.string().max(64).default(""),
2221
2539
  /** Context cursor rotation in radians */
2222
- contextRotation: import_core25.field.float64().default(0)
2540
+ contextRotation: import_core26.field.float64().default(0)
2223
2541
  };
2224
- var CursorDef2 = class extends import_canvas_store31.CanvasSingletonDef {
2542
+ var CursorDef2 = class extends import_canvas_store32.CanvasSingletonDef {
2225
2543
  constructor() {
2226
2544
  super({ name: "cursor" }, CursorSchema);
2227
2545
  }
@@ -2266,10 +2584,10 @@ var CursorDef2 = class extends import_canvas_store31.CanvasSingletonDef {
2266
2584
  var Cursor = new CursorDef2();
2267
2585
 
2268
2586
  // src/singletons/FrameContainmentState.ts
2269
- var import_core26 = require("@woven-ecs/core");
2587
+ var import_core27 = require("@woven-ecs/core");
2270
2588
 
2271
2589
  // src/EditorStateDef.ts
2272
- var import_canvas_store32 = require("@woven-ecs/canvas-store");
2590
+ var import_canvas_store33 = require("@woven-ecs/canvas-store");
2273
2591
 
2274
2592
  // src/machine.ts
2275
2593
  var import_xstate = require("xstate");
@@ -2297,7 +2615,7 @@ function runMachine(machine, currentState, context, events) {
2297
2615
  }
2298
2616
 
2299
2617
  // src/EditorStateDef.ts
2300
- var EditorStateDef = class extends import_canvas_store32.CanvasSingletonDef {
2618
+ var EditorStateDef = class extends import_canvas_store33.CanvasSingletonDef {
2301
2619
  constructor(name, schema) {
2302
2620
  super({ name, sync: "none" }, schema);
2303
2621
  }
@@ -2374,29 +2692,29 @@ function defineEditorState(name, schema) {
2374
2692
  // src/singletons/FrameContainmentState.ts
2375
2693
  var FrameContainmentState = defineEditorState("frameContainmentState", {
2376
2694
  /** Current state machine state */
2377
- state: import_core26.field.string().max(16).default(FrameContainmentStateEnum.Idle),
2695
+ state: import_core27.field.string().max(16).default(FrameContainmentStateEnum.Idle),
2378
2696
  /** EntityId of the frame currently highlighted as a drop target, or null */
2379
- highlightedFrame: import_core26.field.ref()
2697
+ highlightedFrame: import_core27.field.ref()
2380
2698
  });
2381
2699
 
2382
2700
  // src/singletons/Grid.ts
2383
- var import_canvas_store33 = require("@woven-ecs/canvas-store");
2384
- var import_core27 = require("@woven-ecs/core");
2701
+ var import_canvas_store34 = require("@woven-ecs/canvas-store");
2702
+ var import_core28 = require("@woven-ecs/core");
2385
2703
  var GridSchema = {
2386
2704
  /** Whether grid snapping is enabled */
2387
- enabled: import_core27.field.boolean().default(false),
2705
+ enabled: import_core28.field.boolean().default(false),
2388
2706
  /** Whether resized/rotated objects must stay aligned to the grid */
2389
- strict: import_core27.field.boolean().default(false),
2707
+ strict: import_core28.field.boolean().default(false),
2390
2708
  /** Width of each grid column in world units */
2391
- colWidth: import_core27.field.float64().default(20),
2709
+ colWidth: import_core28.field.float64().default(20),
2392
2710
  /** Height of each grid row in world units */
2393
- rowHeight: import_core27.field.float64().default(20),
2711
+ rowHeight: import_core28.field.float64().default(20),
2394
2712
  /** Angular snap increment in radians when grid is enabled */
2395
- snapAngleRad: import_core27.field.float64().default(Math.PI / 36),
2713
+ snapAngleRad: import_core28.field.float64().default(Math.PI / 36),
2396
2714
  /** Angular snap increment in radians when shift key is held */
2397
- shiftSnapAngleRad: import_core27.field.float64().default(Math.PI / 12)
2715
+ shiftSnapAngleRad: import_core28.field.float64().default(Math.PI / 12)
2398
2716
  };
2399
- var GridDef = class extends import_canvas_store33.CanvasSingletonDef {
2717
+ var GridDef = class extends import_canvas_store34.CanvasSingletonDef {
2400
2718
  constructor() {
2401
2719
  super({ name: "grid" }, GridSchema);
2402
2720
  }
@@ -2456,17 +2774,17 @@ var GridDef = class extends import_canvas_store33.CanvasSingletonDef {
2456
2774
  var Grid = new GridDef();
2457
2775
 
2458
2776
  // src/singletons/Intersect.ts
2459
- var import_canvas_store34 = require("@woven-ecs/canvas-store");
2460
- var import_core28 = require("@woven-ecs/core");
2777
+ var import_canvas_store35 = require("@woven-ecs/canvas-store");
2778
+ var import_core29 = require("@woven-ecs/core");
2461
2779
  var IntersectSchema = {
2462
2780
  // 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()
2781
+ entity1: import_core29.field.ref(),
2782
+ entity2: import_core29.field.ref(),
2783
+ entity3: import_core29.field.ref(),
2784
+ entity4: import_core29.field.ref(),
2785
+ entity5: import_core29.field.ref()
2468
2786
  };
2469
- var IntersectDef = class extends import_canvas_store34.CanvasSingletonDef {
2787
+ var IntersectDef = class extends import_canvas_store35.CanvasSingletonDef {
2470
2788
  constructor() {
2471
2789
  super({ name: "intersect" }, IntersectSchema);
2472
2790
  }
@@ -2481,344 +2799,38 @@ var IntersectDef = class extends import_canvas_store34.CanvasSingletonDef {
2481
2799
  */
2482
2800
  getAll(ctx) {
2483
2801
  const intersect = this.read(ctx);
2484
- const result = [];
2485
- if (intersect.entity1 !== null) result.push(intersect.entity1);
2486
- if (intersect.entity2 !== null) result.push(intersect.entity2);
2487
- 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);
2802
+ const result = [];
2803
+ if (intersect.entity1 !== null) result.push(intersect.entity1);
2804
+ if (intersect.entity2 !== null) result.push(intersect.entity2);
2805
+ if (intersect.entity3 !== null) result.push(intersect.entity3);
2806
+ if (intersect.entity4 !== null) result.push(intersect.entity4);
2807
+ if (intersect.entity5 !== null) result.push(intersect.entity5);
2808
+ return result;
2601
2809
  }
2602
- }
2603
- function clearBits(buffer) {
2604
- for (let i = 0; i < buffer.length; i++) {
2605
- buffer[i] = 0;
2810
+ /**
2811
+ * Set intersected entities from an array.
2812
+ */
2813
+ setAll(ctx, entities) {
2814
+ const intersect = this.write(ctx);
2815
+ intersect.entity1 = entities[0] ?? null;
2816
+ intersect.entity2 = entities[1] ?? null;
2817
+ intersect.entity3 = entities[2] ?? null;
2818
+ intersect.entity4 = entities[3] ?? null;
2819
+ intersect.entity5 = entities[4] ?? null;
2820
+ }
2821
+ /**
2822
+ * Clear all intersections.
2823
+ */
2824
+ clear(ctx) {
2825
+ const intersect = this.write(ctx);
2826
+ intersect.entity1 = null;
2827
+ intersect.entity2 = null;
2828
+ intersect.entity3 = null;
2829
+ intersect.entity4 = null;
2830
+ intersect.entity5 = null;
2606
2831
  }
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
2832
  };
2833
+ var Intersect = new IntersectDef();
2822
2834
 
2823
2835
  // src/singletons/Mouse.ts
2824
2836
  var import_canvas_store36 = require("@woven-ecs/canvas-store");
@@ -2834,6 +2846,12 @@ var MouseSchema = {
2834
2846
  moveTrigger: import_core30.field.boolean().default(false),
2835
2847
  /** True for 1 frame when wheel is scrolled */
2836
2848
  wheelTrigger: import_core30.field.boolean().default(false),
2849
+ /**
2850
+ * Modifier key (Ctrl/Cmd) was held during this frame's wheel event.
2851
+ * Also true for trackpad pinch gestures, which browsers report as
2852
+ * wheel events with ctrlKey set.
2853
+ */
2854
+ wheelModKey: import_core30.field.boolean().default(false),
2837
2855
  /** True for 1 frame when mouse enters the editor element */
2838
2856
  enterTrigger: import_core30.field.boolean().default(false),
2839
2857
  /** True for 1 frame when mouse leaves the editor element */
@@ -3053,7 +3071,8 @@ function getMouseInput(ctx) {
3053
3071
  screenPosition: screenPos,
3054
3072
  worldPosition: worldPos,
3055
3073
  wheelDeltaX: mouse.wheelDeltaX,
3056
- wheelDeltaY: mouse.wheelDeltaY
3074
+ wheelDeltaY: mouse.wheelDeltaY,
3075
+ wheelModKey: mouse.wheelModKey
3057
3076
  });
3058
3077
  }
3059
3078
  if (mouse.moveTrigger) {
@@ -3063,7 +3082,8 @@ function getMouseInput(ctx) {
3063
3082
  screenPosition: screenPos,
3064
3083
  worldPosition: worldPos,
3065
3084
  wheelDeltaX: 0,
3066
- wheelDeltaY: 0
3085
+ wheelDeltaY: 0,
3086
+ wheelModKey: false
3067
3087
  });
3068
3088
  }
3069
3089
  return events;
@@ -4382,13 +4402,16 @@ var keyboardSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
4382
4402
  if (keyIndex === void 0) continue;
4383
4403
  if (event.type === "keydown") {
4384
4404
  const wasDown = getBit2(keyboard.keysDown, keyIndex);
4385
- if (!wasDown) {
4405
+ if (!wasDown || !event.repeat) {
4386
4406
  setBit(keyboard.keysDownTrigger, keyIndex, true);
4387
4407
  }
4388
4408
  setBit(keyboard.keysDown, keyIndex, true);
4389
4409
  } else if (event.type === "keyup") {
4390
4410
  setBit(keyboard.keysDown, keyIndex, false);
4391
4411
  setBit(keyboard.keysUpTrigger, keyIndex, true);
4412
+ if (event.code === "MetaLeft" || event.code === "MetaRight") {
4413
+ releaseNonModifierKeys(keyboard);
4414
+ }
4392
4415
  }
4393
4416
  keyboard.shiftDown = event.shiftKey;
4394
4417
  keyboard.altDown = event.altKey;
@@ -4396,6 +4419,25 @@ var keyboardSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
4396
4419
  }
4397
4420
  state.eventsBuffer.length = 0;
4398
4421
  });
4422
+ var MODIFIER_KEY_INDICES = /* @__PURE__ */ new Set([
4423
+ codeToIndex.ShiftLeft,
4424
+ codeToIndex.ShiftRight,
4425
+ codeToIndex.ControlLeft,
4426
+ codeToIndex.ControlRight,
4427
+ codeToIndex.AltLeft,
4428
+ codeToIndex.AltRight,
4429
+ codeToIndex.MetaLeft,
4430
+ codeToIndex.MetaRight
4431
+ ]);
4432
+ function releaseNonModifierKeys(keyboard) {
4433
+ const bitCount = keyboard.keysDown.length * 8;
4434
+ for (let i = 0; i < bitCount; i++) {
4435
+ if (MODIFIER_KEY_INDICES.has(i)) continue;
4436
+ if (!getBit2(keyboard.keysDown, i)) continue;
4437
+ setBit(keyboard.keysDown, i, false);
4438
+ setBit(keyboard.keysUpTrigger, i, true);
4439
+ }
4440
+ }
4399
4441
  function getBit2(buffer, bitIndex) {
4400
4442
  if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
4401
4443
  const byteIndex = Math.floor(bitIndex / 8);
@@ -4432,7 +4474,10 @@ function attachMouseListeners(domElement) {
4432
4474
  clientY: e.clientY,
4433
4475
  deltaX: e.deltaX,
4434
4476
  deltaY: e.deltaY,
4435
- deltaMode: e.deltaMode
4477
+ deltaMode: e.deltaMode,
4478
+ // Trackpad pinch gestures arrive as wheel events with ctrlKey set,
4479
+ // without any keydown — capture the flags from the event itself.
4480
+ modKey: e.ctrlKey || e.metaKey
4436
4481
  });
4437
4482
  },
4438
4483
  onMouseEnter: () => {
@@ -4474,6 +4519,7 @@ var mouseSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
4474
4519
  mouse.leaveTrigger = false;
4475
4520
  mouse.wheelDeltaX = 0;
4476
4521
  mouse.wheelDeltaY = 0;
4522
+ mouse.wheelModKey = false;
4477
4523
  for (const event of state.eventsBuffer) {
4478
4524
  switch (event.type) {
4479
4525
  case "mousemove":
@@ -4485,6 +4531,7 @@ var mouseSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
4485
4531
  mouse.wheelDeltaX = event.deltaX;
4486
4532
  mouse.wheelDeltaY = normalizeWheelDelta(event.deltaY, event.deltaMode);
4487
4533
  mouse.wheelTrigger = true;
4534
+ mouse.wheelModKey = event.modKey === true;
4488
4535
  break;
4489
4536
  case "mouseenter":
4490
4537
  mouse.enterTrigger = true;
@@ -5712,6 +5759,9 @@ var selectSystem = defineEditorSystem({ phase: "capture", priority: 100 }, (ctx)
5712
5759
  buttons = ["left"];
5713
5760
  }
5714
5761
  const events = getPointerInput(ctx, buttons);
5762
+ if (buttons.length === 0 && (state.state === SelectionState.SelectionBoxPointing || state.state === SelectionState.SelectionBoxDragging)) {
5763
+ events.push({ type: "cancel", ctx });
5764
+ }
5715
5765
  if (events.length === 0) return;
5716
5766
  SelectionStateSingleton.run(ctx, selectionMachine, events);
5717
5767
  });
@@ -6599,6 +6649,11 @@ function createCorePlugin(options = {}) {
6599
6649
  command: RemoveSelected.name,
6600
6650
  key: Key.Delete
6601
6651
  },
6652
+ {
6653
+ // Mac keyboards' delete key reports code "Backspace"
6654
+ command: RemoveSelected.name,
6655
+ key: Key.Backspace
6656
+ },
6602
6657
  {
6603
6658
  command: SelectAll.name,
6604
6659
  key: Key.A,