@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.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'
@@ -477,6 +483,7 @@ var BlockDef = z.object({
477
483
  canScale: z.boolean().default(true),
478
484
  snappable: z.boolean().default(true),
479
485
  interactable: z.boolean().default(true),
486
+ showOperations: z.boolean().default(true),
480
487
  excludeFromRankBounds: z.boolean().default(false),
481
488
  connectors: BlockDefConnectors.default(BlockDefConnectors.parse({}))
482
489
  });
@@ -1994,25 +2001,335 @@ var CameraDef = class extends CanvasSingletonDef2 {
1994
2001
  var Camera = new CameraDef();
1995
2002
 
1996
2003
  // src/singletons/Controls.ts
2004
+ import { CanvasSingletonDef as CanvasSingletonDef4 } from "@woven-ecs/canvas-store";
2005
+ import { field as field25 } from "@woven-ecs/core";
2006
+
2007
+ // src/singletons/Keyboard.ts
1997
2008
  import { CanvasSingletonDef as CanvasSingletonDef3 } from "@woven-ecs/canvas-store";
1998
2009
  import { field as field24 } from "@woven-ecs/core";
2010
+ var KEY_BUFFER_SIZE = 32;
2011
+ var KeyboardSchema = {
2012
+ /**
2013
+ * Buffer where each bit represents whether a key is currently pressed.
2014
+ * Uses field.buffer for zero-allocation subarray views.
2015
+ */
2016
+ keysDown: field24.buffer(field24.uint8()).size(KEY_BUFFER_SIZE),
2017
+ /**
2018
+ * Buffer for key-down triggers (true for exactly 1 frame when key is pressed).
2019
+ * Uses field.buffer for zero-allocation subarray views.
2020
+ */
2021
+ keysDownTrigger: field24.buffer(field24.uint8()).size(KEY_BUFFER_SIZE),
2022
+ /**
2023
+ * Buffer for key-up triggers (true for exactly 1 frame when key is released).
2024
+ * Uses field.buffer for zero-allocation subarray views.
2025
+ */
2026
+ keysUpTrigger: field24.buffer(field24.uint8()).size(KEY_BUFFER_SIZE),
2027
+ /** Common modifier - Shift key is down */
2028
+ shiftDown: field24.boolean().default(false),
2029
+ /** Common modifier - Alt/Option key is down */
2030
+ altDown: field24.boolean().default(false),
2031
+ /** Common modifier - Ctrl (Windows/Linux) or Cmd (Mac) is down */
2032
+ modDown: field24.boolean().default(false)
2033
+ };
2034
+ function getBit(buffer, bitIndex) {
2035
+ if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
2036
+ const byteIndex = Math.floor(bitIndex / 8);
2037
+ const bitOffset = bitIndex % 8;
2038
+ return (buffer[byteIndex] & 1 << bitOffset) !== 0;
2039
+ }
2040
+ var KeyboardDef = class extends CanvasSingletonDef3 {
2041
+ constructor() {
2042
+ super({ name: "keyboard" }, KeyboardSchema);
2043
+ }
2044
+ /**
2045
+ * Check if a key is currently pressed.
2046
+ * @param ctx - Editor context
2047
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2048
+ */
2049
+ isKeyDown(ctx, key) {
2050
+ return getBit(this.read(ctx).keysDown, key);
2051
+ }
2052
+ /**
2053
+ * Check if a key was just pressed this frame.
2054
+ * @param ctx - Editor context
2055
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2056
+ */
2057
+ isKeyDownTrigger(ctx, key) {
2058
+ return getBit(this.read(ctx).keysDownTrigger, key);
2059
+ }
2060
+ /**
2061
+ * Check if a key was just released this frame.
2062
+ * @param ctx - Editor context
2063
+ * @param key - The key index to check (use Key.A, Key.Space, etc.)
2064
+ */
2065
+ isKeyUpTrigger(ctx, key) {
2066
+ return getBit(this.read(ctx).keysUpTrigger, key);
2067
+ }
2068
+ /**
2069
+ * Reset all keyboard state.
2070
+ * Clears all key states and modifier flags.
2071
+ */
2072
+ reset(ctx) {
2073
+ const keyboard = this.write(ctx);
2074
+ clearBits(keyboard.keysDown);
2075
+ clearBits(keyboard.keysDownTrigger);
2076
+ clearBits(keyboard.keysUpTrigger);
2077
+ keyboard.shiftDown = false;
2078
+ keyboard.altDown = false;
2079
+ keyboard.modDown = false;
2080
+ }
2081
+ };
2082
+ var Keyboard = new KeyboardDef();
2083
+ function setBit(buffer, bitIndex, value) {
2084
+ if (bitIndex < 0 || bitIndex >= buffer.length * 8) return;
2085
+ const byteIndex = Math.floor(bitIndex / 8);
2086
+ const bitOffset = bitIndex % 8;
2087
+ if (value) {
2088
+ buffer[byteIndex] |= 1 << bitOffset;
2089
+ } else {
2090
+ buffer[byteIndex] &= ~(1 << bitOffset);
2091
+ }
2092
+ }
2093
+ function clearBits(buffer) {
2094
+ for (let i = 0; i < buffer.length; i++) {
2095
+ buffer[i] = 0;
2096
+ }
2097
+ }
2098
+ var codeToIndex = {
2099
+ // Letters (0-25)
2100
+ KeyA: 0,
2101
+ KeyB: 1,
2102
+ KeyC: 2,
2103
+ KeyD: 3,
2104
+ KeyE: 4,
2105
+ KeyF: 5,
2106
+ KeyG: 6,
2107
+ KeyH: 7,
2108
+ KeyI: 8,
2109
+ KeyJ: 9,
2110
+ KeyK: 10,
2111
+ KeyL: 11,
2112
+ KeyM: 12,
2113
+ KeyN: 13,
2114
+ KeyO: 14,
2115
+ KeyP: 15,
2116
+ KeyQ: 16,
2117
+ KeyR: 17,
2118
+ KeyS: 18,
2119
+ KeyT: 19,
2120
+ KeyU: 20,
2121
+ KeyV: 21,
2122
+ KeyW: 22,
2123
+ KeyX: 23,
2124
+ KeyY: 24,
2125
+ KeyZ: 25,
2126
+ // Numbers (26-35)
2127
+ Digit0: 26,
2128
+ Digit1: 27,
2129
+ Digit2: 28,
2130
+ Digit3: 29,
2131
+ Digit4: 30,
2132
+ Digit5: 31,
2133
+ Digit6: 32,
2134
+ Digit7: 33,
2135
+ Digit8: 34,
2136
+ Digit9: 35,
2137
+ // Function keys (36-47)
2138
+ F1: 36,
2139
+ F2: 37,
2140
+ F3: 38,
2141
+ F4: 39,
2142
+ F5: 40,
2143
+ F6: 41,
2144
+ F7: 42,
2145
+ F8: 43,
2146
+ F9: 44,
2147
+ F10: 45,
2148
+ F11: 46,
2149
+ F12: 47,
2150
+ // Modifiers (48-51)
2151
+ ShiftLeft: 48,
2152
+ ShiftRight: 49,
2153
+ ControlLeft: 50,
2154
+ ControlRight: 51,
2155
+ AltLeft: 52,
2156
+ AltRight: 53,
2157
+ MetaLeft: 54,
2158
+ MetaRight: 55,
2159
+ // Navigation (56-71)
2160
+ Escape: 56,
2161
+ Space: 57,
2162
+ Enter: 58,
2163
+ Tab: 59,
2164
+ Backspace: 60,
2165
+ Delete: 61,
2166
+ ArrowLeft: 62,
2167
+ ArrowUp: 63,
2168
+ ArrowRight: 64,
2169
+ ArrowDown: 65,
2170
+ Home: 66,
2171
+ End: 67,
2172
+ PageUp: 68,
2173
+ PageDown: 69,
2174
+ Insert: 70,
2175
+ // Punctuation (72-83)
2176
+ Semicolon: 72,
2177
+ Equal: 73,
2178
+ Comma: 74,
2179
+ Minus: 75,
2180
+ Period: 76,
2181
+ Slash: 77,
2182
+ Backquote: 78,
2183
+ BracketLeft: 79,
2184
+ Backslash: 80,
2185
+ BracketRight: 81,
2186
+ Quote: 82,
2187
+ // Numpad (84-99)
2188
+ Numpad0: 84,
2189
+ Numpad1: 85,
2190
+ Numpad2: 86,
2191
+ Numpad3: 87,
2192
+ Numpad4: 88,
2193
+ Numpad5: 89,
2194
+ Numpad6: 90,
2195
+ Numpad7: 91,
2196
+ Numpad8: 92,
2197
+ Numpad9: 93,
2198
+ NumpadAdd: 94,
2199
+ NumpadSubtract: 95,
2200
+ NumpadMultiply: 96,
2201
+ NumpadDivide: 97,
2202
+ NumpadDecimal: 98,
2203
+ NumpadEnter: 99
2204
+ };
2205
+ var Key = {
2206
+ // Letters
2207
+ A: codeToIndex.KeyA,
2208
+ B: codeToIndex.KeyB,
2209
+ C: codeToIndex.KeyC,
2210
+ D: codeToIndex.KeyD,
2211
+ E: codeToIndex.KeyE,
2212
+ F: codeToIndex.KeyF,
2213
+ G: codeToIndex.KeyG,
2214
+ H: codeToIndex.KeyH,
2215
+ I: codeToIndex.KeyI,
2216
+ J: codeToIndex.KeyJ,
2217
+ K: codeToIndex.KeyK,
2218
+ L: codeToIndex.KeyL,
2219
+ M: codeToIndex.KeyM,
2220
+ N: codeToIndex.KeyN,
2221
+ O: codeToIndex.KeyO,
2222
+ P: codeToIndex.KeyP,
2223
+ Q: codeToIndex.KeyQ,
2224
+ R: codeToIndex.KeyR,
2225
+ S: codeToIndex.KeyS,
2226
+ T: codeToIndex.KeyT,
2227
+ U: codeToIndex.KeyU,
2228
+ V: codeToIndex.KeyV,
2229
+ W: codeToIndex.KeyW,
2230
+ X: codeToIndex.KeyX,
2231
+ Y: codeToIndex.KeyY,
2232
+ Z: codeToIndex.KeyZ,
2233
+ // Numbers
2234
+ Digit0: codeToIndex.Digit0,
2235
+ Digit1: codeToIndex.Digit1,
2236
+ Digit2: codeToIndex.Digit2,
2237
+ Digit3: codeToIndex.Digit3,
2238
+ Digit4: codeToIndex.Digit4,
2239
+ Digit5: codeToIndex.Digit5,
2240
+ Digit6: codeToIndex.Digit6,
2241
+ Digit7: codeToIndex.Digit7,
2242
+ Digit8: codeToIndex.Digit8,
2243
+ Digit9: codeToIndex.Digit9,
2244
+ // Function keys
2245
+ F1: codeToIndex.F1,
2246
+ F2: codeToIndex.F2,
2247
+ F3: codeToIndex.F3,
2248
+ F4: codeToIndex.F4,
2249
+ F5: codeToIndex.F5,
2250
+ F6: codeToIndex.F6,
2251
+ F7: codeToIndex.F7,
2252
+ F8: codeToIndex.F8,
2253
+ F9: codeToIndex.F9,
2254
+ F10: codeToIndex.F10,
2255
+ F11: codeToIndex.F11,
2256
+ F12: codeToIndex.F12,
2257
+ // Modifiers
2258
+ ShiftLeft: codeToIndex.ShiftLeft,
2259
+ ShiftRight: codeToIndex.ShiftRight,
2260
+ ControlLeft: codeToIndex.ControlLeft,
2261
+ ControlRight: codeToIndex.ControlRight,
2262
+ AltLeft: codeToIndex.AltLeft,
2263
+ AltRight: codeToIndex.AltRight,
2264
+ MetaLeft: codeToIndex.MetaLeft,
2265
+ MetaRight: codeToIndex.MetaRight,
2266
+ // Navigation
2267
+ Escape: codeToIndex.Escape,
2268
+ Space: codeToIndex.Space,
2269
+ Enter: codeToIndex.Enter,
2270
+ Tab: codeToIndex.Tab,
2271
+ Backspace: codeToIndex.Backspace,
2272
+ Delete: codeToIndex.Delete,
2273
+ ArrowLeft: codeToIndex.ArrowLeft,
2274
+ ArrowUp: codeToIndex.ArrowUp,
2275
+ ArrowRight: codeToIndex.ArrowRight,
2276
+ ArrowDown: codeToIndex.ArrowDown,
2277
+ Home: codeToIndex.Home,
2278
+ End: codeToIndex.End,
2279
+ PageUp: codeToIndex.PageUp,
2280
+ PageDown: codeToIndex.PageDown,
2281
+ Insert: codeToIndex.Insert,
2282
+ // Punctuation
2283
+ Semicolon: codeToIndex.Semicolon,
2284
+ Equal: codeToIndex.Equal,
2285
+ Comma: codeToIndex.Comma,
2286
+ Minus: codeToIndex.Minus,
2287
+ Period: codeToIndex.Period,
2288
+ Slash: codeToIndex.Slash,
2289
+ Backquote: codeToIndex.Backquote,
2290
+ BracketLeft: codeToIndex.BracketLeft,
2291
+ Backslash: codeToIndex.Backslash,
2292
+ BracketRight: codeToIndex.BracketRight,
2293
+ Quote: codeToIndex.Quote,
2294
+ // Numpad
2295
+ Numpad0: codeToIndex.Numpad0,
2296
+ Numpad1: codeToIndex.Numpad1,
2297
+ Numpad2: codeToIndex.Numpad2,
2298
+ Numpad3: codeToIndex.Numpad3,
2299
+ Numpad4: codeToIndex.Numpad4,
2300
+ Numpad5: codeToIndex.Numpad5,
2301
+ Numpad6: codeToIndex.Numpad6,
2302
+ Numpad7: codeToIndex.Numpad7,
2303
+ Numpad8: codeToIndex.Numpad8,
2304
+ Numpad9: codeToIndex.Numpad9,
2305
+ NumpadAdd: codeToIndex.NumpadAdd,
2306
+ NumpadSubtract: codeToIndex.NumpadSubtract,
2307
+ NumpadMultiply: codeToIndex.NumpadMultiply,
2308
+ NumpadDivide: codeToIndex.NumpadDivide,
2309
+ NumpadDecimal: codeToIndex.NumpadDecimal,
2310
+ NumpadEnter: codeToIndex.NumpadEnter
2311
+ };
2312
+
2313
+ // src/singletons/Controls.ts
1999
2314
  var ControlsSchema = {
2000
2315
  /** Tool activated by left mouse button */
2001
- leftMouseTool: field24.string().max(32).default("select"),
2316
+ leftMouseTool: field25.string().max(32).default("select"),
2317
+ /** Tool activated by left mouse button while the space bar is held (empty string = no remap) */
2318
+ spaceLeftMouseTool: field25.string().max(32).default("hand"),
2002
2319
  /** Tool activated by middle mouse button */
2003
- middleMouseTool: field24.string().max(32).default("hand"),
2320
+ middleMouseTool: field25.string().max(32).default("hand"),
2004
2321
  /** Tool activated by right mouse button */
2005
- rightMouseTool: field24.string().max(32).default("hand"),
2322
+ rightMouseTool: field25.string().max(32).default("hand"),
2006
2323
  /** Tool activated by mouse wheel */
2007
- wheelTool: field24.string().max(32).default("scroll"),
2324
+ wheelTool: field25.string().max(32).default("scroll"),
2008
2325
  /** Tool activated by mouse wheel with modifier key held */
2009
- modWheelTool: field24.string().max(32).default("zoom"),
2326
+ modWheelTool: field25.string().max(32).default("zoom"),
2010
2327
  /** JSON snapshot of block to place on next click (empty string = no placement active) */
2011
- heldSnapshot: field24.string().max(65536).default(""),
2328
+ heldSnapshot: field25.string().max(65536).default(""),
2012
2329
  /** User-facing tool name for UI highlighting (may differ from leftMouseTool during draw/drag-out) */
2013
- activeToolName: field24.string().max(32).default("select")
2330
+ activeToolName: field25.string().max(32).default("select")
2014
2331
  };
2015
- var ControlsDef = class extends CanvasSingletonDef3 {
2332
+ var ControlsDef = class extends CanvasSingletonDef4 {
2016
2333
  constructor() {
2017
2334
  super({ name: "controls" }, ControlsSchema);
2018
2335
  }
@@ -2025,7 +2342,9 @@ var ControlsDef = class extends CanvasSingletonDef3 {
2025
2342
  getButtons(ctx, ...tools) {
2026
2343
  const controls = this.read(ctx);
2027
2344
  const buttons = [];
2028
- if (tools.includes(controls.leftMouseTool)) {
2345
+ const spaceRemap = controls.spaceLeftMouseTool !== "" && Keyboard.isKeyDown(ctx, Key.Space);
2346
+ const leftTool = spaceRemap ? controls.spaceLeftMouseTool : controls.leftMouseTool;
2347
+ if (tools.includes(leftTool)) {
2029
2348
  buttons.push(PointerButton.Left);
2030
2349
  }
2031
2350
  if (tools.includes(controls.middleMouseTool)) {
@@ -2051,19 +2370,19 @@ var ControlsDef = class extends CanvasSingletonDef3 {
2051
2370
  var Controls = new ControlsDef();
2052
2371
 
2053
2372
  // src/singletons/Cursor.ts
2054
- import { CanvasSingletonDef as CanvasSingletonDef4 } from "@woven-ecs/canvas-store";
2055
- import { field as field25 } from "@woven-ecs/core";
2373
+ import { CanvasSingletonDef as CanvasSingletonDef5 } from "@woven-ecs/canvas-store";
2374
+ import { field as field26 } from "@woven-ecs/core";
2056
2375
  var CursorSchema = {
2057
2376
  /** Base cursor kind (from current tool) */
2058
- cursorKind: field25.string().max(64).default("select"),
2377
+ cursorKind: field26.string().max(64).default("select"),
2059
2378
  /** Base cursor rotation in radians */
2060
- rotation: field25.float64().default(0),
2379
+ rotation: field26.float64().default(0),
2061
2380
  /** Context-specific cursor kind (overrides cursorKind when set, e.g., during drag/hover) */
2062
- contextCursorKind: field25.string().max(64).default(""),
2381
+ contextCursorKind: field26.string().max(64).default(""),
2063
2382
  /** Context cursor rotation in radians */
2064
- contextRotation: field25.float64().default(0)
2383
+ contextRotation: field26.float64().default(0)
2065
2384
  };
2066
- var CursorDef2 = class extends CanvasSingletonDef4 {
2385
+ var CursorDef2 = class extends CanvasSingletonDef5 {
2067
2386
  constructor() {
2068
2387
  super({ name: "cursor" }, CursorSchema);
2069
2388
  }
@@ -2108,10 +2427,10 @@ var CursorDef2 = class extends CanvasSingletonDef4 {
2108
2427
  var Cursor = new CursorDef2();
2109
2428
 
2110
2429
  // src/singletons/FrameContainmentState.ts
2111
- import { field as field26 } from "@woven-ecs/core";
2430
+ import { field as field27 } from "@woven-ecs/core";
2112
2431
 
2113
2432
  // src/EditorStateDef.ts
2114
- import { CanvasSingletonDef as CanvasSingletonDef5 } from "@woven-ecs/canvas-store";
2433
+ import { CanvasSingletonDef as CanvasSingletonDef6 } from "@woven-ecs/canvas-store";
2115
2434
 
2116
2435
  // src/machine.ts
2117
2436
  import { transition } from "xstate";
@@ -2139,7 +2458,7 @@ function runMachine(machine, currentState, context, events) {
2139
2458
  }
2140
2459
 
2141
2460
  // src/EditorStateDef.ts
2142
- var EditorStateDef = class extends CanvasSingletonDef5 {
2461
+ var EditorStateDef = class extends CanvasSingletonDef6 {
2143
2462
  constructor(name, schema) {
2144
2463
  super({ name, sync: "none" }, schema);
2145
2464
  }
@@ -2216,29 +2535,29 @@ function defineEditorState(name, schema) {
2216
2535
  // src/singletons/FrameContainmentState.ts
2217
2536
  var FrameContainmentState = defineEditorState("frameContainmentState", {
2218
2537
  /** Current state machine state */
2219
- state: field26.string().max(16).default(FrameContainmentStateEnum.Idle),
2538
+ state: field27.string().max(16).default(FrameContainmentStateEnum.Idle),
2220
2539
  /** EntityId of the frame currently highlighted as a drop target, or null */
2221
- highlightedFrame: field26.ref()
2540
+ highlightedFrame: field27.ref()
2222
2541
  });
2223
2542
 
2224
2543
  // src/singletons/Grid.ts
2225
- import { CanvasSingletonDef as CanvasSingletonDef6 } from "@woven-ecs/canvas-store";
2226
- import { field as field27 } from "@woven-ecs/core";
2544
+ import { CanvasSingletonDef as CanvasSingletonDef7 } from "@woven-ecs/canvas-store";
2545
+ import { field as field28 } from "@woven-ecs/core";
2227
2546
  var GridSchema = {
2228
2547
  /** Whether grid snapping is enabled */
2229
- enabled: field27.boolean().default(false),
2548
+ enabled: field28.boolean().default(false),
2230
2549
  /** Whether resized/rotated objects must stay aligned to the grid */
2231
- strict: field27.boolean().default(false),
2550
+ strict: field28.boolean().default(false),
2232
2551
  /** Width of each grid column in world units */
2233
- colWidth: field27.float64().default(20),
2552
+ colWidth: field28.float64().default(20),
2234
2553
  /** Height of each grid row in world units */
2235
- rowHeight: field27.float64().default(20),
2554
+ rowHeight: field28.float64().default(20),
2236
2555
  /** Angular snap increment in radians when grid is enabled */
2237
- snapAngleRad: field27.float64().default(Math.PI / 36),
2556
+ snapAngleRad: field28.float64().default(Math.PI / 36),
2238
2557
  /** Angular snap increment in radians when shift key is held */
2239
- shiftSnapAngleRad: field27.float64().default(Math.PI / 12)
2558
+ shiftSnapAngleRad: field28.float64().default(Math.PI / 12)
2240
2559
  };
2241
- var GridDef = class extends CanvasSingletonDef6 {
2560
+ var GridDef = class extends CanvasSingletonDef7 {
2242
2561
  constructor() {
2243
2562
  super({ name: "grid" }, GridSchema);
2244
2563
  }
@@ -2298,17 +2617,17 @@ var GridDef = class extends CanvasSingletonDef6 {
2298
2617
  var Grid = new GridDef();
2299
2618
 
2300
2619
  // src/singletons/Intersect.ts
2301
- import { CanvasSingletonDef as CanvasSingletonDef7 } from "@woven-ecs/canvas-store";
2302
- import { field as field28 } from "@woven-ecs/core";
2620
+ import { CanvasSingletonDef as CanvasSingletonDef8 } from "@woven-ecs/canvas-store";
2621
+ import { field as field29 } from "@woven-ecs/core";
2303
2622
  var IntersectSchema = {
2304
2623
  // 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()
2624
+ entity1: field29.ref(),
2625
+ entity2: field29.ref(),
2626
+ entity3: field29.ref(),
2627
+ entity4: field29.ref(),
2628
+ entity5: field29.ref()
2310
2629
  };
2311
- var IntersectDef = class extends CanvasSingletonDef7 {
2630
+ var IntersectDef = class extends CanvasSingletonDef8 {
2312
2631
  constructor() {
2313
2632
  super({ name: "intersect" }, IntersectSchema);
2314
2633
  }
@@ -2327,340 +2646,34 @@ var IntersectDef = class extends CanvasSingletonDef7 {
2327
2646
  if (intersect.entity1 !== null) result.push(intersect.entity1);
2328
2647
  if (intersect.entity2 !== null) result.push(intersect.entity2);
2329
2648
  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);
2649
+ if (intersect.entity4 !== null) result.push(intersect.entity4);
2650
+ if (intersect.entity5 !== null) result.push(intersect.entity5);
2651
+ return result;
2443
2652
  }
2444
- }
2445
- function clearBits(buffer) {
2446
- for (let i = 0; i < buffer.length; i++) {
2447
- buffer[i] = 0;
2653
+ /**
2654
+ * Set intersected entities from an array.
2655
+ */
2656
+ setAll(ctx, entities) {
2657
+ const intersect = this.write(ctx);
2658
+ intersect.entity1 = entities[0] ?? null;
2659
+ intersect.entity2 = entities[1] ?? null;
2660
+ intersect.entity3 = entities[2] ?? null;
2661
+ intersect.entity4 = entities[3] ?? null;
2662
+ intersect.entity5 = entities[4] ?? null;
2663
+ }
2664
+ /**
2665
+ * Clear all intersections.
2666
+ */
2667
+ clear(ctx) {
2668
+ const intersect = this.write(ctx);
2669
+ intersect.entity1 = null;
2670
+ intersect.entity2 = null;
2671
+ intersect.entity3 = null;
2672
+ intersect.entity4 = null;
2673
+ intersect.entity5 = null;
2448
2674
  }
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
2675
  };
2676
+ var Intersect = new IntersectDef();
2664
2677
 
2665
2678
  // src/singletons/Mouse.ts
2666
2679
  import { CanvasSingletonDef as CanvasSingletonDef9 } from "@woven-ecs/canvas-store";
@@ -3153,6 +3166,21 @@ function getDescendants(ctx, parentId) {
3153
3166
  }
3154
3167
  return result;
3155
3168
  }
3169
+ function filterRoots(ctx, entityIds) {
3170
+ const idSet = new Set(entityIds);
3171
+ return entityIds.filter((entityId) => {
3172
+ if (!hasComponent3(ctx, entityId, Block)) return false;
3173
+ let current = Block.read(ctx, entityId).parentId;
3174
+ const visited = /* @__PURE__ */ new Set();
3175
+ while (current !== null && hasComponent3(ctx, current, Block)) {
3176
+ if (idSet.has(current)) return false;
3177
+ if (visited.has(current)) break;
3178
+ visited.add(current);
3179
+ current = Block.read(ctx, current).parentId;
3180
+ }
3181
+ return true;
3182
+ });
3183
+ }
3156
3184
  function cascadeDelete(ctx, entityId) {
3157
3185
  const children = getBackrefs(ctx, entityId, Block, "parentId");
3158
3186
  for (const childId of children) {
@@ -5611,6 +5639,9 @@ var selectSystem = defineEditorSystem({ phase: "capture", priority: 100 }, (ctx)
5611
5639
  buttons = ["left"];
5612
5640
  }
5613
5641
  const events = getPointerInput(ctx, buttons);
5642
+ if (buttons.length === 0 && (state.state === SelectionState.SelectionBoxPointing || state.state === SelectionState.SelectionBoxDragging)) {
5643
+ events.push({ type: "cancel", ctx });
5644
+ }
5614
5645
  if (events.length === 0) return;
5615
5646
  SelectionStateSingleton.run(ctx, selectionMachine, events);
5616
5647
  });
@@ -5784,6 +5815,7 @@ var blockSystem = defineEditorSystem({ phase: "update" }, (ctx) => {
5784
5815
  }
5785
5816
  }
5786
5817
  });
5818
+ on(ctx, DuplicateSelected, duplicateSelected);
5787
5819
  on(ctx, BringForwardSelected, bringForwardSelected);
5788
5820
  on(ctx, SendBackwardSelected, sendBackwardSelected);
5789
5821
  on(ctx, SetCursor, (ctx2, payload) => {
@@ -5809,6 +5841,23 @@ function deselectAllBlocks(ctx) {
5809
5841
  deselectBlock(ctx, entityId);
5810
5842
  }
5811
5843
  }
5844
+ var DUPLICATE_OFFSET = 20;
5845
+ function duplicateSelected(ctx) {
5846
+ const selectedBlocks = [...selectedBlocksQuery4.current(ctx)];
5847
+ if (selectedBlocks.length === 0) return;
5848
+ cloneEntities(ctx, selectedBlocks, [0, 0], crypto.randomUUID());
5849
+ const grid = Grid.read(ctx);
5850
+ const offset = grid.enabled && grid.colWidth > 0 && grid.rowHeight > 0 ? [grid.colWidth, grid.rowHeight] : [DUPLICATE_OFFSET, DUPLICATE_OFFSET];
5851
+ for (const entityId of filterRoots(ctx, selectedBlocks)) {
5852
+ const worldPos = Block.getWorldPosition(ctx, entityId);
5853
+ Vec25.add(worldPos, offset);
5854
+ Block.setWorldPosition(ctx, entityId, worldPos);
5855
+ }
5856
+ const { transformBoxId } = TransformBoxStateSingleton.read(ctx);
5857
+ if (transformBoxId !== null) {
5858
+ UpdateTransformBox.spawn(ctx, { transformBoxId });
5859
+ }
5860
+ }
5812
5861
  function bringForwardSelected(ctx) {
5813
5862
  const selectedBlocks = [...selectedBlocksQuery4.current(ctx)];
5814
5863
  if (selectedBlocks.length === 0) return;
@@ -5846,7 +5895,6 @@ function sendBackwardSelected(ctx) {
5846
5895
  }
5847
5896
  }
5848
5897
  function cloneEntities(ctx, entityIds, offset, seed) {
5849
- const rootSet = new Set(entityIds);
5850
5898
  const allEntityIdsSet = new Set(entityIds);
5851
5899
  for (const entityId of entityIds) {
5852
5900
  for (const descendant of getDescendants(ctx, entityId)) {
@@ -5854,6 +5902,7 @@ function cloneEntities(ctx, entityIds, offset, seed) {
5854
5902
  }
5855
5903
  }
5856
5904
  entityIds = [...allEntityIdsSet];
5905
+ const rootSet = new Set(filterRoots(ctx, entityIds));
5857
5906
  const { componentsById } = getResources13(ctx);
5858
5907
  const documentComponents = new Map([...componentsById].filter(([, def]) => def.sync === "document"));
5859
5908
  const syncedComponentId = Synced7._getComponentId(ctx);
@@ -7268,6 +7317,7 @@ export {
7268
7317
  deselectBlock,
7269
7318
  detectEmbedProvider,
7270
7319
  field37 as field,
7320
+ filterRoots,
7271
7321
  findFrameAtPoint,
7272
7322
  generateUuidBySeed,
7273
7323
  getBackrefs5 as getBackrefs,