@woven-canvas/core 0.1.3 → 1.0.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
@@ -21,7 +21,7 @@ import {
21
21
  EventType,
22
22
  field as field28,
23
23
  getBackrefs,
24
- getResources as getResources12,
24
+ getResources as getResources13,
25
25
  hasComponent as hasComponent7,
26
26
  isAlive,
27
27
  MainThreadSystem as MainThreadSystem2,
@@ -35,7 +35,7 @@ import {
35
35
  CanvasComponentDef as CanvasComponentDef6,
36
36
  CanvasSingletonDef as CanvasSingletonDef10
37
37
  } from "@woven-ecs/canvas-store";
38
- import { getResources as getResources11 } from "@woven-ecs/core";
38
+ import { getResources as getResources12 } from "@woven-ecs/core";
39
39
 
40
40
  // src/components/index.ts
41
41
  var components_exports = {};
@@ -75,6 +75,261 @@ import { field as field2 } from "@woven-ecs/core";
75
75
  import { Rect, Vec2 } from "@woven-canvas/math";
76
76
  import { CanvasComponentDef } from "@woven-ecs/canvas-store";
77
77
  import { field } from "@woven-ecs/core";
78
+
79
+ // src/types.ts
80
+ import { z } from "zod";
81
+ function generateUserColor() {
82
+ const colors = [
83
+ "#f43f5e",
84
+ // rose
85
+ "#ec4899",
86
+ // pink
87
+ "#a855f7",
88
+ // purple
89
+ "#6366f1",
90
+ // indigo
91
+ "#3b82f6",
92
+ // blue
93
+ "#0ea5e9",
94
+ // sky
95
+ "#14b8a6",
96
+ // teal
97
+ "#22c55e",
98
+ // green
99
+ "#eab308",
100
+ // yellow
101
+ "#f97316"
102
+ // orange
103
+ ];
104
+ return colors[Math.floor(Math.random() * colors.length)];
105
+ }
106
+ var UserData = z.object({
107
+ userId: z.string().max(36).default(() => crypto.randomUUID()),
108
+ sessionId: z.string().max(36).default(() => crypto.randomUUID()),
109
+ color: z.string().max(7).default(generateUserColor),
110
+ name: z.string().max(100).default("Anonymous"),
111
+ avatar: z.string().max(500).default("")
112
+ });
113
+ function getPluginResources(ctx, pluginName) {
114
+ const resources = ctx.resources;
115
+ return resources.pluginResources[pluginName];
116
+ }
117
+ var GridOptions = z.object({
118
+ /**
119
+ * Whether grid snapping is enabled.
120
+ * @default true
121
+ */
122
+ enabled: z.boolean().default(true),
123
+ /**
124
+ * Whether resized/rotated objects must stay aligned to the grid.
125
+ * If true, objects snap to grid during resize/rotate.
126
+ * If false, objects scale proportionally to the transform box, which may
127
+ * cause them to be unaligned with the grid.
128
+ * @default false
129
+ */
130
+ strict: z.boolean().default(false),
131
+ /**
132
+ * Width of each grid column in world units.
133
+ * @default 20
134
+ */
135
+ colWidth: z.number().nonnegative().default(20),
136
+ /**
137
+ * Height of each grid row in world units.
138
+ * @default 20
139
+ */
140
+ rowHeight: z.number().nonnegative().default(20),
141
+ /**
142
+ * Angular snap increment in radians when grid is enabled.
143
+ * @default Math.PI / 36 (5 degrees)
144
+ */
145
+ snapAngleRad: z.number().nonnegative().default(Math.PI / 36),
146
+ /**
147
+ * Angular snap increment in radians when shift key is held.
148
+ * @default Math.PI / 12 (15 degrees)
149
+ */
150
+ shiftSnapAngleRad: z.number().nonnegative().default(Math.PI / 12)
151
+ });
152
+ var ControlsOptions = z.object({
153
+ /**
154
+ * Tool activated by left mouse button.
155
+ * @default 'select'
156
+ */
157
+ leftMouseTool: z.string().max(32).default("select"),
158
+ /**
159
+ * Tool activated by middle mouse button.
160
+ * @default 'hand'
161
+ */
162
+ middleMouseTool: z.string().max(32).default("hand"),
163
+ /**
164
+ * Tool activated by right mouse button.
165
+ * @default 'menu'
166
+ */
167
+ rightMouseTool: z.string().max(32).default("menu"),
168
+ /**
169
+ * Tool activated by mouse wheel.
170
+ * @default 'scroll'
171
+ */
172
+ wheelTool: z.string().max(32).default("scroll"),
173
+ /**
174
+ * Tool activated by mouse wheel with modifier key held.
175
+ * @default 'zoom'
176
+ */
177
+ modWheelTool: z.string().max(32).default("zoom")
178
+ });
179
+ var EditorOptionsSchema = z.object({
180
+ /**
181
+ * Plugins to load.
182
+ * Plugins are sorted by dependencies automatically.
183
+ */
184
+ plugins: z.array(z.custom()).default([]),
185
+ /**
186
+ * Maximum number of entities.
187
+ * @default 5_000
188
+ */
189
+ maxEntities: z.number().default(5e3),
190
+ /**
191
+ * User data for presence tracking.
192
+ * All fields are optional - defaults will be applied.
193
+ */
194
+ user: z.custom().default({}),
195
+ /**
196
+ * Grid configuration for snap-to-grid behavior.
197
+ * All fields are optional - defaults will be applied.
198
+ */
199
+ grid: z.custom().default({}),
200
+ /**
201
+ * Custom block definitions.
202
+ * Accepts partial block definitions - defaults will be applied automatically.
203
+ */
204
+ blockDefs: z.array(z.custom()).default([]),
205
+ /**
206
+ * Keybind definitions for keyboard shortcuts.
207
+ * These map key combinations to plugin commands.
208
+ */
209
+ keybinds: z.array(z.custom()).default([]),
210
+ /**
211
+ * Custom cursor definitions.
212
+ * Override default cursors or define new ones for transform operations.
213
+ */
214
+ cursors: z.record(z.string(), z.custom()).default({}),
215
+ /**
216
+ * Custom components to register without creating a plugin.
217
+ */
218
+ components: z.array(z.custom()).default([]),
219
+ /**
220
+ * Custom singletons to register without creating a plugin.
221
+ */
222
+ singletons: z.array(z.custom()).default([]),
223
+ /**
224
+ * Custom systems to register without creating a plugin.
225
+ * Each system specifies its phase and priority.
226
+ */
227
+ systems: z.array(z.custom()).default([]),
228
+ /**
229
+ * Custom font families to load and make available in the font selector.
230
+ * Fonts will be loaded automatically during editor initialization.
231
+ */
232
+ fonts: z.array(z.custom()).default([]),
233
+ /**
234
+ * If true, keybinds from plugins will be ignored.
235
+ * Use this when you want full control over keybinds.
236
+ */
237
+ omitPluginKeybinds: z.boolean().default(false),
238
+ /**
239
+ * If true, cursors from plugins will be ignored.
240
+ * Use this when you want full control over cursors.
241
+ */
242
+ omitPluginCursors: z.boolean().default(false),
243
+ /**
244
+ * If true, fonts from plugins will be ignored.
245
+ * Use this when you want full control over fonts.
246
+ */
247
+ omitPluginFonts: z.boolean().default(false),
248
+ /**
249
+ * Initial controls configuration.
250
+ * Override default tool mappings for mouse buttons, wheel, etc.
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * controls: {
255
+ * leftMouseTool: 'pen', // Start with pen tool active
256
+ * middleMouseTool: 'hand', // Middle mouse pans
257
+ * }
258
+ * ```
259
+ */
260
+ controls: z.custom().optional()
261
+ });
262
+ var Keybind = z.object({
263
+ /** The command to execute when this keybind is triggered */
264
+ command: z.string(),
265
+ /** The key index from the Key constants (e.g., Key.A, Key.Delete) */
266
+ key: z.number(),
267
+ /** Whether the modifier key (Cmd on Mac, Ctrl on Windows) must be held */
268
+ mod: z.boolean().optional(),
269
+ /** Whether the Shift key must be held */
270
+ shift: z.boolean().optional()
271
+ });
272
+ var CursorDef = z.object({
273
+ /** Function that generates the SVG string for a given rotation angle (in radians) */
274
+ makeSvg: z.function({ input: z.tuple([z.number()]), output: z.string() }),
275
+ /** Hotspot coordinates [x, y] for the cursor */
276
+ hotspot: z.tuple([z.number(), z.number()]),
277
+ /** Base rotation offset applied before the dynamic rotation */
278
+ rotationOffset: z.number()
279
+ });
280
+ var ResizeMode = {
281
+ Default: "default",
282
+ Scale: "scale",
283
+ Text: "text",
284
+ Free: "free",
285
+ GroupOnly: "groupOnly"
286
+ };
287
+ var VerticalAlignment = {
288
+ Top: "top",
289
+ Center: "center",
290
+ Bottom: "bottom"
291
+ };
292
+ var TextAlignment = {
293
+ Left: "left",
294
+ Center: "center",
295
+ Right: "right",
296
+ Justify: "justify"
297
+ };
298
+ var BlockDefEditOptions = z.object({
299
+ canEdit: z.boolean().default(false),
300
+ removeWhenTextEmpty: z.boolean().default(false)
301
+ });
302
+ var BlockDefConnectors = z.object({
303
+ /** Whether connectors are enabled for this block type */
304
+ enabled: z.boolean().default(true),
305
+ /** UV coordinates of terminal positions (0-1 range, where [0,0] is top-left) */
306
+ terminals: z.array(z.tuple([z.number(), z.number()])).default([
307
+ [0.5, 0],
308
+ // top
309
+ [0.5, 1],
310
+ // bottom
311
+ [0.5, 0.5],
312
+ // center
313
+ [0, 0.5],
314
+ // left
315
+ [1, 0.5]
316
+ // right
317
+ ])
318
+ });
319
+ var Stratum = z.enum(["background", "content", "overlay"]);
320
+ var BlockDef = z.object({
321
+ tag: z.string(),
322
+ stratum: Stratum.default("content"),
323
+ editOptions: BlockDefEditOptions.default(BlockDefEditOptions.parse({})),
324
+ components: z.array(z.custom()).default([]),
325
+ resizeMode: z.enum([ResizeMode.Scale, ResizeMode.Text, ResizeMode.Free, ResizeMode.GroupOnly]).default(ResizeMode.Scale),
326
+ canRotate: z.boolean().default(true),
327
+ canScale: z.boolean().default(true),
328
+ selectable: z.boolean().default(true),
329
+ connectors: BlockDefConnectors.default(BlockDefConnectors.parse({}))
330
+ });
331
+
332
+ // src/components/Block.ts
78
333
  var _aabbCorners = [
79
334
  [0, 0],
80
335
  [0, 0],
@@ -103,9 +358,11 @@ var BlockSchema = {
103
358
  /** Flip state as [flipX, flipY] */
104
359
  flip: field.tuple(field.boolean(), 2).default([false, false]),
105
360
  /** Z-order rank (LexoRank string) */
106
- rank: field.string().max(36).default("")
361
+ rank: field.string().max(36).default(""),
362
+ /** How the block can be resized */
363
+ resizeMode: field.enum(ResizeMode).default(ResizeMode.Default)
107
364
  };
108
- var BlockDef = class extends CanvasComponentDef {
365
+ var BlockDef2 = class extends CanvasComponentDef {
109
366
  constructor() {
110
367
  super({ name: "block", sync: "document" }, BlockSchema);
111
368
  }
@@ -247,7 +504,7 @@ var BlockDef = class extends CanvasComponentDef {
247
504
  }
248
505
  }
249
506
  };
250
- var Block = new BlockDef();
507
+ var Block = new BlockDef2();
251
508
 
252
509
  // src/components/Aabb.ts
253
510
  var LEFT = 0;
@@ -1030,253 +1287,6 @@ var Shape = defineCanvasComponent9(
1030
1287
  // src/components/Text.ts
1031
1288
  import { defineCanvasComponent as defineCanvasComponent10 } from "@woven-ecs/canvas-store";
1032
1289
  import { field as field13 } from "@woven-ecs/core";
1033
-
1034
- // src/types.ts
1035
- import { z } from "zod";
1036
- function generateUserColor() {
1037
- const colors = [
1038
- "#f43f5e",
1039
- // rose
1040
- "#ec4899",
1041
- // pink
1042
- "#a855f7",
1043
- // purple
1044
- "#6366f1",
1045
- // indigo
1046
- "#3b82f6",
1047
- // blue
1048
- "#0ea5e9",
1049
- // sky
1050
- "#14b8a6",
1051
- // teal
1052
- "#22c55e",
1053
- // green
1054
- "#eab308",
1055
- // yellow
1056
- "#f97316"
1057
- // orange
1058
- ];
1059
- return colors[Math.floor(Math.random() * colors.length)];
1060
- }
1061
- var UserData = z.object({
1062
- userId: z.string().max(36).default(() => crypto.randomUUID()),
1063
- sessionId: z.string().max(36).default(() => crypto.randomUUID()),
1064
- color: z.string().max(7).default(generateUserColor),
1065
- name: z.string().max(100).default("Anonymous"),
1066
- avatar: z.string().max(500).default("")
1067
- });
1068
- function getPluginResources(ctx, pluginName) {
1069
- const resources = ctx.resources;
1070
- return resources.pluginResources[pluginName];
1071
- }
1072
- var GridOptions = z.object({
1073
- /**
1074
- * Whether grid snapping is enabled.
1075
- * @default true
1076
- */
1077
- enabled: z.boolean().default(true),
1078
- /**
1079
- * Whether resized/rotated objects must stay aligned to the grid.
1080
- * If true, objects snap to grid during resize/rotate.
1081
- * If false, objects scale proportionally to the transform box, which may
1082
- * cause them to be unaligned with the grid.
1083
- * @default false
1084
- */
1085
- strict: z.boolean().default(false),
1086
- /**
1087
- * Width of each grid column in world units.
1088
- * @default 20
1089
- */
1090
- colWidth: z.number().nonnegative().default(20),
1091
- /**
1092
- * Height of each grid row in world units.
1093
- * @default 20
1094
- */
1095
- rowHeight: z.number().nonnegative().default(20),
1096
- /**
1097
- * Angular snap increment in radians when grid is enabled.
1098
- * @default Math.PI / 36 (5 degrees)
1099
- */
1100
- snapAngleRad: z.number().nonnegative().default(Math.PI / 36),
1101
- /**
1102
- * Angular snap increment in radians when shift key is held.
1103
- * @default Math.PI / 12 (15 degrees)
1104
- */
1105
- shiftSnapAngleRad: z.number().nonnegative().default(Math.PI / 12)
1106
- });
1107
- var ControlsOptions = z.object({
1108
- /**
1109
- * Tool activated by left mouse button.
1110
- * @default 'select'
1111
- */
1112
- leftMouseTool: z.string().max(32).default("select"),
1113
- /**
1114
- * Tool activated by middle mouse button.
1115
- * @default 'hand'
1116
- */
1117
- middleMouseTool: z.string().max(32).default("hand"),
1118
- /**
1119
- * Tool activated by right mouse button.
1120
- * @default 'menu'
1121
- */
1122
- rightMouseTool: z.string().max(32).default("menu"),
1123
- /**
1124
- * Tool activated by mouse wheel.
1125
- * @default 'scroll'
1126
- */
1127
- wheelTool: z.string().max(32).default("scroll"),
1128
- /**
1129
- * Tool activated by mouse wheel with modifier key held.
1130
- * @default 'zoom'
1131
- */
1132
- modWheelTool: z.string().max(32).default("zoom")
1133
- });
1134
- var EditorOptionsSchema = z.object({
1135
- /**
1136
- * Plugins to load.
1137
- * Plugins are sorted by dependencies automatically.
1138
- */
1139
- plugins: z.array(z.custom()).default([]),
1140
- /**
1141
- * Maximum number of entities.
1142
- * @default 5_000
1143
- */
1144
- maxEntities: z.number().default(5e3),
1145
- /**
1146
- * User data for presence tracking.
1147
- * All fields are optional - defaults will be applied.
1148
- */
1149
- user: z.custom().default({}),
1150
- /**
1151
- * Grid configuration for snap-to-grid behavior.
1152
- * All fields are optional - defaults will be applied.
1153
- */
1154
- grid: z.custom().default({}),
1155
- /**
1156
- * Custom block definitions.
1157
- * Accepts partial block definitions - defaults will be applied automatically.
1158
- */
1159
- blockDefs: z.array(z.custom()).default([]),
1160
- /**
1161
- * Keybind definitions for keyboard shortcuts.
1162
- * These map key combinations to plugin commands.
1163
- */
1164
- keybinds: z.array(z.custom()).default([]),
1165
- /**
1166
- * Custom cursor definitions.
1167
- * Override default cursors or define new ones for transform operations.
1168
- */
1169
- cursors: z.record(z.string(), z.custom()).default({}),
1170
- /**
1171
- * Custom components to register without creating a plugin.
1172
- */
1173
- components: z.array(z.custom()).default([]),
1174
- /**
1175
- * Custom singletons to register without creating a plugin.
1176
- */
1177
- singletons: z.array(z.custom()).default([]),
1178
- /**
1179
- * Custom systems to register without creating a plugin.
1180
- * Each system specifies its phase and priority.
1181
- */
1182
- systems: z.array(z.custom()).default([]),
1183
- /**
1184
- * Custom font families to load and make available in the font selector.
1185
- * Fonts will be loaded automatically during editor initialization.
1186
- */
1187
- fonts: z.array(z.custom()).default([]),
1188
- /**
1189
- * If true, keybinds from plugins will be ignored.
1190
- * Use this when you want full control over keybinds.
1191
- */
1192
- omitPluginKeybinds: z.boolean().default(false),
1193
- /**
1194
- * If true, cursors from plugins will be ignored.
1195
- * Use this when you want full control over cursors.
1196
- */
1197
- omitPluginCursors: z.boolean().default(false),
1198
- /**
1199
- * If true, fonts from plugins will be ignored.
1200
- * Use this when you want full control over fonts.
1201
- */
1202
- omitPluginFonts: z.boolean().default(false),
1203
- /**
1204
- * Initial controls configuration.
1205
- * Override default tool mappings for mouse buttons, wheel, etc.
1206
- *
1207
- * @example
1208
- * ```typescript
1209
- * controls: {
1210
- * leftMouseTool: 'pen', // Start with pen tool active
1211
- * middleMouseTool: 'hand', // Middle mouse pans
1212
- * }
1213
- * ```
1214
- */
1215
- controls: z.custom().optional()
1216
- });
1217
- var Keybind = z.object({
1218
- /** The command to execute when this keybind is triggered */
1219
- command: z.string(),
1220
- /** The key index from the Key constants (e.g., Key.A, Key.Delete) */
1221
- key: z.number(),
1222
- /** Whether the modifier key (Cmd on Mac, Ctrl on Windows) must be held */
1223
- mod: z.boolean().optional(),
1224
- /** Whether the Shift key must be held */
1225
- shift: z.boolean().optional()
1226
- });
1227
- var CursorDef = z.object({
1228
- /** Function that generates the SVG string for a given rotation angle (in radians) */
1229
- makeSvg: z.function({ input: z.tuple([z.number()]), output: z.string() }),
1230
- /** Hotspot coordinates [x, y] for the cursor */
1231
- hotspot: z.tuple([z.number(), z.number()]),
1232
- /** Base rotation offset applied before the dynamic rotation */
1233
- rotationOffset: z.number()
1234
- });
1235
- var VerticalAlignment = {
1236
- Top: "top",
1237
- Center: "center",
1238
- Bottom: "bottom"
1239
- };
1240
- var TextAlignment = {
1241
- Left: "left",
1242
- Center: "center",
1243
- Right: "right",
1244
- Justify: "justify"
1245
- };
1246
- var BlockDefEditOptions = z.object({
1247
- canEdit: z.boolean().default(false),
1248
- removeWhenTextEmpty: z.boolean().default(false)
1249
- });
1250
- var BlockDefConnectors = z.object({
1251
- /** Whether connectors are enabled for this block type */
1252
- enabled: z.boolean().default(true),
1253
- /** UV coordinates of terminal positions (0-1 range, where [0,0] is top-left) */
1254
- terminals: z.array(z.tuple([z.number(), z.number()])).default([
1255
- [0.5, 0],
1256
- // top
1257
- [0.5, 1],
1258
- // bottom
1259
- [0.5, 0.5],
1260
- // center
1261
- [0, 0.5],
1262
- // left
1263
- [1, 0.5]
1264
- // right
1265
- ])
1266
- });
1267
- var Stratum = z.enum(["background", "content", "overlay"]);
1268
- var BlockDef2 = z.object({
1269
- tag: z.string(),
1270
- stratum: Stratum.default("content"),
1271
- editOptions: BlockDefEditOptions.default(BlockDefEditOptions.parse({})),
1272
- components: z.array(z.custom()).default([]),
1273
- resizeMode: z.enum(["scale", "text", "free", "groupOnly"]).default("scale"),
1274
- canRotate: z.boolean().default(true),
1275
- canScale: z.boolean().default(true),
1276
- connectors: BlockDefConnectors.default(BlockDefConnectors.parse({}))
1277
- });
1278
-
1279
- // src/components/Text.ts
1280
1290
  var Text = defineCanvasComponent10(
1281
1291
  { name: "text", sync: "document" },
1282
1292
  {
@@ -1323,7 +1333,7 @@ var VerticalAlign = defineCanvasComponent12(
1323
1333
  );
1324
1334
 
1325
1335
  // src/constants.ts
1326
- var PLUGIN_NAME = "core";
1336
+ var CORE_PLUGIN_NAME = "core";
1327
1337
  var STRATUM_ORDER = {
1328
1338
  background: 0,
1329
1339
  content: 1,
@@ -1489,7 +1499,7 @@ var ControlsSchema = {
1489
1499
  /** Tool activated by mouse wheel with modifier key held */
1490
1500
  modWheelTool: field18.string().max(32).default("zoom"),
1491
1501
  /** JSON snapshot of block to place on next click (empty string = no placement active) */
1492
- heldSnapshot: field18.string().max(4096).default("")
1502
+ heldSnapshot: field18.string().max(65536).default("")
1493
1503
  };
1494
1504
  var ControlsDef = class extends CanvasSingletonDef3 {
1495
1505
  constructor() {
@@ -2061,7 +2071,9 @@ var MouseSchema = {
2061
2071
  /** True for 1 frame when mouse enters the editor element */
2062
2072
  enterTrigger: field24.boolean().default(false),
2063
2073
  /** True for 1 frame when mouse leaves the editor element */
2064
- leaveTrigger: field24.boolean().default(false)
2074
+ leaveTrigger: field24.boolean().default(false),
2075
+ /** Whether the mouse is over a UI element (not the canvas) */
2076
+ obscured: field24.boolean().default(false)
2065
2077
  };
2066
2078
  var MouseDef = class extends CanvasSingletonDef8 {
2067
2079
  constructor() {
@@ -2093,6 +2105,10 @@ var MouseDef = class extends CanvasSingletonDef8 {
2093
2105
  const m = this.read(ctx);
2094
2106
  return [m.wheelDeltaX, m.wheelDeltaY];
2095
2107
  }
2108
+ /** Check if mouse is over a UI element (not the canvas) */
2109
+ isObscured(ctx) {
2110
+ return this.read(ctx).obscured;
2111
+ }
2096
2112
  };
2097
2113
  var Mouse = new MouseDef();
2098
2114
 
@@ -2385,7 +2401,8 @@ function attachMouseListeners(domElement) {
2385
2401
  state.eventsBuffer.push({
2386
2402
  type: "mousemove",
2387
2403
  clientX: e.clientX,
2388
- clientY: e.clientY
2404
+ clientY: e.clientY,
2405
+ target: e.target
2389
2406
  });
2390
2407
  },
2391
2408
  onWheel: (e) => {
@@ -2442,6 +2459,7 @@ var mouseSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
2442
2459
  case "mousemove":
2443
2460
  mouse.position = [event.clientX - screen.left, event.clientY - screen.top];
2444
2461
  mouse.moveTrigger = true;
2462
+ mouse.obscured = event.target !== resources.domElement;
2445
2463
  break;
2446
2464
  case "wheel":
2447
2465
  mouse.wheelDeltaX = event.deltaX;
@@ -2701,11 +2719,22 @@ function getBlockDefs(ctx) {
2701
2719
  }
2702
2720
  function getBlockDef(ctx, tag) {
2703
2721
  const blockDefs = getBlockDefs(ctx);
2704
- return blockDefs[tag] ?? BlockDef2.parse({ tag });
2722
+ return blockDefs[tag] ?? BlockDef.parse({ tag });
2705
2723
  }
2706
2724
  function canBlockEdit(ctx, tag) {
2707
2725
  return getBlockDef(ctx, tag).editOptions.canEdit;
2708
2726
  }
2727
+ function canBlockSelect(ctx, tag) {
2728
+ return getBlockDef(ctx, tag).selectable;
2729
+ }
2730
+ function getBlockResizeMode(ctx, entityId) {
2731
+ const block = Block.read(ctx, entityId);
2732
+ if (block.resizeMode !== ResizeMode.Default) {
2733
+ return block.resizeMode;
2734
+ }
2735
+ const blockDef = getBlockDef(ctx, block.tag);
2736
+ return blockDef.resizeMode;
2737
+ }
2709
2738
 
2710
2739
  // src/helpers/computeAabb.ts
2711
2740
  import { Aabb as Aabb2 } from "@woven-canvas/math";
@@ -2906,7 +2935,7 @@ function updateUserPosition(ctx, position) {
2906
2935
  }
2907
2936
 
2908
2937
  // src/systems/preCapture/intersectSystem.ts
2909
- import { addComponent as addComponent4, defineQuery as defineQuery6, hasComponent as hasComponent5, removeComponent } from "@woven-ecs/core";
2938
+ import { addComponent as addComponent4, defineQuery as defineQuery6, getResources as getResources11, hasComponent as hasComponent5, removeComponent } from "@woven-ecs/core";
2910
2939
  var blocksChanged = defineQuery6((q) => q.tracking(Block));
2911
2940
  var hitGeometryChanged = defineQuery6((q) => q.tracking(HitGeometry));
2912
2941
  var heldQuery = defineQuery6((q) => q.with(Held).tracking(Held));
@@ -2945,6 +2974,7 @@ function arraysEqual(a, b) {
2945
2974
  }
2946
2975
  var added = /* @__PURE__ */ new Set();
2947
2976
  var changed = /* @__PURE__ */ new Set();
2977
+ var prevObscuredByElement = /* @__PURE__ */ new WeakMap();
2948
2978
  var intersectSystem = defineEditorSystem({ phase: "capture", priority: 100 }, (ctx) => {
2949
2979
  added.clear();
2950
2980
  for (const entityId of blocksChanged.added(ctx)) {
@@ -2986,6 +3016,15 @@ var intersectSystem = defineEditorSystem({ phase: "capture", priority: 100 }, (c
2986
3016
  clearHovered(ctx);
2987
3017
  return;
2988
3018
  }
3019
+ const resources = getResources11(ctx);
3020
+ const isObscured = Mouse.isObscured(ctx);
3021
+ const wasObscured = prevObscuredByElement.get(resources.domElement) ?? false;
3022
+ const obscuredChanged = isObscured !== wasObscured;
3023
+ prevObscuredByElement.set(resources.domElement, isObscured);
3024
+ if (isObscured) {
3025
+ clearHovered(ctx);
3026
+ return;
3027
+ }
2989
3028
  const mousePos = Mouse.getPosition(ctx);
2990
3029
  const worldPos = Camera.toWorld(ctx, mousePos);
2991
3030
  const intersected = intersectPoint(ctx, worldPos);
@@ -2998,7 +3037,7 @@ var intersectSystem = defineEditorSystem({ phase: "capture", priority: 100 }, (c
2998
3037
  if (pointers.length > 0) {
2999
3038
  return;
3000
3039
  }
3001
- if (intersectsChanged || heldChanged) {
3040
+ if (intersectsChanged || heldChanged || obscuredChanged) {
3002
3041
  updateHovered(ctx, intersected);
3003
3042
  }
3004
3043
  });
@@ -3100,7 +3139,7 @@ function scaleBlock(ctx, entityId, zoom) {
3100
3139
 
3101
3140
  // src/CorePlugin.ts
3102
3141
  var CorePlugin = {
3103
- name: PLUGIN_NAME,
3142
+ name: CORE_PLUGIN_NAME,
3104
3143
  singletons: Object.values(singletons_exports).filter((v) => v instanceof CanvasSingletonDef10),
3105
3144
  components: Object.values(components_exports).filter((v) => v instanceof CanvasComponentDef6),
3106
3145
  blockDefs: [
@@ -3114,7 +3153,7 @@ var CorePlugin = {
3114
3153
  {
3115
3154
  tag: "text",
3116
3155
  components: [Text],
3117
- resizeMode: "text",
3156
+ resizeMode: ResizeMode.Text,
3118
3157
  editOptions: {
3119
3158
  canEdit: true,
3120
3159
  removeWhenTextEmpty: true
@@ -3123,12 +3162,12 @@ var CorePlugin = {
3123
3162
  {
3124
3163
  tag: "image",
3125
3164
  components: [Image, Asset],
3126
- resizeMode: "scale"
3165
+ resizeMode: ResizeMode.Scale
3127
3166
  },
3128
3167
  {
3129
3168
  tag: "shape",
3130
3169
  components: [Shape, Text, VerticalAlign],
3131
- resizeMode: "free",
3170
+ resizeMode: ResizeMode.Free,
3132
3171
  editOptions: {
3133
3172
  canEdit: true
3134
3173
  }
@@ -3159,14 +3198,14 @@ var CorePlugin = {
3159
3198
  // priority: -100
3160
3199
  ],
3161
3200
  setup(ctx) {
3162
- const { domElement } = getResources11(ctx);
3201
+ const { domElement } = getResources12(ctx);
3163
3202
  attachKeyboardListeners(domElement);
3164
3203
  attachMouseListeners(domElement);
3165
3204
  attachScreenObserver(domElement);
3166
3205
  attachPointerListeners(domElement);
3167
3206
  },
3168
3207
  teardown(ctx) {
3169
- const { domElement } = getResources11(ctx);
3208
+ const { domElement } = getResources12(ctx);
3170
3209
  detachKeyboardListeners(domElement);
3171
3210
  detachMouseListeners(domElement);
3172
3211
  detachScreenObserver(domElement);
@@ -3419,13 +3458,13 @@ var Editor = class {
3419
3458
  this.keybinds = keybinds;
3420
3459
  const blockDefs = {};
3421
3460
  for (const input of options.blockDefs) {
3422
- const parsed = BlockDef2.parse(input);
3461
+ const parsed = BlockDef.parse(input);
3423
3462
  blockDefs[parsed.tag] = parsed;
3424
3463
  }
3425
3464
  for (const plugin of sortedPlugins) {
3426
3465
  if (plugin.blockDefs) {
3427
3466
  for (const input of plugin.blockDefs) {
3428
- const parsed = BlockDef2.parse(input);
3467
+ const parsed = BlockDef.parse(input);
3429
3468
  blockDefs[parsed.tag] = parsed;
3430
3469
  }
3431
3470
  }
@@ -3950,7 +3989,7 @@ export {
3950
3989
  Aabb,
3951
3990
  Asset,
3952
3991
  Block,
3953
- BlockDef2 as BlockDef,
3992
+ BlockDef,
3954
3993
  Camera,
3955
3994
  CanvasComponentDef7 as CanvasComponentDef,
3956
3995
  CanvasSingletonDef12 as CanvasSingletonDef,
@@ -3991,6 +4030,7 @@ export {
3991
4030
  RankBounds,
3992
4031
  Redo,
3993
4032
  ResetKeyboard,
4033
+ ResizeMode,
3994
4034
  SINGLETON_ENTITY_ID,
3995
4035
  STRATUM_ORDER,
3996
4036
  ScaleWithZoom,
@@ -4010,6 +4050,7 @@ export {
4010
4050
  VerticalAlignment,
4011
4051
  addComponent6 as addComponent,
4012
4052
  canBlockEdit,
4053
+ canBlockSelect,
4013
4054
  clearPointerTrackingState,
4014
4055
  createEntity5 as createEntity,
4015
4056
  defineCanvasComponent13 as defineCanvasComponent,
@@ -4022,12 +4063,13 @@ export {
4022
4063
  field28 as field,
4023
4064
  getBackrefs,
4024
4065
  getBlockDef,
4066
+ getBlockResizeMode,
4025
4067
  getFrameInput,
4026
4068
  getKeyboardInput,
4027
4069
  getMouseInput,
4028
4070
  getPluginResources,
4029
4071
  getPointerInput,
4030
- getResources12 as getResources,
4072
+ getResources13 as getResources,
4031
4073
  getTopmostBlockAtPoint,
4032
4074
  hasComponent7 as hasComponent,
4033
4075
  intersectAabb,