@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.js CHANGED
@@ -278,6 +278,12 @@ var ControlsOptions = z.object({
278
278
  * @default 'select'
279
279
  */
280
280
  leftMouseTool: z.string().max(32).default("select"),
281
+ /**
282
+ * Tool activated by left mouse button while the space bar is held.
283
+ * Set to an empty string to disable the remap.
284
+ * @default 'hand'
285
+ */
286
+ spaceLeftMouseTool: z.string().max(32).default("hand"),
281
287
  /**
282
288
  * Tool activated by middle mouse button.
283
289
  * @default 'hand'
@@ -1994,25 +2000,335 @@ var CameraDef = class extends CanvasSingletonDef2 {
1994
2000
  var Camera = new CameraDef();
1995
2001
 
1996
2002
  // src/singletons/Controls.ts
2003
+ import { CanvasSingletonDef as CanvasSingletonDef4 } from "@woven-ecs/canvas-store";
2004
+ import { field as field25 } from "@woven-ecs/core";
2005
+
2006
+ // src/singletons/Keyboard.ts
1997
2007
  import { CanvasSingletonDef as CanvasSingletonDef3 } from "@woven-ecs/canvas-store";
1998
2008
  import { field as field24 } from "@woven-ecs/core";
2009
+ var KEY_BUFFER_SIZE = 32;
2010
+ var KeyboardSchema = {
2011
+ /**
2012
+ * Buffer where each bit represents whether a key is currently pressed.
2013
+ * Uses field.buffer for zero-allocation subarray views.
2014
+ */
2015
+ keysDown: field24.buffer(field24.uint8()).size(KEY_BUFFER_SIZE),
2016
+ /**
2017
+ * Buffer for key-down triggers (true for exactly 1 frame when key is pressed).
2018
+ * Uses field.buffer for zero-allocation subarray views.
2019
+ */
2020
+ keysDownTrigger: field24.buffer(field24.uint8()).size(KEY_BUFFER_SIZE),
2021
+ /**
2022
+ * Buffer for key-up triggers (true for exactly 1 frame when key is released).
2023
+ * Uses field.buffer for zero-allocation subarray views.
2024
+ */
2025
+ keysUpTrigger: field24.buffer(field24.uint8()).size(KEY_BUFFER_SIZE),
2026
+ /** Common modifier - Shift key is down */
2027
+ shiftDown: field24.boolean().default(false),
2028
+ /** Common modifier - Alt/Option key is down */
2029
+ altDown: field24.boolean().default(false),
2030
+ /** Common modifier - Ctrl (Windows/Linux) or Cmd (Mac) is down */
2031
+ modDown: field24.boolean().default(false)
2032
+ };
2033
+ function getBit(buffer, bitIndex) {
2034
+ if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
2035
+ const byteIndex = Math.floor(bitIndex / 8);
2036
+ const bitOffset = bitIndex % 8;
2037
+ return (buffer[byteIndex] & 1 << bitOffset) !== 0;
2038
+ }
2039
+ var KeyboardDef = class extends CanvasSingletonDef3 {
2040
+ constructor() {
2041
+ super({ name: "keyboard" }, KeyboardSchema);
2042
+ }
2043
+ /**
2044
+ * Check if a key is currently pressed.
2045
+ * @param ctx - Editor context
2046
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2047
+ */
2048
+ isKeyDown(ctx, key) {
2049
+ return getBit(this.read(ctx).keysDown, key);
2050
+ }
2051
+ /**
2052
+ * Check if a key was just pressed this frame.
2053
+ * @param ctx - Editor context
2054
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2055
+ */
2056
+ isKeyDownTrigger(ctx, key) {
2057
+ return getBit(this.read(ctx).keysDownTrigger, key);
2058
+ }
2059
+ /**
2060
+ * Check if a key was just released this frame.
2061
+ * @param ctx - Editor context
2062
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2063
+ */
2064
+ isKeyUpTrigger(ctx, key) {
2065
+ return getBit(this.read(ctx).keysUpTrigger, key);
2066
+ }
2067
+ /**
2068
+ * Reset all keyboard state.
2069
+ * Clears all key states and modifier flags.
2070
+ */
2071
+ reset(ctx) {
2072
+ const keyboard = this.write(ctx);
2073
+ clearBits(keyboard.keysDown);
2074
+ clearBits(keyboard.keysDownTrigger);
2075
+ clearBits(keyboard.keysUpTrigger);
2076
+ keyboard.shiftDown = false;
2077
+ keyboard.altDown = false;
2078
+ keyboard.modDown = false;
2079
+ }
2080
+ };
2081
+ var Keyboard = new KeyboardDef();
2082
+ function setBit(buffer, bitIndex, value) {
2083
+ if (bitIndex < 0 || bitIndex >= buffer.length * 8) return;
2084
+ const byteIndex = Math.floor(bitIndex / 8);
2085
+ const bitOffset = bitIndex % 8;
2086
+ if (value) {
2087
+ buffer[byteIndex] |= 1 << bitOffset;
2088
+ } else {
2089
+ buffer[byteIndex] &= ~(1 << bitOffset);
2090
+ }
2091
+ }
2092
+ function clearBits(buffer) {
2093
+ for (let i = 0; i < buffer.length; i++) {
2094
+ buffer[i] = 0;
2095
+ }
2096
+ }
2097
+ var codeToIndex = {
2098
+ // Letters (0-25)
2099
+ KeyA: 0,
2100
+ KeyB: 1,
2101
+ KeyC: 2,
2102
+ KeyD: 3,
2103
+ KeyE: 4,
2104
+ KeyF: 5,
2105
+ KeyG: 6,
2106
+ KeyH: 7,
2107
+ KeyI: 8,
2108
+ KeyJ: 9,
2109
+ KeyK: 10,
2110
+ KeyL: 11,
2111
+ KeyM: 12,
2112
+ KeyN: 13,
2113
+ KeyO: 14,
2114
+ KeyP: 15,
2115
+ KeyQ: 16,
2116
+ KeyR: 17,
2117
+ KeyS: 18,
2118
+ KeyT: 19,
2119
+ KeyU: 20,
2120
+ KeyV: 21,
2121
+ KeyW: 22,
2122
+ KeyX: 23,
2123
+ KeyY: 24,
2124
+ KeyZ: 25,
2125
+ // Numbers (26-35)
2126
+ Digit0: 26,
2127
+ Digit1: 27,
2128
+ Digit2: 28,
2129
+ Digit3: 29,
2130
+ Digit4: 30,
2131
+ Digit5: 31,
2132
+ Digit6: 32,
2133
+ Digit7: 33,
2134
+ Digit8: 34,
2135
+ Digit9: 35,
2136
+ // Function keys (36-47)
2137
+ F1: 36,
2138
+ F2: 37,
2139
+ F3: 38,
2140
+ F4: 39,
2141
+ F5: 40,
2142
+ F6: 41,
2143
+ F7: 42,
2144
+ F8: 43,
2145
+ F9: 44,
2146
+ F10: 45,
2147
+ F11: 46,
2148
+ F12: 47,
2149
+ // Modifiers (48-51)
2150
+ ShiftLeft: 48,
2151
+ ShiftRight: 49,
2152
+ ControlLeft: 50,
2153
+ ControlRight: 51,
2154
+ AltLeft: 52,
2155
+ AltRight: 53,
2156
+ MetaLeft: 54,
2157
+ MetaRight: 55,
2158
+ // Navigation (56-71)
2159
+ Escape: 56,
2160
+ Space: 57,
2161
+ Enter: 58,
2162
+ Tab: 59,
2163
+ Backspace: 60,
2164
+ Delete: 61,
2165
+ ArrowLeft: 62,
2166
+ ArrowUp: 63,
2167
+ ArrowRight: 64,
2168
+ ArrowDown: 65,
2169
+ Home: 66,
2170
+ End: 67,
2171
+ PageUp: 68,
2172
+ PageDown: 69,
2173
+ Insert: 70,
2174
+ // Punctuation (72-83)
2175
+ Semicolon: 72,
2176
+ Equal: 73,
2177
+ Comma: 74,
2178
+ Minus: 75,
2179
+ Period: 76,
2180
+ Slash: 77,
2181
+ Backquote: 78,
2182
+ BracketLeft: 79,
2183
+ Backslash: 80,
2184
+ BracketRight: 81,
2185
+ Quote: 82,
2186
+ // Numpad (84-99)
2187
+ Numpad0: 84,
2188
+ Numpad1: 85,
2189
+ Numpad2: 86,
2190
+ Numpad3: 87,
2191
+ Numpad4: 88,
2192
+ Numpad5: 89,
2193
+ Numpad6: 90,
2194
+ Numpad7: 91,
2195
+ Numpad8: 92,
2196
+ Numpad9: 93,
2197
+ NumpadAdd: 94,
2198
+ NumpadSubtract: 95,
2199
+ NumpadMultiply: 96,
2200
+ NumpadDivide: 97,
2201
+ NumpadDecimal: 98,
2202
+ NumpadEnter: 99
2203
+ };
2204
+ var Key = {
2205
+ // Letters
2206
+ A: codeToIndex.KeyA,
2207
+ B: codeToIndex.KeyB,
2208
+ C: codeToIndex.KeyC,
2209
+ D: codeToIndex.KeyD,
2210
+ E: codeToIndex.KeyE,
2211
+ F: codeToIndex.KeyF,
2212
+ G: codeToIndex.KeyG,
2213
+ H: codeToIndex.KeyH,
2214
+ I: codeToIndex.KeyI,
2215
+ J: codeToIndex.KeyJ,
2216
+ K: codeToIndex.KeyK,
2217
+ L: codeToIndex.KeyL,
2218
+ M: codeToIndex.KeyM,
2219
+ N: codeToIndex.KeyN,
2220
+ O: codeToIndex.KeyO,
2221
+ P: codeToIndex.KeyP,
2222
+ Q: codeToIndex.KeyQ,
2223
+ R: codeToIndex.KeyR,
2224
+ S: codeToIndex.KeyS,
2225
+ T: codeToIndex.KeyT,
2226
+ U: codeToIndex.KeyU,
2227
+ V: codeToIndex.KeyV,
2228
+ W: codeToIndex.KeyW,
2229
+ X: codeToIndex.KeyX,
2230
+ Y: codeToIndex.KeyY,
2231
+ Z: codeToIndex.KeyZ,
2232
+ // Numbers
2233
+ Digit0: codeToIndex.Digit0,
2234
+ Digit1: codeToIndex.Digit1,
2235
+ Digit2: codeToIndex.Digit2,
2236
+ Digit3: codeToIndex.Digit3,
2237
+ Digit4: codeToIndex.Digit4,
2238
+ Digit5: codeToIndex.Digit5,
2239
+ Digit6: codeToIndex.Digit6,
2240
+ Digit7: codeToIndex.Digit7,
2241
+ Digit8: codeToIndex.Digit8,
2242
+ Digit9: codeToIndex.Digit9,
2243
+ // Function keys
2244
+ F1: codeToIndex.F1,
2245
+ F2: codeToIndex.F2,
2246
+ F3: codeToIndex.F3,
2247
+ F4: codeToIndex.F4,
2248
+ F5: codeToIndex.F5,
2249
+ F6: codeToIndex.F6,
2250
+ F7: codeToIndex.F7,
2251
+ F8: codeToIndex.F8,
2252
+ F9: codeToIndex.F9,
2253
+ F10: codeToIndex.F10,
2254
+ F11: codeToIndex.F11,
2255
+ F12: codeToIndex.F12,
2256
+ // Modifiers
2257
+ ShiftLeft: codeToIndex.ShiftLeft,
2258
+ ShiftRight: codeToIndex.ShiftRight,
2259
+ ControlLeft: codeToIndex.ControlLeft,
2260
+ ControlRight: codeToIndex.ControlRight,
2261
+ AltLeft: codeToIndex.AltLeft,
2262
+ AltRight: codeToIndex.AltRight,
2263
+ MetaLeft: codeToIndex.MetaLeft,
2264
+ MetaRight: codeToIndex.MetaRight,
2265
+ // Navigation
2266
+ Escape: codeToIndex.Escape,
2267
+ Space: codeToIndex.Space,
2268
+ Enter: codeToIndex.Enter,
2269
+ Tab: codeToIndex.Tab,
2270
+ Backspace: codeToIndex.Backspace,
2271
+ Delete: codeToIndex.Delete,
2272
+ ArrowLeft: codeToIndex.ArrowLeft,
2273
+ ArrowUp: codeToIndex.ArrowUp,
2274
+ ArrowRight: codeToIndex.ArrowRight,
2275
+ ArrowDown: codeToIndex.ArrowDown,
2276
+ Home: codeToIndex.Home,
2277
+ End: codeToIndex.End,
2278
+ PageUp: codeToIndex.PageUp,
2279
+ PageDown: codeToIndex.PageDown,
2280
+ Insert: codeToIndex.Insert,
2281
+ // Punctuation
2282
+ Semicolon: codeToIndex.Semicolon,
2283
+ Equal: codeToIndex.Equal,
2284
+ Comma: codeToIndex.Comma,
2285
+ Minus: codeToIndex.Minus,
2286
+ Period: codeToIndex.Period,
2287
+ Slash: codeToIndex.Slash,
2288
+ Backquote: codeToIndex.Backquote,
2289
+ BracketLeft: codeToIndex.BracketLeft,
2290
+ Backslash: codeToIndex.Backslash,
2291
+ BracketRight: codeToIndex.BracketRight,
2292
+ Quote: codeToIndex.Quote,
2293
+ // Numpad
2294
+ Numpad0: codeToIndex.Numpad0,
2295
+ Numpad1: codeToIndex.Numpad1,
2296
+ Numpad2: codeToIndex.Numpad2,
2297
+ Numpad3: codeToIndex.Numpad3,
2298
+ Numpad4: codeToIndex.Numpad4,
2299
+ Numpad5: codeToIndex.Numpad5,
2300
+ Numpad6: codeToIndex.Numpad6,
2301
+ Numpad7: codeToIndex.Numpad7,
2302
+ Numpad8: codeToIndex.Numpad8,
2303
+ Numpad9: codeToIndex.Numpad9,
2304
+ NumpadAdd: codeToIndex.NumpadAdd,
2305
+ NumpadSubtract: codeToIndex.NumpadSubtract,
2306
+ NumpadMultiply: codeToIndex.NumpadMultiply,
2307
+ NumpadDivide: codeToIndex.NumpadDivide,
2308
+ NumpadDecimal: codeToIndex.NumpadDecimal,
2309
+ NumpadEnter: codeToIndex.NumpadEnter
2310
+ };
2311
+
2312
+ // src/singletons/Controls.ts
1999
2313
  var ControlsSchema = {
2000
2314
  /** Tool activated by left mouse button */
2001
- leftMouseTool: field24.string().max(32).default("select"),
2315
+ leftMouseTool: field25.string().max(32).default("select"),
2316
+ /** Tool activated by left mouse button while the space bar is held (empty string = no remap) */
2317
+ spaceLeftMouseTool: field25.string().max(32).default("hand"),
2002
2318
  /** Tool activated by middle mouse button */
2003
- middleMouseTool: field24.string().max(32).default("hand"),
2319
+ middleMouseTool: field25.string().max(32).default("hand"),
2004
2320
  /** Tool activated by right mouse button */
2005
- rightMouseTool: field24.string().max(32).default("hand"),
2321
+ rightMouseTool: field25.string().max(32).default("hand"),
2006
2322
  /** Tool activated by mouse wheel */
2007
- wheelTool: field24.string().max(32).default("scroll"),
2323
+ wheelTool: field25.string().max(32).default("scroll"),
2008
2324
  /** Tool activated by mouse wheel with modifier key held */
2009
- modWheelTool: field24.string().max(32).default("zoom"),
2325
+ modWheelTool: field25.string().max(32).default("zoom"),
2010
2326
  /** JSON snapshot of block to place on next click (empty string = no placement active) */
2011
- heldSnapshot: field24.string().max(65536).default(""),
2327
+ heldSnapshot: field25.string().max(65536).default(""),
2012
2328
  /** User-facing tool name for UI highlighting (may differ from leftMouseTool during draw/drag-out) */
2013
- activeToolName: field24.string().max(32).default("select")
2329
+ activeToolName: field25.string().max(32).default("select")
2014
2330
  };
2015
- var ControlsDef = class extends CanvasSingletonDef3 {
2331
+ var ControlsDef = class extends CanvasSingletonDef4 {
2016
2332
  constructor() {
2017
2333
  super({ name: "controls" }, ControlsSchema);
2018
2334
  }
@@ -2025,7 +2341,9 @@ var ControlsDef = class extends CanvasSingletonDef3 {
2025
2341
  getButtons(ctx, ...tools) {
2026
2342
  const controls = this.read(ctx);
2027
2343
  const buttons = [];
2028
- if (tools.includes(controls.leftMouseTool)) {
2344
+ const spaceRemap = controls.spaceLeftMouseTool !== "" && Keyboard.isKeyDown(ctx, Key.Space);
2345
+ const leftTool = spaceRemap ? controls.spaceLeftMouseTool : controls.leftMouseTool;
2346
+ if (tools.includes(leftTool)) {
2029
2347
  buttons.push(PointerButton.Left);
2030
2348
  }
2031
2349
  if (tools.includes(controls.middleMouseTool)) {
@@ -2051,19 +2369,19 @@ var ControlsDef = class extends CanvasSingletonDef3 {
2051
2369
  var Controls = new ControlsDef();
2052
2370
 
2053
2371
  // src/singletons/Cursor.ts
2054
- import { CanvasSingletonDef as CanvasSingletonDef4 } from "@woven-ecs/canvas-store";
2055
- import { field as field25 } from "@woven-ecs/core";
2372
+ import { CanvasSingletonDef as CanvasSingletonDef5 } from "@woven-ecs/canvas-store";
2373
+ import { field as field26 } from "@woven-ecs/core";
2056
2374
  var CursorSchema = {
2057
2375
  /** Base cursor kind (from current tool) */
2058
- cursorKind: field25.string().max(64).default("select"),
2376
+ cursorKind: field26.string().max(64).default("select"),
2059
2377
  /** Base cursor rotation in radians */
2060
- rotation: field25.float64().default(0),
2378
+ rotation: field26.float64().default(0),
2061
2379
  /** Context-specific cursor kind (overrides cursorKind when set, e.g., during drag/hover) */
2062
- contextCursorKind: field25.string().max(64).default(""),
2380
+ contextCursorKind: field26.string().max(64).default(""),
2063
2381
  /** Context cursor rotation in radians */
2064
- contextRotation: field25.float64().default(0)
2382
+ contextRotation: field26.float64().default(0)
2065
2383
  };
2066
- var CursorDef2 = class extends CanvasSingletonDef4 {
2384
+ var CursorDef2 = class extends CanvasSingletonDef5 {
2067
2385
  constructor() {
2068
2386
  super({ name: "cursor" }, CursorSchema);
2069
2387
  }
@@ -2108,10 +2426,10 @@ var CursorDef2 = class extends CanvasSingletonDef4 {
2108
2426
  var Cursor = new CursorDef2();
2109
2427
 
2110
2428
  // src/singletons/FrameContainmentState.ts
2111
- import { field as field26 } from "@woven-ecs/core";
2429
+ import { field as field27 } from "@woven-ecs/core";
2112
2430
 
2113
2431
  // src/EditorStateDef.ts
2114
- import { CanvasSingletonDef as CanvasSingletonDef5 } from "@woven-ecs/canvas-store";
2432
+ import { CanvasSingletonDef as CanvasSingletonDef6 } from "@woven-ecs/canvas-store";
2115
2433
 
2116
2434
  // src/machine.ts
2117
2435
  import { transition } from "xstate";
@@ -2139,7 +2457,7 @@ function runMachine(machine, currentState, context, events) {
2139
2457
  }
2140
2458
 
2141
2459
  // src/EditorStateDef.ts
2142
- var EditorStateDef = class extends CanvasSingletonDef5 {
2460
+ var EditorStateDef = class extends CanvasSingletonDef6 {
2143
2461
  constructor(name, schema) {
2144
2462
  super({ name, sync: "none" }, schema);
2145
2463
  }
@@ -2216,29 +2534,29 @@ function defineEditorState(name, schema) {
2216
2534
  // src/singletons/FrameContainmentState.ts
2217
2535
  var FrameContainmentState = defineEditorState("frameContainmentState", {
2218
2536
  /** Current state machine state */
2219
- state: field26.string().max(16).default(FrameContainmentStateEnum.Idle),
2537
+ state: field27.string().max(16).default(FrameContainmentStateEnum.Idle),
2220
2538
  /** EntityId of the frame currently highlighted as a drop target, or null */
2221
- highlightedFrame: field26.ref()
2539
+ highlightedFrame: field27.ref()
2222
2540
  });
2223
2541
 
2224
2542
  // src/singletons/Grid.ts
2225
- import { CanvasSingletonDef as CanvasSingletonDef6 } from "@woven-ecs/canvas-store";
2226
- import { field as field27 } from "@woven-ecs/core";
2543
+ import { CanvasSingletonDef as CanvasSingletonDef7 } from "@woven-ecs/canvas-store";
2544
+ import { field as field28 } from "@woven-ecs/core";
2227
2545
  var GridSchema = {
2228
2546
  /** Whether grid snapping is enabled */
2229
- enabled: field27.boolean().default(false),
2547
+ enabled: field28.boolean().default(false),
2230
2548
  /** Whether resized/rotated objects must stay aligned to the grid */
2231
- strict: field27.boolean().default(false),
2549
+ strict: field28.boolean().default(false),
2232
2550
  /** Width of each grid column in world units */
2233
- colWidth: field27.float64().default(20),
2551
+ colWidth: field28.float64().default(20),
2234
2552
  /** Height of each grid row in world units */
2235
- rowHeight: field27.float64().default(20),
2553
+ rowHeight: field28.float64().default(20),
2236
2554
  /** Angular snap increment in radians when grid is enabled */
2237
- snapAngleRad: field27.float64().default(Math.PI / 36),
2555
+ snapAngleRad: field28.float64().default(Math.PI / 36),
2238
2556
  /** Angular snap increment in radians when shift key is held */
2239
- shiftSnapAngleRad: field27.float64().default(Math.PI / 12)
2557
+ shiftSnapAngleRad: field28.float64().default(Math.PI / 12)
2240
2558
  };
2241
- var GridDef = class extends CanvasSingletonDef6 {
2559
+ var GridDef = class extends CanvasSingletonDef7 {
2242
2560
  constructor() {
2243
2561
  super({ name: "grid" }, GridSchema);
2244
2562
  }
@@ -2298,17 +2616,17 @@ var GridDef = class extends CanvasSingletonDef6 {
2298
2616
  var Grid = new GridDef();
2299
2617
 
2300
2618
  // src/singletons/Intersect.ts
2301
- import { CanvasSingletonDef as CanvasSingletonDef7 } from "@woven-ecs/canvas-store";
2302
- import { field as field28 } from "@woven-ecs/core";
2619
+ import { CanvasSingletonDef as CanvasSingletonDef8 } from "@woven-ecs/canvas-store";
2620
+ import { field as field29 } from "@woven-ecs/core";
2303
2621
  var IntersectSchema = {
2304
2622
  // Store up to 5 intersected entity IDs
2305
- entity1: field28.ref(),
2306
- entity2: field28.ref(),
2307
- entity3: field28.ref(),
2308
- entity4: field28.ref(),
2309
- entity5: field28.ref()
2623
+ entity1: field29.ref(),
2624
+ entity2: field29.ref(),
2625
+ entity3: field29.ref(),
2626
+ entity4: field29.ref(),
2627
+ entity5: field29.ref()
2310
2628
  };
2311
- var IntersectDef = class extends CanvasSingletonDef7 {
2629
+ var IntersectDef = class extends CanvasSingletonDef8 {
2312
2630
  constructor() {
2313
2631
  super({ name: "intersect" }, IntersectSchema);
2314
2632
  }
@@ -2323,344 +2641,38 @@ var IntersectDef = class extends CanvasSingletonDef7 {
2323
2641
  */
2324
2642
  getAll(ctx) {
2325
2643
  const intersect = this.read(ctx);
2326
- const result = [];
2327
- if (intersect.entity1 !== null) result.push(intersect.entity1);
2328
- if (intersect.entity2 !== null) result.push(intersect.entity2);
2329
- if (intersect.entity3 !== null) result.push(intersect.entity3);
2330
- if (intersect.entity4 !== null) result.push(intersect.entity4);
2331
- if (intersect.entity5 !== null) result.push(intersect.entity5);
2332
- return result;
2333
- }
2334
- /**
2335
- * Set intersected entities from an array.
2336
- */
2337
- setAll(ctx, entities) {
2338
- const intersect = this.write(ctx);
2339
- intersect.entity1 = entities[0] ?? null;
2340
- intersect.entity2 = entities[1] ?? null;
2341
- intersect.entity3 = entities[2] ?? null;
2342
- intersect.entity4 = entities[3] ?? null;
2343
- intersect.entity5 = entities[4] ?? null;
2344
- }
2345
- /**
2346
- * Clear all intersections.
2347
- */
2348
- clear(ctx) {
2349
- const intersect = this.write(ctx);
2350
- intersect.entity1 = null;
2351
- intersect.entity2 = null;
2352
- intersect.entity3 = null;
2353
- intersect.entity4 = null;
2354
- intersect.entity5 = null;
2355
- }
2356
- };
2357
- var Intersect = new IntersectDef();
2358
-
2359
- // src/singletons/Keyboard.ts
2360
- import { CanvasSingletonDef as CanvasSingletonDef8 } from "@woven-ecs/canvas-store";
2361
- import { field as field29 } from "@woven-ecs/core";
2362
- var KEY_BUFFER_SIZE = 32;
2363
- var KeyboardSchema = {
2364
- /**
2365
- * Buffer where each bit represents whether a key is currently pressed.
2366
- * Uses field.buffer for zero-allocation subarray views.
2367
- */
2368
- keysDown: field29.buffer(field29.uint8()).size(KEY_BUFFER_SIZE),
2369
- /**
2370
- * Buffer for key-down triggers (true for exactly 1 frame when key is pressed).
2371
- * Uses field.buffer for zero-allocation subarray views.
2372
- */
2373
- keysDownTrigger: field29.buffer(field29.uint8()).size(KEY_BUFFER_SIZE),
2374
- /**
2375
- * Buffer for key-up triggers (true for exactly 1 frame when key is released).
2376
- * Uses field.buffer for zero-allocation subarray views.
2377
- */
2378
- keysUpTrigger: field29.buffer(field29.uint8()).size(KEY_BUFFER_SIZE),
2379
- /** Common modifier - Shift key is down */
2380
- shiftDown: field29.boolean().default(false),
2381
- /** Common modifier - Alt/Option key is down */
2382
- altDown: field29.boolean().default(false),
2383
- /** Common modifier - Ctrl (Windows/Linux) or Cmd (Mac) is down */
2384
- modDown: field29.boolean().default(false)
2385
- };
2386
- function getBit(buffer, bitIndex) {
2387
- if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
2388
- const byteIndex = Math.floor(bitIndex / 8);
2389
- const bitOffset = bitIndex % 8;
2390
- return (buffer[byteIndex] & 1 << bitOffset) !== 0;
2391
- }
2392
- var KeyboardDef = class extends CanvasSingletonDef8 {
2393
- constructor() {
2394
- super({ name: "keyboard" }, KeyboardSchema);
2395
- }
2396
- /**
2397
- * Check if a key is currently pressed.
2398
- * @param ctx - Editor context
2399
- * @param key - The key index to check (use Key.A, Key.Space, etc.)
2400
- */
2401
- isKeyDown(ctx, key) {
2402
- return getBit(this.read(ctx).keysDown, key);
2403
- }
2404
- /**
2405
- * Check if a key was just pressed this frame.
2406
- * @param ctx - Editor context
2407
- * @param key - The key index to check (use Key.A, Key.Space, etc.)
2408
- */
2409
- isKeyDownTrigger(ctx, key) {
2410
- return getBit(this.read(ctx).keysDownTrigger, key);
2411
- }
2412
- /**
2413
- * Check if a key was just released this frame.
2414
- * @param ctx - Editor context
2415
- * @param key - The key index to check (use Key.A, Key.Space, etc.)
2416
- */
2417
- isKeyUpTrigger(ctx, key) {
2418
- return getBit(this.read(ctx).keysUpTrigger, key);
2419
- }
2420
- /**
2421
- * Reset all keyboard state.
2422
- * Clears all key states and modifier flags.
2423
- */
2424
- reset(ctx) {
2425
- const keyboard = this.write(ctx);
2426
- clearBits(keyboard.keysDown);
2427
- clearBits(keyboard.keysDownTrigger);
2428
- clearBits(keyboard.keysUpTrigger);
2429
- keyboard.shiftDown = false;
2430
- keyboard.altDown = false;
2431
- keyboard.modDown = false;
2432
- }
2433
- };
2434
- var Keyboard = new KeyboardDef();
2435
- function setBit(buffer, bitIndex, value) {
2436
- if (bitIndex < 0 || bitIndex >= buffer.length * 8) return;
2437
- const byteIndex = Math.floor(bitIndex / 8);
2438
- const bitOffset = bitIndex % 8;
2439
- if (value) {
2440
- buffer[byteIndex] |= 1 << bitOffset;
2441
- } else {
2442
- buffer[byteIndex] &= ~(1 << bitOffset);
2644
+ const result = [];
2645
+ if (intersect.entity1 !== null) result.push(intersect.entity1);
2646
+ if (intersect.entity2 !== null) result.push(intersect.entity2);
2647
+ if (intersect.entity3 !== null) result.push(intersect.entity3);
2648
+ if (intersect.entity4 !== null) result.push(intersect.entity4);
2649
+ if (intersect.entity5 !== null) result.push(intersect.entity5);
2650
+ return result;
2443
2651
  }
2444
- }
2445
- function clearBits(buffer) {
2446
- for (let i = 0; i < buffer.length; i++) {
2447
- buffer[i] = 0;
2652
+ /**
2653
+ * Set intersected entities from an array.
2654
+ */
2655
+ setAll(ctx, entities) {
2656
+ const intersect = this.write(ctx);
2657
+ intersect.entity1 = entities[0] ?? null;
2658
+ intersect.entity2 = entities[1] ?? null;
2659
+ intersect.entity3 = entities[2] ?? null;
2660
+ intersect.entity4 = entities[3] ?? null;
2661
+ intersect.entity5 = entities[4] ?? null;
2662
+ }
2663
+ /**
2664
+ * Clear all intersections.
2665
+ */
2666
+ clear(ctx) {
2667
+ const intersect = this.write(ctx);
2668
+ intersect.entity1 = null;
2669
+ intersect.entity2 = null;
2670
+ intersect.entity3 = null;
2671
+ intersect.entity4 = null;
2672
+ intersect.entity5 = null;
2448
2673
  }
2449
- }
2450
- var codeToIndex = {
2451
- // Letters (0-25)
2452
- KeyA: 0,
2453
- KeyB: 1,
2454
- KeyC: 2,
2455
- KeyD: 3,
2456
- KeyE: 4,
2457
- KeyF: 5,
2458
- KeyG: 6,
2459
- KeyH: 7,
2460
- KeyI: 8,
2461
- KeyJ: 9,
2462
- KeyK: 10,
2463
- KeyL: 11,
2464
- KeyM: 12,
2465
- KeyN: 13,
2466
- KeyO: 14,
2467
- KeyP: 15,
2468
- KeyQ: 16,
2469
- KeyR: 17,
2470
- KeyS: 18,
2471
- KeyT: 19,
2472
- KeyU: 20,
2473
- KeyV: 21,
2474
- KeyW: 22,
2475
- KeyX: 23,
2476
- KeyY: 24,
2477
- KeyZ: 25,
2478
- // Numbers (26-35)
2479
- Digit0: 26,
2480
- Digit1: 27,
2481
- Digit2: 28,
2482
- Digit3: 29,
2483
- Digit4: 30,
2484
- Digit5: 31,
2485
- Digit6: 32,
2486
- Digit7: 33,
2487
- Digit8: 34,
2488
- Digit9: 35,
2489
- // Function keys (36-47)
2490
- F1: 36,
2491
- F2: 37,
2492
- F3: 38,
2493
- F4: 39,
2494
- F5: 40,
2495
- F6: 41,
2496
- F7: 42,
2497
- F8: 43,
2498
- F9: 44,
2499
- F10: 45,
2500
- F11: 46,
2501
- F12: 47,
2502
- // Modifiers (48-51)
2503
- ShiftLeft: 48,
2504
- ShiftRight: 49,
2505
- ControlLeft: 50,
2506
- ControlRight: 51,
2507
- AltLeft: 52,
2508
- AltRight: 53,
2509
- MetaLeft: 54,
2510
- MetaRight: 55,
2511
- // Navigation (56-71)
2512
- Escape: 56,
2513
- Space: 57,
2514
- Enter: 58,
2515
- Tab: 59,
2516
- Backspace: 60,
2517
- Delete: 61,
2518
- ArrowLeft: 62,
2519
- ArrowUp: 63,
2520
- ArrowRight: 64,
2521
- ArrowDown: 65,
2522
- Home: 66,
2523
- End: 67,
2524
- PageUp: 68,
2525
- PageDown: 69,
2526
- Insert: 70,
2527
- // Punctuation (72-83)
2528
- Semicolon: 72,
2529
- Equal: 73,
2530
- Comma: 74,
2531
- Minus: 75,
2532
- Period: 76,
2533
- Slash: 77,
2534
- Backquote: 78,
2535
- BracketLeft: 79,
2536
- Backslash: 80,
2537
- BracketRight: 81,
2538
- Quote: 82,
2539
- // Numpad (84-99)
2540
- Numpad0: 84,
2541
- Numpad1: 85,
2542
- Numpad2: 86,
2543
- Numpad3: 87,
2544
- Numpad4: 88,
2545
- Numpad5: 89,
2546
- Numpad6: 90,
2547
- Numpad7: 91,
2548
- Numpad8: 92,
2549
- Numpad9: 93,
2550
- NumpadAdd: 94,
2551
- NumpadSubtract: 95,
2552
- NumpadMultiply: 96,
2553
- NumpadDivide: 97,
2554
- NumpadDecimal: 98,
2555
- NumpadEnter: 99
2556
- };
2557
- var Key = {
2558
- // Letters
2559
- A: codeToIndex.KeyA,
2560
- B: codeToIndex.KeyB,
2561
- C: codeToIndex.KeyC,
2562
- D: codeToIndex.KeyD,
2563
- E: codeToIndex.KeyE,
2564
- F: codeToIndex.KeyF,
2565
- G: codeToIndex.KeyG,
2566
- H: codeToIndex.KeyH,
2567
- I: codeToIndex.KeyI,
2568
- J: codeToIndex.KeyJ,
2569
- K: codeToIndex.KeyK,
2570
- L: codeToIndex.KeyL,
2571
- M: codeToIndex.KeyM,
2572
- N: codeToIndex.KeyN,
2573
- O: codeToIndex.KeyO,
2574
- P: codeToIndex.KeyP,
2575
- Q: codeToIndex.KeyQ,
2576
- R: codeToIndex.KeyR,
2577
- S: codeToIndex.KeyS,
2578
- T: codeToIndex.KeyT,
2579
- U: codeToIndex.KeyU,
2580
- V: codeToIndex.KeyV,
2581
- W: codeToIndex.KeyW,
2582
- X: codeToIndex.KeyX,
2583
- Y: codeToIndex.KeyY,
2584
- Z: codeToIndex.KeyZ,
2585
- // Numbers
2586
- Digit0: codeToIndex.Digit0,
2587
- Digit1: codeToIndex.Digit1,
2588
- Digit2: codeToIndex.Digit2,
2589
- Digit3: codeToIndex.Digit3,
2590
- Digit4: codeToIndex.Digit4,
2591
- Digit5: codeToIndex.Digit5,
2592
- Digit6: codeToIndex.Digit6,
2593
- Digit7: codeToIndex.Digit7,
2594
- Digit8: codeToIndex.Digit8,
2595
- Digit9: codeToIndex.Digit9,
2596
- // Function keys
2597
- F1: codeToIndex.F1,
2598
- F2: codeToIndex.F2,
2599
- F3: codeToIndex.F3,
2600
- F4: codeToIndex.F4,
2601
- F5: codeToIndex.F5,
2602
- F6: codeToIndex.F6,
2603
- F7: codeToIndex.F7,
2604
- F8: codeToIndex.F8,
2605
- F9: codeToIndex.F9,
2606
- F10: codeToIndex.F10,
2607
- F11: codeToIndex.F11,
2608
- F12: codeToIndex.F12,
2609
- // Modifiers
2610
- ShiftLeft: codeToIndex.ShiftLeft,
2611
- ShiftRight: codeToIndex.ShiftRight,
2612
- ControlLeft: codeToIndex.ControlLeft,
2613
- ControlRight: codeToIndex.ControlRight,
2614
- AltLeft: codeToIndex.AltLeft,
2615
- AltRight: codeToIndex.AltRight,
2616
- MetaLeft: codeToIndex.MetaLeft,
2617
- MetaRight: codeToIndex.MetaRight,
2618
- // Navigation
2619
- Escape: codeToIndex.Escape,
2620
- Space: codeToIndex.Space,
2621
- Enter: codeToIndex.Enter,
2622
- Tab: codeToIndex.Tab,
2623
- Backspace: codeToIndex.Backspace,
2624
- Delete: codeToIndex.Delete,
2625
- ArrowLeft: codeToIndex.ArrowLeft,
2626
- ArrowUp: codeToIndex.ArrowUp,
2627
- ArrowRight: codeToIndex.ArrowRight,
2628
- ArrowDown: codeToIndex.ArrowDown,
2629
- Home: codeToIndex.Home,
2630
- End: codeToIndex.End,
2631
- PageUp: codeToIndex.PageUp,
2632
- PageDown: codeToIndex.PageDown,
2633
- Insert: codeToIndex.Insert,
2634
- // Punctuation
2635
- Semicolon: codeToIndex.Semicolon,
2636
- Equal: codeToIndex.Equal,
2637
- Comma: codeToIndex.Comma,
2638
- Minus: codeToIndex.Minus,
2639
- Period: codeToIndex.Period,
2640
- Slash: codeToIndex.Slash,
2641
- Backquote: codeToIndex.Backquote,
2642
- BracketLeft: codeToIndex.BracketLeft,
2643
- Backslash: codeToIndex.Backslash,
2644
- BracketRight: codeToIndex.BracketRight,
2645
- Quote: codeToIndex.Quote,
2646
- // Numpad
2647
- Numpad0: codeToIndex.Numpad0,
2648
- Numpad1: codeToIndex.Numpad1,
2649
- Numpad2: codeToIndex.Numpad2,
2650
- Numpad3: codeToIndex.Numpad3,
2651
- Numpad4: codeToIndex.Numpad4,
2652
- Numpad5: codeToIndex.Numpad5,
2653
- Numpad6: codeToIndex.Numpad6,
2654
- Numpad7: codeToIndex.Numpad7,
2655
- Numpad8: codeToIndex.Numpad8,
2656
- Numpad9: codeToIndex.Numpad9,
2657
- NumpadAdd: codeToIndex.NumpadAdd,
2658
- NumpadSubtract: codeToIndex.NumpadSubtract,
2659
- NumpadMultiply: codeToIndex.NumpadMultiply,
2660
- NumpadDivide: codeToIndex.NumpadDivide,
2661
- NumpadDecimal: codeToIndex.NumpadDecimal,
2662
- NumpadEnter: codeToIndex.NumpadEnter
2663
2674
  };
2675
+ var Intersect = new IntersectDef();
2664
2676
 
2665
2677
  // src/singletons/Mouse.ts
2666
2678
  import { CanvasSingletonDef as CanvasSingletonDef9 } from "@woven-ecs/canvas-store";
@@ -2676,6 +2688,12 @@ var MouseSchema = {
2676
2688
  moveTrigger: field30.boolean().default(false),
2677
2689
  /** True for 1 frame when wheel is scrolled */
2678
2690
  wheelTrigger: field30.boolean().default(false),
2691
+ /**
2692
+ * Modifier key (Ctrl/Cmd) was held during this frame's wheel event.
2693
+ * Also true for trackpad pinch gestures, which browsers report as
2694
+ * wheel events with ctrlKey set.
2695
+ */
2696
+ wheelModKey: field30.boolean().default(false),
2679
2697
  /** True for 1 frame when mouse enters the editor element */
2680
2698
  enterTrigger: field30.boolean().default(false),
2681
2699
  /** True for 1 frame when mouse leaves the editor element */
@@ -2895,7 +2913,8 @@ function getMouseInput(ctx) {
2895
2913
  screenPosition: screenPos,
2896
2914
  worldPosition: worldPos,
2897
2915
  wheelDeltaX: mouse.wheelDeltaX,
2898
- wheelDeltaY: mouse.wheelDeltaY
2916
+ wheelDeltaY: mouse.wheelDeltaY,
2917
+ wheelModKey: mouse.wheelModKey
2899
2918
  });
2900
2919
  }
2901
2920
  if (mouse.moveTrigger) {
@@ -2905,7 +2924,8 @@ function getMouseInput(ctx) {
2905
2924
  screenPosition: screenPos,
2906
2925
  worldPosition: worldPos,
2907
2926
  wheelDeltaX: 0,
2908
- wheelDeltaY: 0
2927
+ wheelDeltaY: 0,
2928
+ wheelModKey: false
2909
2929
  });
2910
2930
  }
2911
2931
  return events;
@@ -4230,13 +4250,16 @@ var keyboardSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
4230
4250
  if (keyIndex === void 0) continue;
4231
4251
  if (event.type === "keydown") {
4232
4252
  const wasDown = getBit2(keyboard.keysDown, keyIndex);
4233
- if (!wasDown) {
4253
+ if (!wasDown || !event.repeat) {
4234
4254
  setBit(keyboard.keysDownTrigger, keyIndex, true);
4235
4255
  }
4236
4256
  setBit(keyboard.keysDown, keyIndex, true);
4237
4257
  } else if (event.type === "keyup") {
4238
4258
  setBit(keyboard.keysDown, keyIndex, false);
4239
4259
  setBit(keyboard.keysUpTrigger, keyIndex, true);
4260
+ if (event.code === "MetaLeft" || event.code === "MetaRight") {
4261
+ releaseNonModifierKeys(keyboard);
4262
+ }
4240
4263
  }
4241
4264
  keyboard.shiftDown = event.shiftKey;
4242
4265
  keyboard.altDown = event.altKey;
@@ -4244,6 +4267,25 @@ var keyboardSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
4244
4267
  }
4245
4268
  state.eventsBuffer.length = 0;
4246
4269
  });
4270
+ var MODIFIER_KEY_INDICES = /* @__PURE__ */ new Set([
4271
+ codeToIndex.ShiftLeft,
4272
+ codeToIndex.ShiftRight,
4273
+ codeToIndex.ControlLeft,
4274
+ codeToIndex.ControlRight,
4275
+ codeToIndex.AltLeft,
4276
+ codeToIndex.AltRight,
4277
+ codeToIndex.MetaLeft,
4278
+ codeToIndex.MetaRight
4279
+ ]);
4280
+ function releaseNonModifierKeys(keyboard) {
4281
+ const bitCount = keyboard.keysDown.length * 8;
4282
+ for (let i = 0; i < bitCount; i++) {
4283
+ if (MODIFIER_KEY_INDICES.has(i)) continue;
4284
+ if (!getBit2(keyboard.keysDown, i)) continue;
4285
+ setBit(keyboard.keysDown, i, false);
4286
+ setBit(keyboard.keysUpTrigger, i, true);
4287
+ }
4288
+ }
4247
4289
  function getBit2(buffer, bitIndex) {
4248
4290
  if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
4249
4291
  const byteIndex = Math.floor(bitIndex / 8);
@@ -4280,7 +4322,10 @@ function attachMouseListeners(domElement) {
4280
4322
  clientY: e.clientY,
4281
4323
  deltaX: e.deltaX,
4282
4324
  deltaY: e.deltaY,
4283
- deltaMode: e.deltaMode
4325
+ deltaMode: e.deltaMode,
4326
+ // Trackpad pinch gestures arrive as wheel events with ctrlKey set,
4327
+ // without any keydown — capture the flags from the event itself.
4328
+ modKey: e.ctrlKey || e.metaKey
4284
4329
  });
4285
4330
  },
4286
4331
  onMouseEnter: () => {
@@ -4322,6 +4367,7 @@ var mouseSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
4322
4367
  mouse.leaveTrigger = false;
4323
4368
  mouse.wheelDeltaX = 0;
4324
4369
  mouse.wheelDeltaY = 0;
4370
+ mouse.wheelModKey = false;
4325
4371
  for (const event of state.eventsBuffer) {
4326
4372
  switch (event.type) {
4327
4373
  case "mousemove":
@@ -4333,6 +4379,7 @@ var mouseSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
4333
4379
  mouse.wheelDeltaX = event.deltaX;
4334
4380
  mouse.wheelDeltaY = normalizeWheelDelta(event.deltaY, event.deltaMode);
4335
4381
  mouse.wheelTrigger = true;
4382
+ mouse.wheelModKey = event.modKey === true;
4336
4383
  break;
4337
4384
  case "mouseenter":
4338
4385
  mouse.enterTrigger = true;
@@ -5576,6 +5623,9 @@ var selectSystem = defineEditorSystem({ phase: "capture", priority: 100 }, (ctx)
5576
5623
  buttons = ["left"];
5577
5624
  }
5578
5625
  const events = getPointerInput(ctx, buttons);
5626
+ if (buttons.length === 0 && (state.state === SelectionState.SelectionBoxPointing || state.state === SelectionState.SelectionBoxDragging)) {
5627
+ events.push({ type: "cancel", ctx });
5628
+ }
5579
5629
  if (events.length === 0) return;
5580
5630
  SelectionStateSingleton.run(ctx, selectionMachine, events);
5581
5631
  });
@@ -6473,6 +6523,11 @@ function createCorePlugin(options = {}) {
6473
6523
  command: RemoveSelected.name,
6474
6524
  key: Key.Delete
6475
6525
  },
6526
+ {
6527
+ // Mac keyboards' delete key reports code "Backspace"
6528
+ command: RemoveSelected.name,
6529
+ key: Key.Backspace
6530
+ },
6476
6531
  {
6477
6532
  command: SelectAll.name,
6478
6533
  key: Key.A,