@vectojs/core 1.8.0 → 1.9.1

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.
@@ -372,10 +372,18 @@ var Entity = (_class4 = class {
372
372
  * `child.parent` is only ever `null` or `this`'s ultimate owner, so this
373
373
  * check is O(1) for the overwhelming common case (a brand-new entity).
374
374
  *
375
- * @param child - The entity to add as a child.
375
+ * Accepts multiple children in one call (`parent.add(a, b, c)`); each is
376
+ * attached in argument order with the same detach-then-attach semantics.
377
+ *
378
+ * @param children - One or more entities to add as children.
376
379
  * @returns `this` for method chaining.
377
380
  */
378
- add(child) {
381
+ add(...children) {
382
+ for (const child of children) this._addOne(child);
383
+ return this;
384
+ }
385
+ /** Attach a single child (the O(1) common path). See {@link add}. */
386
+ _addOne(child) {
379
387
  if (child.parent) child.parent.remove(child);
380
388
  child.parent = this;
381
389
  this.children.push(child);
@@ -385,7 +393,6 @@ var Entity = (_class4 = class {
385
393
  s.markDirty();
386
394
  child._notifyMounted();
387
395
  }
388
- return this;
389
396
  }
390
397
  /** Called once when this entity becomes attached to a live Scene. Override to react. */
391
398
  onMounted() {
@@ -417,6 +424,24 @@ var Entity = (_class4 = class {
417
424
  }
418
425
  return this;
419
426
  }
427
+ /**
428
+ * Assign several own properties in one call, each through its normal setter
429
+ * (so a property with a configured {@link setTransition} still animates, and
430
+ * `interactive` still flags the scene for a11y reorder). A construction-time
431
+ * ergonomic only — it is a plain `for…in` over the given object and touches
432
+ * no per-frame path.
433
+ *
434
+ * @param props - Partial set of this entity's own properties to assign.
435
+ * @returns `this` for method chaining.
436
+ * @example rect.set({ x: 40, y: 40, width: 120, fill: '#38bdf8' });
437
+ */
438
+ set(props) {
439
+ for (const key in props) {
440
+ const value = props[key];
441
+ if (value !== void 0) this[key] = value;
442
+ }
443
+ return this;
444
+ }
420
445
  /**
421
446
  * Set the local position of this entity.
422
447
  *
@@ -372,10 +372,18 @@ var Entity = class {
372
372
  * `child.parent` is only ever `null` or `this`'s ultimate owner, so this
373
373
  * check is O(1) for the overwhelming common case (a brand-new entity).
374
374
  *
375
- * @param child - The entity to add as a child.
375
+ * Accepts multiple children in one call (`parent.add(a, b, c)`); each is
376
+ * attached in argument order with the same detach-then-attach semantics.
377
+ *
378
+ * @param children - One or more entities to add as children.
376
379
  * @returns `this` for method chaining.
377
380
  */
378
- add(child) {
381
+ add(...children) {
382
+ for (const child of children) this._addOne(child);
383
+ return this;
384
+ }
385
+ /** Attach a single child (the O(1) common path). See {@link add}. */
386
+ _addOne(child) {
379
387
  if (child.parent) child.parent.remove(child);
380
388
  child.parent = this;
381
389
  this.children.push(child);
@@ -385,7 +393,6 @@ var Entity = class {
385
393
  s.markDirty();
386
394
  child._notifyMounted();
387
395
  }
388
- return this;
389
396
  }
390
397
  /** Called once when this entity becomes attached to a live Scene. Override to react. */
391
398
  onMounted() {
@@ -417,6 +424,24 @@ var Entity = class {
417
424
  }
418
425
  return this;
419
426
  }
427
+ /**
428
+ * Assign several own properties in one call, each through its normal setter
429
+ * (so a property with a configured {@link setTransition} still animates, and
430
+ * `interactive` still flags the scene for a11y reorder). A construction-time
431
+ * ergonomic only — it is a plain `for…in` over the given object and touches
432
+ * no per-frame path.
433
+ *
434
+ * @param props - Partial set of this entity's own properties to assign.
435
+ * @returns `this` for method chaining.
436
+ * @example rect.set({ x: 40, y: 40, width: 120, fill: '#38bdf8' });
437
+ */
438
+ set(props) {
439
+ for (const key in props) {
440
+ const value = props[key];
441
+ if (value !== void 0) this[key] = value;
442
+ }
443
+ return this;
444
+ }
420
445
  /**
421
446
  * Set the local position of this entity.
422
447
  *
@@ -0,0 +1,48 @@
1
+ import { Entity, type Bounds, type BatchCircle } from '../tree/Entity';
2
+ import type { IRenderer } from '../renderer/IRenderer';
3
+ /** Construction options for {@link Circle}. */
4
+ export interface CircleOptions {
5
+ /** Radius in local units. Default `0`. */
6
+ radius?: number;
7
+ /** CSS fill color, or `null` for no fill. Default `'#38bdf8'`. */
8
+ fill?: string | null;
9
+ /** CSS stroke color, or `null` for no stroke. Default `null`. */
10
+ stroke?: string | null;
11
+ /** Stroke width in local units. Default `1`. */
12
+ strokeWidth?: number;
13
+ }
14
+ /**
15
+ * A concrete circle primitive centered on its local origin `(0, 0)`.
16
+ * Instantiate directly — no subclassing:
17
+ *
18
+ * @example
19
+ * const dot = new Circle({ radius: 24, fill: '#f97316' });
20
+ * dot.set({ x: 100, y: 100 });
21
+ * scene.add(dot);
22
+ *
23
+ * The accessibility shadow box is sized to the circle's bounding square and
24
+ * offset by `-radius` so it covers the drawn disc (whose center is the entity
25
+ * origin). A solid-fill (unstroked) Circle opts into the point-batch fast path
26
+ * via {@link getBatchCircle}; a stroked circle renders through the normal path.
27
+ */
28
+ export declare class Circle extends Entity {
29
+ fill: string | null;
30
+ stroke: string | null;
31
+ strokeWidth: number;
32
+ private _radius;
33
+ constructor(opts?: CircleOptions);
34
+ get radius(): number;
35
+ set radius(v: number);
36
+ /** Keep the a11y box (a square around the centered disc) in sync with radius. */
37
+ private syncBox;
38
+ getBounds(): Bounds;
39
+ /**
40
+ * A solid-fill, unstroked circle opts into the renderer's circle batch
41
+ * (center = entity origin, radius scaled by world scale). A stroke needs the
42
+ * exact Canvas path, so return `null` there. Read each frame, so an animated
43
+ * `fill`/`radius` still batches.
44
+ */
45
+ getBatchCircle(): BatchCircle | null;
46
+ isPointInside(globalX: number, globalY: number): boolean;
47
+ render(renderer: IRenderer): void;
48
+ }
@@ -0,0 +1,24 @@
1
+ import { Entity } from '../tree/Entity';
2
+ import type { IRenderer } from '../renderer/IRenderer';
3
+ /**
4
+ * A transform-only container: it draws nothing itself and is invisible to
5
+ * hit-testing, existing solely to compose one transform (`x`/`y`/`scale`/
6
+ * `rotation`/`opacity`) onto a set of children. Instantiate directly and pass
7
+ * children inline:
8
+ *
9
+ * @example
10
+ * const toolbar = new Group(saveBtn, undoBtn, redoBtn);
11
+ * toolbar.set({ x: 20, y: 20 });
12
+ * scene.add(toolbar);
13
+ *
14
+ * `isPointInside` returns `false` so the group never becomes the pick target;
15
+ * `Scene`'s hit-test recurses into children before testing a parent, so the
16
+ * children remain independently interactive. `render` is a no-op — children are
17
+ * drawn by the scene's normal tree walk under this group's accumulated
18
+ * transform.
19
+ */
20
+ export declare class Group extends Entity {
21
+ constructor(...children: Entity[]);
22
+ isPointInside(): boolean;
23
+ render(_renderer: IRenderer): void;
24
+ }
@@ -0,0 +1,48 @@
1
+ import { Entity, type Bounds, type BatchRect } from '../tree/Entity';
2
+ import type { IRenderer } from '../renderer/IRenderer';
3
+ /** Construction options for {@link Rect}. */
4
+ export interface RectOptions {
5
+ /** Width in local units. Default `0`. */
6
+ width?: number;
7
+ /** Height in local units. Default `0`. */
8
+ height?: number;
9
+ /** CSS fill color, or `null` for no fill. Default `'#38bdf8'`. */
10
+ fill?: string | null;
11
+ /** CSS stroke color, or `null` for no stroke. Default `null`. */
12
+ stroke?: string | null;
13
+ /** Stroke width in local units. Default `1`. */
14
+ strokeWidth?: number;
15
+ /** Corner radius in local units (uniform). Default `0` (sharp corners). */
16
+ radius?: number;
17
+ }
18
+ /**
19
+ * A concrete axis-aligned rectangle primitive drawn from its local origin
20
+ * `(0, 0)` to `(width, height)`. Instantiate directly — no subclassing:
21
+ *
22
+ * @example
23
+ * const box = new Rect({ width: 120, height: 64, fill: '#38bdf8', radius: 8 });
24
+ * box.set({ x: 40, y: 40 });
25
+ * scene.add(box);
26
+ *
27
+ * The box matches the entity's `width`/`height`, so its accessibility shadow
28
+ * node lines up with what's drawn. A solid-fill, non-rounded, unstroked Rect
29
+ * opts into the WebGL instanced-rectangle fast path via {@link getBatchRect};
30
+ * rounded or stroked rectangles render through the normal Canvas path.
31
+ */
32
+ export declare class Rect extends Entity {
33
+ fill: string | null;
34
+ stroke: string | null;
35
+ strokeWidth: number;
36
+ radius: number;
37
+ constructor(opts?: RectOptions);
38
+ getBounds(): Bounds;
39
+ /**
40
+ * Solid-fill, square-cornered, unstroked rectangles opt into the GPU
41
+ * instanced-rect batch (WebGL `pointBackend` only). Any stroke or corner
42
+ * radius needs the exact Canvas path, so return `null` to fall back to
43
+ * {@link render}. Read each frame, so an animated `fill` still batches.
44
+ */
45
+ getBatchRect(): BatchRect | null;
46
+ isPointInside(globalX: number, globalY: number): boolean;
47
+ render(renderer: IRenderer): void;
48
+ }
package/dist/index.d.ts CHANGED
@@ -10,6 +10,9 @@ export * from './tree/Scene';
10
10
  export * from './components/TextEntity';
11
11
  export * from './components/GridTextEntity';
12
12
  export * from './components/SplineEntity';
13
+ export * from './components/Rect';
14
+ export * from './components/Circle';
15
+ export * from './components/Group';
13
16
  export * from './layout/LayoutEngine';
14
17
  export * from './layout/measure';
15
18
  export * from './text/MSDFFont';
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ var _chunkBPMNCGU7js = require('./chunk-BPMNCGU7.js');
27
27
 
28
28
 
29
29
 
30
- var _chunkFIQAIF55js = require('./chunk-FIQAIF55.js');
30
+ var _chunkGKLTTGBRjs = require('./chunk-GKLTTGBR.js');
31
31
 
32
32
 
33
33
 
@@ -44,7 +44,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
44
44
  var PARTICLE_OFFSET_ORIGIN_Y = 5;
45
45
  var PARTICLE_OFFSET_SIZE = 6;
46
46
  var PARTICLE_OFFSET_LIFE = 7;
47
- var ComputeParticleEntity = (_class = class extends _chunkFIQAIF55js.Entity {
47
+ var ComputeParticleEntity = (_class = class extends _chunkGKLTTGBRjs.Entity {
48
48
 
49
49
 
50
50
 
@@ -755,7 +755,7 @@ var Scene = (_class2 = class _Scene {
755
755
  this.a11ySyncInterval = _nullishCoalesce(options.a11ySyncInterval, () => ( 0));
756
756
  this.contentProjectionEnabled = _nullishCoalesce(options.contentProjection, () => ( true));
757
757
  this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
758
- this.root = new class RootEntity extends _chunkFIQAIF55js.Entity {
758
+ this.root = new class RootEntity extends _chunkGKLTTGBRjs.Entity {
759
759
  isPointInside() {
760
760
  return false;
761
761
  }
@@ -764,7 +764,7 @@ var Scene = (_class2 = class _Scene {
764
764
  }
765
765
  }("root");
766
766
  this.root._scene = this;
767
- this.overlayRoot = new class OverlayRoot extends _chunkFIQAIF55js.Entity {
767
+ this.overlayRoot = new class OverlayRoot extends _chunkGKLTTGBRjs.Entity {
768
768
  isPointInside() {
769
769
  return false;
770
770
  }
@@ -915,7 +915,7 @@ var Scene = (_class2 = class _Scene {
915
915
  };
916
916
  if (typeof document !== "undefined" && document.fonts) {
917
917
  this.fontLoadHandler = () => {
918
- _chunkFIQAIF55js.clearCssLineBoxMetrics.call(void 0, );
918
+ _chunkGKLTTGBRjs.clearCssLineBoxMetrics.call(void 0, );
919
919
  this.contentFontEpoch++;
920
920
  this.markDirty();
921
921
  };
@@ -1214,6 +1214,13 @@ var Scene = (_class2 = class _Scene {
1214
1214
  }
1215
1215
  /** True when any node in the subtree has a pending animation. */
1216
1216
  /** True when any node in the subtree is interactive (drives a11y sync). */
1217
+ syncOptionalAttribute(element, name, value) {
1218
+ if (value === void 0) {
1219
+ if (element.hasAttribute(name)) element.removeAttribute(name);
1220
+ return;
1221
+ }
1222
+ if (element.getAttribute(name) !== value) element.setAttribute(name, value);
1223
+ }
1217
1224
  syncA11y(node) {
1218
1225
  if (!this.a11yRoot) return;
1219
1226
  if (node.isDOMPortal) {
@@ -1259,15 +1266,15 @@ var Scene = (_class2 = class _Scene {
1259
1266
  el.style.background = "transparent";
1260
1267
  }
1261
1268
  el.addEventListener("click", (e) => {
1262
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("click", node, e));
1269
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("click", node, e));
1263
1270
  });
1264
1271
  el.addEventListener("mouseenter", (e) => {
1265
1272
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
1266
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("hover", node, e, false));
1273
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("hover", node, e, false));
1267
1274
  });
1268
1275
  el.addEventListener("mouseleave", (e) => {
1269
1276
  if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
1270
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("pointerleave", node, e, false));
1277
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointerleave", node, e, false));
1271
1278
  });
1272
1279
  const capEl = el;
1273
1280
  const releasePointer = (event) => {
@@ -1283,32 +1290,32 @@ var Scene = (_class2 = class _Scene {
1283
1290
  };
1284
1291
  el.addEventListener("pointerdown", (e) => {
1285
1292
  if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
1286
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("pointerdown", node, e));
1293
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointerdown", node, e));
1287
1294
  });
1288
1295
  el.addEventListener("pointerup", (e) => {
1289
1296
  releasePointer(e);
1290
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("pointerup", node, e));
1297
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointerup", node, e));
1291
1298
  });
1292
1299
  el.addEventListener("pointercancel", (e) => {
1293
1300
  releasePointer(e);
1294
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("pointercancel", node, e));
1301
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointercancel", node, e));
1295
1302
  });
1296
1303
  el.addEventListener(
1297
1304
  "pointermove",
1298
- (e) => node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("pointermove", node, e))
1305
+ (e) => node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("pointermove", node, e))
1299
1306
  );
1300
1307
  el.addEventListener(
1301
1308
  "wheel",
1302
1309
  (e) => {
1303
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("wheel", node, e));
1310
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("wheel", node, e));
1304
1311
  },
1305
1312
  { passive: false }
1306
1313
  );
1307
1314
  el.addEventListener("keydown", (e) => {
1308
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("keydown", node, e));
1315
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("keydown", node, e));
1309
1316
  });
1310
1317
  el.addEventListener("keyup", (e) => {
1311
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("keyup", node, e));
1318
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("keyup", node, e));
1312
1319
  });
1313
1320
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
1314
1321
  const input = el;
@@ -1368,7 +1375,7 @@ var Scene = (_class2 = class _Scene {
1368
1375
  el.addEventListener("keydown", (e) => {
1369
1376
  if (e.key === "Enter" || e.key === " ") {
1370
1377
  e.preventDefault();
1371
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("click", node, e));
1378
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("click", node, e));
1372
1379
  }
1373
1380
  });
1374
1381
  }
@@ -1380,12 +1387,8 @@ var Scene = (_class2 = class _Scene {
1380
1387
  this.a11yElements.set(node.id, el);
1381
1388
  this.a11yNeedsReorder = true;
1382
1389
  }
1383
- if (attrs.role !== void 0 && el.getAttribute("role") !== attrs.role) {
1384
- el.setAttribute("role", attrs.role);
1385
- }
1386
- if (attrs.label !== void 0 && el.getAttribute("aria-label") !== attrs.label) {
1387
- el.setAttribute("aria-label", attrs.label);
1388
- }
1390
+ this.syncOptionalAttribute(el, "role", attrs.role);
1391
+ this.syncOptionalAttribute(el, "aria-label", attrs.label);
1389
1392
  const semanticPointerEvents = _nullishCoalesce(attrs.pointerEvents, () => ( "auto"));
1390
1393
  if (el.style.pointerEvents !== semanticPointerEvents) {
1391
1394
  el.style.pointerEvents = semanticPointerEvents;
@@ -1397,58 +1400,58 @@ var Scene = (_class2 = class _Scene {
1397
1400
  } else if (el.getAttribute("tabindex") !== String(desiredTabIndex)) {
1398
1401
  el.setAttribute("tabindex", String(desiredTabIndex));
1399
1402
  }
1400
- if (attrs.inputType !== void 0 && el.getAttribute("type") !== attrs.inputType) {
1401
- el.setAttribute("type", attrs.inputType);
1403
+ this.syncOptionalAttribute(el, "type", attrs.inputType);
1404
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
1405
+ const placeholder = _nullishCoalesce(attrs.placeholder, () => ( ""));
1406
+ if (el.placeholder !== placeholder) el.placeholder = placeholder;
1402
1407
  }
1403
- if (attrs.placeholder !== void 0 && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1404
- if (el.placeholder !== attrs.placeholder) el.placeholder = attrs.placeholder;
1405
- }
1406
- if (attrs.href !== void 0 && el instanceof HTMLAnchorElement) {
1407
- const safeHref = _chunkBPMNCGU7js.sanitizeUrl.call(void 0, attrs.href);
1408
- if (el.getAttribute("href") !== safeHref) el.setAttribute("href", safeHref);
1409
- if (attrs.target !== void 0 && el.getAttribute("target") !== attrs.target) {
1410
- el.setAttribute("target", attrs.target);
1411
- }
1408
+ if (el instanceof HTMLAnchorElement) {
1409
+ this.syncOptionalAttribute(
1410
+ el,
1411
+ "href",
1412
+ attrs.href === void 0 ? void 0 : _chunkBPMNCGU7js.sanitizeUrl.call(void 0, attrs.href)
1413
+ );
1414
+ this.syncOptionalAttribute(el, "target", attrs.target);
1412
1415
  }
1413
1416
  if (el instanceof HTMLImageElement) {
1414
- if (attrs.src !== void 0 && el.src !== attrs.src) el.src = attrs.src;
1415
- if (attrs.alt !== void 0 && el.alt !== attrs.alt) el.alt = attrs.alt;
1416
- }
1417
- if (attrs.checked !== void 0) {
1418
- if (el instanceof HTMLInputElement) {
1419
- if (el.checked !== attrs.checked) el.checked = attrs.checked;
1420
- } else if (el.getAttribute("aria-checked") !== String(attrs.checked)) {
1421
- el.setAttribute("aria-checked", String(attrs.checked));
1422
- }
1417
+ this.syncOptionalAttribute(el, "src", attrs.src);
1418
+ this.syncOptionalAttribute(el, "alt", attrs.alt);
1423
1419
  }
1424
- if (attrs.disabled !== void 0) {
1425
- if ("disabled" in el) {
1426
- if (el.disabled !== attrs.disabled) el.disabled = attrs.disabled;
1427
- } else if (el.getAttribute("aria-disabled") !== String(attrs.disabled)) {
1428
- el.setAttribute("aria-disabled", String(attrs.disabled));
1429
- }
1430
- }
1431
- if (attrs.expanded !== void 0 && el.getAttribute("aria-expanded") !== String(attrs.expanded)) {
1432
- el.setAttribute("aria-expanded", String(attrs.expanded));
1433
- }
1434
- if (attrs.controls !== void 0 && el.getAttribute("aria-controls") !== attrs.controls) {
1435
- el.setAttribute("aria-controls", attrs.controls);
1436
- }
1437
- if (attrs.haspopup !== void 0 && el.getAttribute("aria-haspopup") !== attrs.haspopup) {
1438
- el.setAttribute("aria-haspopup", attrs.haspopup);
1439
- }
1440
- if (attrs.selected !== void 0 && el.getAttribute("aria-selected") !== String(attrs.selected)) {
1441
- el.setAttribute("aria-selected", String(attrs.selected));
1442
- }
1443
- if (attrs.activedescendant !== void 0 && el.getAttribute("aria-activedescendant") !== attrs.activedescendant) {
1444
- el.setAttribute("aria-activedescendant", attrs.activedescendant);
1445
- }
1446
- if (attrs.valuemin !== void 0 && el.getAttribute("aria-valuemin") !== attrs.valuemin) {
1447
- el.setAttribute("aria-valuemin", attrs.valuemin);
1420
+ if (el instanceof HTMLInputElement) {
1421
+ const checked = _nullishCoalesce(attrs.checked, () => ( false));
1422
+ if (el.checked !== checked) el.checked = checked;
1423
+ } else {
1424
+ this.syncOptionalAttribute(
1425
+ el,
1426
+ "aria-checked",
1427
+ attrs.checked === void 0 ? void 0 : String(attrs.checked)
1428
+ );
1448
1429
  }
1449
- if (attrs.valuemax !== void 0 && el.getAttribute("aria-valuemax") !== attrs.valuemax) {
1450
- el.setAttribute("aria-valuemax", attrs.valuemax);
1430
+ if ("disabled" in el) {
1431
+ const disabled = _nullishCoalesce(attrs.disabled, () => ( false));
1432
+ if (el.disabled !== disabled) el.disabled = disabled;
1433
+ } else {
1434
+ this.syncOptionalAttribute(
1435
+ el,
1436
+ "aria-disabled",
1437
+ attrs.disabled === void 0 ? void 0 : String(attrs.disabled)
1438
+ );
1451
1439
  }
1440
+ this.syncOptionalAttribute(
1441
+ el,
1442
+ "aria-expanded",
1443
+ attrs.expanded === void 0 ? void 0 : String(attrs.expanded)
1444
+ );
1445
+ this.syncOptionalAttribute(el, "aria-controls", attrs.controls);
1446
+ this.syncOptionalAttribute(el, "aria-haspopup", attrs.haspopup);
1447
+ this.syncOptionalAttribute(
1448
+ el,
1449
+ "aria-selected",
1450
+ attrs.selected === void 0 ? void 0 : String(attrs.selected)
1451
+ );
1452
+ this.syncOptionalAttribute(el, "aria-activedescendant", attrs.activedescendant);
1453
+ this.syncOptionalAttribute(el, "aria-valuemin", attrs.valuemin);
1454
+ this.syncOptionalAttribute(el, "aria-valuemax", attrs.valuemax);
1452
1455
  if (attrs.value !== void 0) {
1453
1456
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
1454
1457
  if (el.value !== attrs.value) {
@@ -1458,9 +1461,11 @@ var Scene = (_class2 = class _Scene {
1458
1461
  el._lastSyncedValue = attrs.value;
1459
1462
  }
1460
1463
  }
1461
- } else if (el.getAttribute("aria-valuenow") !== attrs.value) {
1462
- el.setAttribute("aria-valuenow", attrs.value);
1464
+ } else {
1465
+ this.syncOptionalAttribute(el, "aria-valuenow", attrs.value);
1463
1466
  }
1467
+ } else if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1468
+ this.syncOptionalAttribute(el, "aria-valuenow", void 0);
1464
1469
  }
1465
1470
  if (attrs.textInputStyle !== void 0 && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1466
1471
  const textStyle = attrs.textInputStyle;
@@ -1529,7 +1534,7 @@ var Scene = (_class2 = class _Scene {
1529
1534
  el.addEventListener(
1530
1535
  "wheel",
1531
1536
  (e2) => {
1532
- node.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)("wheel", node, e2));
1537
+ node.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)("wheel", node, e2));
1533
1538
  },
1534
1539
  { passive: false }
1535
1540
  );
@@ -1560,7 +1565,7 @@ var Scene = (_class2 = class _Scene {
1560
1565
  lineElement.style.position = "absolute";
1561
1566
  lineElement.dir = "auto";
1562
1567
  lineElement.style.left = `${line.x}px`;
1563
- lineElement.style.top = `${line.y + line.baseline - _chunkFIQAIF55js.cssLineBoxBaseline.call(void 0, lineFont, lineHeight2)}px`;
1568
+ lineElement.style.top = `${line.y + line.baseline - _chunkGKLTTGBRjs.cssLineBoxBaseline.call(void 0, lineFont, lineHeight2)}px`;
1564
1569
  lineElement.style.whiteSpace = "pre";
1565
1570
  if (lineFont) lineElement.style.font = lineFont;
1566
1571
  lineElement.style.lineHeight = `${lineHeight2}px`;
@@ -1612,7 +1617,7 @@ var Scene = (_class2 = class _Scene {
1612
1617
  const { a, b, c, d, e, f } = node.getWorldTransform();
1613
1618
  const contentX = _nullishCoalesce(projection.contentX, () => ( 0));
1614
1619
  const contentY = _nullishCoalesce(projection.contentY, () => ( 0));
1615
- const baselineOffset = lines && lines.length > 0 ? 0 : projection.baseline === void 0 ? 0 : projection.baseline - _chunkFIQAIF55js.cssLineBoxBaseline.call(void 0, font, _nullishCoalesce(projection.lineHeight, () => ( 16)));
1620
+ const baselineOffset = lines && lines.length > 0 ? 0 : projection.baseline === void 0 ? 0 : projection.baseline - _chunkGKLTTGBRjs.cssLineBoxBaseline.call(void 0, font, _nullishCoalesce(projection.lineHeight, () => ( 16)));
1616
1621
  const localY = contentY + baselineOffset;
1617
1622
  el.style.left = `${e + a * contentX + c * localY}px`;
1618
1623
  el.style.top = `${f + b * contentX + d * localY}px`;
@@ -1684,7 +1689,7 @@ var Scene = (_class2 = class _Scene {
1684
1689
  lineElement.dataset.vectoGridLine = `${lineIndex}`;
1685
1690
  lineElement.style.position = "absolute";
1686
1691
  lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _68 => _68.x]), () => ( 0))}px`;
1687
- lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _69 => _69.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _chunkFIQAIF55js.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
1692
+ lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _69 => _69.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _chunkGKLTTGBRjs.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
1688
1693
  lineElement.style.width = `${gridLine.width}px`;
1689
1694
  lineElement.style.height = `${lineHeight}px`;
1690
1695
  lineElement.style.whiteSpace = "pre";
@@ -2616,7 +2621,7 @@ function defaultMeasurer() {
2616
2621
  if (sharedMeasurer === void 0) sharedMeasurer = _chunkBA5HUUDFjs.createCanvasMeasurer.call(void 0, "sans-serif");
2617
2622
  return sharedMeasurer;
2618
2623
  }
2619
- var TextEntity = (_class3 = class extends _chunkFIQAIF55js.Entity {
2624
+ var TextEntity = (_class3 = class extends _chunkGKLTTGBRjs.Entity {
2620
2625
 
2621
2626
 
2622
2627
 
@@ -2742,7 +2747,7 @@ var TextEntity = (_class3 = class extends _chunkFIQAIF55js.Entity {
2742
2747
  }, _class3);
2743
2748
 
2744
2749
  // src/components/GridTextEntity.ts
2745
- var GridTextEntity = (_class4 = class extends _chunkFIQAIF55js.Entity {
2750
+ var GridTextEntity = (_class4 = class extends _chunkGKLTTGBRjs.Entity {
2746
2751
 
2747
2752
  __init68() {this.fillStyle = "#ffffff"}
2748
2753
  __init69() {this.grid = []}
@@ -2836,7 +2841,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
2836
2841
  const ey = py - cy;
2837
2842
  return ex * ex + ey * ey;
2838
2843
  }
2839
- var SplineEntity = (_class5 = class extends _chunkFIQAIF55js.Entity {
2844
+ var SplineEntity = (_class5 = class extends _chunkGKLTTGBRjs.Entity {
2840
2845
 
2841
2846
 
2842
2847
 
@@ -3110,6 +3115,128 @@ async function loadSpline(url) {
3110
3115
  return await res.json();
3111
3116
  }
3112
3117
 
3118
+ // src/components/Rect.ts
3119
+ var Rect = class extends _chunkGKLTTGBRjs.Entity {
3120
+
3121
+
3122
+
3123
+
3124
+ constructor(opts = {}) {
3125
+ super();
3126
+ this.width = _nullishCoalesce(opts.width, () => ( 0));
3127
+ this.height = _nullishCoalesce(opts.height, () => ( 0));
3128
+ this.fill = opts.fill === void 0 ? "#38bdf8" : opts.fill;
3129
+ this.stroke = _nullishCoalesce(opts.stroke, () => ( null));
3130
+ this.strokeWidth = _nullishCoalesce(opts.strokeWidth, () => ( 1));
3131
+ this.radius = _nullishCoalesce(opts.radius, () => ( 0));
3132
+ }
3133
+ getBounds() {
3134
+ return { x: 0, y: 0, width: this.width, height: this.height };
3135
+ }
3136
+ /**
3137
+ * Solid-fill, square-cornered, unstroked rectangles opt into the GPU
3138
+ * instanced-rect batch (WebGL `pointBackend` only). Any stroke or corner
3139
+ * radius needs the exact Canvas path, so return `null` to fall back to
3140
+ * {@link render}. Read each frame, so an animated `fill` still batches.
3141
+ */
3142
+ getBatchRect() {
3143
+ if (!this.fill || this.stroke || this.radius > 0) return null;
3144
+ return { width: this.width, height: this.height, color: this.fill };
3145
+ }
3146
+ isPointInside(globalX, globalY) {
3147
+ const local = this.worldToLocal(globalX, globalY);
3148
+ if (!local) return false;
3149
+ return local.x >= 0 && local.x <= this.width && local.y >= 0 && local.y <= this.height;
3150
+ }
3151
+ render(renderer) {
3152
+ renderer.beginPath();
3153
+ if (this.radius > 0) {
3154
+ renderer.roundRect(0, 0, this.width, this.height, this.radius);
3155
+ } else {
3156
+ renderer.moveTo(0, 0);
3157
+ renderer.lineTo(this.width, 0);
3158
+ renderer.lineTo(this.width, this.height);
3159
+ renderer.lineTo(0, this.height);
3160
+ renderer.closePath();
3161
+ }
3162
+ if (this.fill) renderer.fill(this.fill);
3163
+ if (this.stroke) renderer.stroke(this.stroke, this.strokeWidth);
3164
+ }
3165
+ };
3166
+
3167
+ // src/components/Circle.ts
3168
+ var Circle = class extends _chunkGKLTTGBRjs.Entity {
3169
+
3170
+
3171
+
3172
+
3173
+ constructor(opts = {}) {
3174
+ super();
3175
+ this._radius = _nullishCoalesce(opts.radius, () => ( 0));
3176
+ this.fill = opts.fill === void 0 ? "#38bdf8" : opts.fill;
3177
+ this.stroke = _nullishCoalesce(opts.stroke, () => ( null));
3178
+ this.strokeWidth = _nullishCoalesce(opts.strokeWidth, () => ( 1));
3179
+ this.syncBox();
3180
+ }
3181
+ get radius() {
3182
+ return this._radius;
3183
+ }
3184
+ set radius(v) {
3185
+ this._radius = v;
3186
+ this.syncBox();
3187
+ }
3188
+ /** Keep the a11y box (a square around the centered disc) in sync with radius. */
3189
+ syncBox() {
3190
+ this.width = this._radius * 2;
3191
+ this.height = this._radius * 2;
3192
+ this.a11yOffsetX = -this._radius;
3193
+ this.a11yOffsetY = -this._radius;
3194
+ }
3195
+ getBounds() {
3196
+ return {
3197
+ x: -this._radius,
3198
+ y: -this._radius,
3199
+ width: this._radius * 2,
3200
+ height: this._radius * 2
3201
+ };
3202
+ }
3203
+ /**
3204
+ * A solid-fill, unstroked circle opts into the renderer's circle batch
3205
+ * (center = entity origin, radius scaled by world scale). A stroke needs the
3206
+ * exact Canvas path, so return `null` there. Read each frame, so an animated
3207
+ * `fill`/`radius` still batches.
3208
+ */
3209
+ getBatchCircle() {
3210
+ if (!this.fill || this.stroke) return null;
3211
+ return { radius: this._radius, color: this.fill };
3212
+ }
3213
+ isPointInside(globalX, globalY) {
3214
+ const local = this.worldToLocal(globalX, globalY);
3215
+ if (!local) return false;
3216
+ return local.x * local.x + local.y * local.y <= this._radius * this._radius;
3217
+ }
3218
+ render(renderer) {
3219
+ renderer.beginPath();
3220
+ renderer.arc(0, 0, this._radius, 0, Math.PI * 2);
3221
+ renderer.closePath();
3222
+ if (this.fill) renderer.fill(this.fill);
3223
+ if (this.stroke) renderer.stroke(this.stroke, this.strokeWidth);
3224
+ }
3225
+ };
3226
+
3227
+ // src/components/Group.ts
3228
+ var Group = class extends _chunkGKLTTGBRjs.Entity {
3229
+ constructor(...children) {
3230
+ super();
3231
+ if (children.length > 0) this.add(...children);
3232
+ }
3233
+ isPointInside() {
3234
+ return false;
3235
+ }
3236
+ render(_renderer) {
3237
+ }
3238
+ };
3239
+
3113
3240
  // src/math/SpatialHashGrid.ts
3114
3241
  var SpatialHashGrid = (_class6 = class {
3115
3242
 
@@ -3205,7 +3332,7 @@ var SpatialHashGrid = (_class6 = class {
3205
3332
  }, _class6);
3206
3333
 
3207
3334
  // src/tree/DOMPortalEntity.ts
3208
- var DOMPortalEntity = (_class7 = class extends _chunkFIQAIF55js.Entity {
3335
+ var DOMPortalEntity = (_class7 = class extends _chunkGKLTTGBRjs.Entity {
3209
3336
 
3210
3337
  __init80() {this.isDOMPortal = true}
3211
3338
  __init81() {this.domListeners = []}
@@ -3247,7 +3374,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkFIQAIF55js.Entity {
3247
3374
  ];
3248
3375
  for (const type of events) {
3249
3376
  const handler = (e) => {
3250
- this.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)(type, this, e));
3377
+ this.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)(type, this, e));
3251
3378
  };
3252
3379
  this.domElement.addEventListener(type, handler);
3253
3380
  this.domListeners.push({ type, handler, capture: false });
@@ -3258,7 +3385,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkFIQAIF55js.Entity {
3258
3385
  ];
3259
3386
  for (const { native, vecto } of hoverEvents) {
3260
3387
  const handler = (e) => {
3261
- this.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)(vecto, this, e, false));
3388
+ this.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)(vecto, this, e, false));
3262
3389
  };
3263
3390
  this.domElement.addEventListener(native, handler);
3264
3391
  this.domListeners.push({ type: native, handler, capture: false });
@@ -3266,7 +3393,7 @@ var DOMPortalEntity = (_class7 = class extends _chunkFIQAIF55js.Entity {
3266
3393
  const focusEvents = ["focus", "blur"];
3267
3394
  for (const type of focusEvents) {
3268
3395
  const handler = (e) => {
3269
- this.dispatchEvent(new (0, _chunkFIQAIF55js.VectoJSEvent)(type, this, e, true));
3396
+ this.dispatchEvent(new (0, _chunkGKLTTGBRjs.VectoJSEvent)(type, this, e, true));
3270
3397
  };
3271
3398
  this.domElement.addEventListener(type, handler, true);
3272
3399
  this.domListeners.push({ type, handler, capture: true });
@@ -3354,4 +3481,7 @@ Scene.registerWebGPUParticleSystemManager(_chunkBPMNCGU7js.WebGPUParticleSystemM
3354
3481
 
3355
3482
 
3356
3483
 
3357
- exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.CanvasRenderer = _chunkBPMNCGU7js.CanvasRenderer; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunkFIQAIF55js.Easing; exports.Entity = _chunkFIQAIF55js.Entity; exports.GridTextEntity = GridTextEntity; exports.LayoutEngine = _chunkBA5HUUDFjs.LayoutEngine; exports.LayoutResultBuffer = _chunkBA5HUUDFjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunk4AR425ARjs.LayoutWorkerManager; exports.MSDFFont = _chunkFIQAIF55js.MSDFFont; exports.MSDFTextEntity = _chunkFIQAIF55js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.SVGEntity = _chunkFIQAIF55js.SVGEntity; exports.SVGRenderer = _chunkBPMNCGU7js.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunkFIQAIF55js.SpringDriver; exports.SpringPhysics = _chunkFIQAIF55js.SpringPhysics; exports.TextEntity = TextEntity; exports.TweenDriver = _chunkFIQAIF55js.TweenDriver; exports.VectoJSEvent = _chunkFIQAIF55js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkBPMNCGU7js.WebGPUParticleSystemManager; exports.clearCssLineBoxMetrics = _chunkFIQAIF55js.clearCssLineBoxMetrics; exports.computeLineSegments = _chunkBA5HUUDFjs.computeLineSegments; exports.createCanvasMeasurer = _chunkBA5HUUDFjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkBPMNCGU7js.createWebGLPointRenderer; exports.cssLineBoxBaseline = _chunkFIQAIF55js.cssLineBoxBaseline; exports.isSafeUrl = _chunkBPMNCGU7js.isSafeUrl; exports.isTweenConfig = _chunkFIQAIF55js.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkBPMNCGU7js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.prepareContentGrid = _chunkFIQAIF55js.prepareContentGrid; exports.sanitizeUrl = _chunkBPMNCGU7js.sanitizeUrl;
3484
+
3485
+
3486
+
3487
+ exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.CanvasRenderer = _chunkBPMNCGU7js.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Easing = _chunkGKLTTGBRjs.Easing; exports.Entity = _chunkGKLTTGBRjs.Entity; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.LayoutEngine = _chunkBA5HUUDFjs.LayoutEngine; exports.LayoutResultBuffer = _chunkBA5HUUDFjs.LayoutResultBuffer; exports.LayoutWorkerManager = _chunk4AR425ARjs.LayoutWorkerManager; exports.MSDFFont = _chunkGKLTTGBRjs.MSDFFont; exports.MSDFTextEntity = _chunkGKLTTGBRjs.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunkGKLTTGBRjs.SVGEntity; exports.SVGRenderer = _chunkBPMNCGU7js.SVGRenderer; exports.Scene = Scene; exports.SpatialHashGrid = SpatialHashGrid; exports.SplineEntity = SplineEntity; exports.SpringDriver = _chunkGKLTTGBRjs.SpringDriver; exports.SpringPhysics = _chunkGKLTTGBRjs.SpringPhysics; exports.TextEntity = TextEntity; exports.TweenDriver = _chunkGKLTTGBRjs.TweenDriver; exports.VectoJSEvent = _chunkGKLTTGBRjs.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkBPMNCGU7js.WebGPUParticleSystemManager; exports.clearCssLineBoxMetrics = _chunkGKLTTGBRjs.clearCssLineBoxMetrics; exports.computeLineSegments = _chunkBA5HUUDFjs.computeLineSegments; exports.createCanvasMeasurer = _chunkBA5HUUDFjs.createCanvasMeasurer; exports.createWebGLPointRenderer = _chunkBPMNCGU7js.createWebGLPointRenderer; exports.cssLineBoxBaseline = _chunkGKLTTGBRjs.cssLineBoxBaseline; exports.isSafeUrl = _chunkBPMNCGU7js.isSafeUrl; exports.isTweenConfig = _chunkGKLTTGBRjs.isTweenConfig; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkBPMNCGU7js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.prepareContentGrid = _chunkGKLTTGBRjs.prepareContentGrid; exports.sanitizeUrl = _chunkBPMNCGU7js.sanitizeUrl;
package/dist/index.mjs CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  cssLineBoxBaseline,
28
28
  isTweenConfig,
29
29
  prepareContentGrid
30
- } from "./chunk-RQ2ETTBN.mjs";
30
+ } from "./chunk-J2P6QZJV.mjs";
31
31
  import {
32
32
  ArabicShaper,
33
33
  BidiResolver,
@@ -1214,6 +1214,13 @@ var Scene = class _Scene {
1214
1214
  }
1215
1215
  /** True when any node in the subtree has a pending animation. */
1216
1216
  /** True when any node in the subtree is interactive (drives a11y sync). */
1217
+ syncOptionalAttribute(element, name, value) {
1218
+ if (value === void 0) {
1219
+ if (element.hasAttribute(name)) element.removeAttribute(name);
1220
+ return;
1221
+ }
1222
+ if (element.getAttribute(name) !== value) element.setAttribute(name, value);
1223
+ }
1217
1224
  syncA11y(node) {
1218
1225
  if (!this.a11yRoot) return;
1219
1226
  if (node.isDOMPortal) {
@@ -1380,12 +1387,8 @@ var Scene = class _Scene {
1380
1387
  this.a11yElements.set(node.id, el);
1381
1388
  this.a11yNeedsReorder = true;
1382
1389
  }
1383
- if (attrs.role !== void 0 && el.getAttribute("role") !== attrs.role) {
1384
- el.setAttribute("role", attrs.role);
1385
- }
1386
- if (attrs.label !== void 0 && el.getAttribute("aria-label") !== attrs.label) {
1387
- el.setAttribute("aria-label", attrs.label);
1388
- }
1390
+ this.syncOptionalAttribute(el, "role", attrs.role);
1391
+ this.syncOptionalAttribute(el, "aria-label", attrs.label);
1389
1392
  const semanticPointerEvents = attrs.pointerEvents ?? "auto";
1390
1393
  if (el.style.pointerEvents !== semanticPointerEvents) {
1391
1394
  el.style.pointerEvents = semanticPointerEvents;
@@ -1397,58 +1400,58 @@ var Scene = class _Scene {
1397
1400
  } else if (el.getAttribute("tabindex") !== String(desiredTabIndex)) {
1398
1401
  el.setAttribute("tabindex", String(desiredTabIndex));
1399
1402
  }
1400
- if (attrs.inputType !== void 0 && el.getAttribute("type") !== attrs.inputType) {
1401
- el.setAttribute("type", attrs.inputType);
1403
+ this.syncOptionalAttribute(el, "type", attrs.inputType);
1404
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
1405
+ const placeholder = attrs.placeholder ?? "";
1406
+ if (el.placeholder !== placeholder) el.placeholder = placeholder;
1402
1407
  }
1403
- if (attrs.placeholder !== void 0 && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1404
- if (el.placeholder !== attrs.placeholder) el.placeholder = attrs.placeholder;
1405
- }
1406
- if (attrs.href !== void 0 && el instanceof HTMLAnchorElement) {
1407
- const safeHref = sanitizeUrl(attrs.href);
1408
- if (el.getAttribute("href") !== safeHref) el.setAttribute("href", safeHref);
1409
- if (attrs.target !== void 0 && el.getAttribute("target") !== attrs.target) {
1410
- el.setAttribute("target", attrs.target);
1411
- }
1408
+ if (el instanceof HTMLAnchorElement) {
1409
+ this.syncOptionalAttribute(
1410
+ el,
1411
+ "href",
1412
+ attrs.href === void 0 ? void 0 : sanitizeUrl(attrs.href)
1413
+ );
1414
+ this.syncOptionalAttribute(el, "target", attrs.target);
1412
1415
  }
1413
1416
  if (el instanceof HTMLImageElement) {
1414
- if (attrs.src !== void 0 && el.src !== attrs.src) el.src = attrs.src;
1415
- if (attrs.alt !== void 0 && el.alt !== attrs.alt) el.alt = attrs.alt;
1416
- }
1417
- if (attrs.checked !== void 0) {
1418
- if (el instanceof HTMLInputElement) {
1419
- if (el.checked !== attrs.checked) el.checked = attrs.checked;
1420
- } else if (el.getAttribute("aria-checked") !== String(attrs.checked)) {
1421
- el.setAttribute("aria-checked", String(attrs.checked));
1422
- }
1417
+ this.syncOptionalAttribute(el, "src", attrs.src);
1418
+ this.syncOptionalAttribute(el, "alt", attrs.alt);
1423
1419
  }
1424
- if (attrs.disabled !== void 0) {
1425
- if ("disabled" in el) {
1426
- if (el.disabled !== attrs.disabled) el.disabled = attrs.disabled;
1427
- } else if (el.getAttribute("aria-disabled") !== String(attrs.disabled)) {
1428
- el.setAttribute("aria-disabled", String(attrs.disabled));
1429
- }
1430
- }
1431
- if (attrs.expanded !== void 0 && el.getAttribute("aria-expanded") !== String(attrs.expanded)) {
1432
- el.setAttribute("aria-expanded", String(attrs.expanded));
1433
- }
1434
- if (attrs.controls !== void 0 && el.getAttribute("aria-controls") !== attrs.controls) {
1435
- el.setAttribute("aria-controls", attrs.controls);
1436
- }
1437
- if (attrs.haspopup !== void 0 && el.getAttribute("aria-haspopup") !== attrs.haspopup) {
1438
- el.setAttribute("aria-haspopup", attrs.haspopup);
1439
- }
1440
- if (attrs.selected !== void 0 && el.getAttribute("aria-selected") !== String(attrs.selected)) {
1441
- el.setAttribute("aria-selected", String(attrs.selected));
1442
- }
1443
- if (attrs.activedescendant !== void 0 && el.getAttribute("aria-activedescendant") !== attrs.activedescendant) {
1444
- el.setAttribute("aria-activedescendant", attrs.activedescendant);
1445
- }
1446
- if (attrs.valuemin !== void 0 && el.getAttribute("aria-valuemin") !== attrs.valuemin) {
1447
- el.setAttribute("aria-valuemin", attrs.valuemin);
1420
+ if (el instanceof HTMLInputElement) {
1421
+ const checked = attrs.checked ?? false;
1422
+ if (el.checked !== checked) el.checked = checked;
1423
+ } else {
1424
+ this.syncOptionalAttribute(
1425
+ el,
1426
+ "aria-checked",
1427
+ attrs.checked === void 0 ? void 0 : String(attrs.checked)
1428
+ );
1448
1429
  }
1449
- if (attrs.valuemax !== void 0 && el.getAttribute("aria-valuemax") !== attrs.valuemax) {
1450
- el.setAttribute("aria-valuemax", attrs.valuemax);
1430
+ if ("disabled" in el) {
1431
+ const disabled = attrs.disabled ?? false;
1432
+ if (el.disabled !== disabled) el.disabled = disabled;
1433
+ } else {
1434
+ this.syncOptionalAttribute(
1435
+ el,
1436
+ "aria-disabled",
1437
+ attrs.disabled === void 0 ? void 0 : String(attrs.disabled)
1438
+ );
1451
1439
  }
1440
+ this.syncOptionalAttribute(
1441
+ el,
1442
+ "aria-expanded",
1443
+ attrs.expanded === void 0 ? void 0 : String(attrs.expanded)
1444
+ );
1445
+ this.syncOptionalAttribute(el, "aria-controls", attrs.controls);
1446
+ this.syncOptionalAttribute(el, "aria-haspopup", attrs.haspopup);
1447
+ this.syncOptionalAttribute(
1448
+ el,
1449
+ "aria-selected",
1450
+ attrs.selected === void 0 ? void 0 : String(attrs.selected)
1451
+ );
1452
+ this.syncOptionalAttribute(el, "aria-activedescendant", attrs.activedescendant);
1453
+ this.syncOptionalAttribute(el, "aria-valuemin", attrs.valuemin);
1454
+ this.syncOptionalAttribute(el, "aria-valuemax", attrs.valuemax);
1452
1455
  if (attrs.value !== void 0) {
1453
1456
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
1454
1457
  if (el.value !== attrs.value) {
@@ -1458,9 +1461,11 @@ var Scene = class _Scene {
1458
1461
  el._lastSyncedValue = attrs.value;
1459
1462
  }
1460
1463
  }
1461
- } else if (el.getAttribute("aria-valuenow") !== attrs.value) {
1462
- el.setAttribute("aria-valuenow", attrs.value);
1464
+ } else {
1465
+ this.syncOptionalAttribute(el, "aria-valuenow", attrs.value);
1463
1466
  }
1467
+ } else if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1468
+ this.syncOptionalAttribute(el, "aria-valuenow", void 0);
1464
1469
  }
1465
1470
  if (attrs.textInputStyle !== void 0 && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
1466
1471
  const textStyle = attrs.textInputStyle;
@@ -3110,6 +3115,128 @@ async function loadSpline(url) {
3110
3115
  return await res.json();
3111
3116
  }
3112
3117
 
3118
+ // src/components/Rect.ts
3119
+ var Rect = class extends Entity {
3120
+ fill;
3121
+ stroke;
3122
+ strokeWidth;
3123
+ radius;
3124
+ constructor(opts = {}) {
3125
+ super();
3126
+ this.width = opts.width ?? 0;
3127
+ this.height = opts.height ?? 0;
3128
+ this.fill = opts.fill === void 0 ? "#38bdf8" : opts.fill;
3129
+ this.stroke = opts.stroke ?? null;
3130
+ this.strokeWidth = opts.strokeWidth ?? 1;
3131
+ this.radius = opts.radius ?? 0;
3132
+ }
3133
+ getBounds() {
3134
+ return { x: 0, y: 0, width: this.width, height: this.height };
3135
+ }
3136
+ /**
3137
+ * Solid-fill, square-cornered, unstroked rectangles opt into the GPU
3138
+ * instanced-rect batch (WebGL `pointBackend` only). Any stroke or corner
3139
+ * radius needs the exact Canvas path, so return `null` to fall back to
3140
+ * {@link render}. Read each frame, so an animated `fill` still batches.
3141
+ */
3142
+ getBatchRect() {
3143
+ if (!this.fill || this.stroke || this.radius > 0) return null;
3144
+ return { width: this.width, height: this.height, color: this.fill };
3145
+ }
3146
+ isPointInside(globalX, globalY) {
3147
+ const local = this.worldToLocal(globalX, globalY);
3148
+ if (!local) return false;
3149
+ return local.x >= 0 && local.x <= this.width && local.y >= 0 && local.y <= this.height;
3150
+ }
3151
+ render(renderer) {
3152
+ renderer.beginPath();
3153
+ if (this.radius > 0) {
3154
+ renderer.roundRect(0, 0, this.width, this.height, this.radius);
3155
+ } else {
3156
+ renderer.moveTo(0, 0);
3157
+ renderer.lineTo(this.width, 0);
3158
+ renderer.lineTo(this.width, this.height);
3159
+ renderer.lineTo(0, this.height);
3160
+ renderer.closePath();
3161
+ }
3162
+ if (this.fill) renderer.fill(this.fill);
3163
+ if (this.stroke) renderer.stroke(this.stroke, this.strokeWidth);
3164
+ }
3165
+ };
3166
+
3167
+ // src/components/Circle.ts
3168
+ var Circle = class extends Entity {
3169
+ fill;
3170
+ stroke;
3171
+ strokeWidth;
3172
+ _radius;
3173
+ constructor(opts = {}) {
3174
+ super();
3175
+ this._radius = opts.radius ?? 0;
3176
+ this.fill = opts.fill === void 0 ? "#38bdf8" : opts.fill;
3177
+ this.stroke = opts.stroke ?? null;
3178
+ this.strokeWidth = opts.strokeWidth ?? 1;
3179
+ this.syncBox();
3180
+ }
3181
+ get radius() {
3182
+ return this._radius;
3183
+ }
3184
+ set radius(v) {
3185
+ this._radius = v;
3186
+ this.syncBox();
3187
+ }
3188
+ /** Keep the a11y box (a square around the centered disc) in sync with radius. */
3189
+ syncBox() {
3190
+ this.width = this._radius * 2;
3191
+ this.height = this._radius * 2;
3192
+ this.a11yOffsetX = -this._radius;
3193
+ this.a11yOffsetY = -this._radius;
3194
+ }
3195
+ getBounds() {
3196
+ return {
3197
+ x: -this._radius,
3198
+ y: -this._radius,
3199
+ width: this._radius * 2,
3200
+ height: this._radius * 2
3201
+ };
3202
+ }
3203
+ /**
3204
+ * A solid-fill, unstroked circle opts into the renderer's circle batch
3205
+ * (center = entity origin, radius scaled by world scale). A stroke needs the
3206
+ * exact Canvas path, so return `null` there. Read each frame, so an animated
3207
+ * `fill`/`radius` still batches.
3208
+ */
3209
+ getBatchCircle() {
3210
+ if (!this.fill || this.stroke) return null;
3211
+ return { radius: this._radius, color: this.fill };
3212
+ }
3213
+ isPointInside(globalX, globalY) {
3214
+ const local = this.worldToLocal(globalX, globalY);
3215
+ if (!local) return false;
3216
+ return local.x * local.x + local.y * local.y <= this._radius * this._radius;
3217
+ }
3218
+ render(renderer) {
3219
+ renderer.beginPath();
3220
+ renderer.arc(0, 0, this._radius, 0, Math.PI * 2);
3221
+ renderer.closePath();
3222
+ if (this.fill) renderer.fill(this.fill);
3223
+ if (this.stroke) renderer.stroke(this.stroke, this.strokeWidth);
3224
+ }
3225
+ };
3226
+
3227
+ // src/components/Group.ts
3228
+ var Group = class extends Entity {
3229
+ constructor(...children) {
3230
+ super();
3231
+ if (children.length > 0) this.add(...children);
3232
+ }
3233
+ isPointInside() {
3234
+ return false;
3235
+ }
3236
+ render(_renderer) {
3237
+ }
3238
+ };
3239
+
3113
3240
  // src/math/SpatialHashGrid.ts
3114
3241
  var SpatialHashGrid = class {
3115
3242
  cellSize;
@@ -3311,11 +3438,13 @@ export {
3311
3438
  ArabicShaper,
3312
3439
  BidiResolver,
3313
3440
  CanvasRenderer,
3441
+ Circle,
3314
3442
  ComputeParticleEntity,
3315
3443
  DOMPortalEntity,
3316
3444
  Easing,
3317
3445
  Entity,
3318
3446
  GridTextEntity,
3447
+ Group,
3319
3448
  LayoutEngine,
3320
3449
  LayoutResultBuffer,
3321
3450
  LayoutWorkerManager,
@@ -3331,6 +3460,7 @@ export {
3331
3460
  PARTICLE_OFFSET_VELOCITY_Y,
3332
3461
  PARTICLE_STRIDE_FLOATS,
3333
3462
  REDUCED_MOTION_FPS,
3463
+ Rect,
3334
3464
  SVGEntity,
3335
3465
  SVGRenderer,
3336
3466
  Scene,
package/dist/text.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkFIQAIF55js = require('./chunk-FIQAIF55.js');
8
+ var _chunkGKLTTGBRjs = require('./chunk-GKLTTGBR.js');
9
9
 
10
10
 
11
11
 
@@ -19,4 +19,4 @@ var _chunk4AR425ARjs = require('./chunk-4AR425AR.js');
19
19
 
20
20
 
21
21
 
22
- exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.MSDFFont = _chunkFIQAIF55js.MSDFFont; exports.MSDFTextEntity = _chunkFIQAIF55js.MSDFTextEntity; exports.SVGEntity = _chunkFIQAIF55js.SVGEntity; exports.clearCssLineBoxMetrics = _chunkFIQAIF55js.clearCssLineBoxMetrics; exports.cssLineBoxBaseline = _chunkFIQAIF55js.cssLineBoxBaseline; exports.prepareContentGrid = _chunkFIQAIF55js.prepareContentGrid;
22
+ exports.ArabicShaper = _chunk4AR425ARjs.ArabicShaper; exports.BidiResolver = _chunk4AR425ARjs.BidiResolver; exports.MSDFFont = _chunkGKLTTGBRjs.MSDFFont; exports.MSDFTextEntity = _chunkGKLTTGBRjs.MSDFTextEntity; exports.SVGEntity = _chunkGKLTTGBRjs.SVGEntity; exports.clearCssLineBoxMetrics = _chunkGKLTTGBRjs.clearCssLineBoxMetrics; exports.cssLineBoxBaseline = _chunkGKLTTGBRjs.cssLineBoxBaseline; exports.prepareContentGrid = _chunkGKLTTGBRjs.prepareContentGrid;
package/dist/text.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  clearCssLineBoxMetrics,
6
6
  cssLineBoxBaseline,
7
7
  prepareContentGrid
8
- } from "./chunk-RQ2ETTBN.mjs";
8
+ } from "./chunk-J2P6QZJV.mjs";
9
9
  import {
10
10
  ArabicShaper,
11
11
  BidiResolver
@@ -348,10 +348,15 @@ export declare abstract class Entity {
348
348
  * `child.parent` is only ever `null` or `this`'s ultimate owner, so this
349
349
  * check is O(1) for the overwhelming common case (a brand-new entity).
350
350
  *
351
- * @param child - The entity to add as a child.
351
+ * Accepts multiple children in one call (`parent.add(a, b, c)`); each is
352
+ * attached in argument order with the same detach-then-attach semantics.
353
+ *
354
+ * @param children - One or more entities to add as children.
352
355
  * @returns `this` for method chaining.
353
356
  */
354
- add(child: Entity): this;
357
+ add(...children: Entity[]): this;
358
+ /** Attach a single child (the O(1) common path). See {@link add}. */
359
+ private _addOne;
355
360
  /** Called once when this entity becomes attached to a live Scene. Override to react. */
356
361
  protected onMounted(): void;
357
362
  /** Fire onMounted for this node and its descendants, guarded against double-fire. */
@@ -363,6 +368,18 @@ export declare abstract class Entity {
363
368
  * @returns `this` for method chaining.
364
369
  */
365
370
  remove(child: Entity): this;
371
+ /**
372
+ * Assign several own properties in one call, each through its normal setter
373
+ * (so a property with a configured {@link setTransition} still animates, and
374
+ * `interactive` still flags the scene for a11y reorder). A construction-time
375
+ * ergonomic only — it is a plain `for…in` over the given object and touches
376
+ * no per-frame path.
377
+ *
378
+ * @param props - Partial set of this entity's own properties to assign.
379
+ * @returns `this` for method chaining.
380
+ * @example rect.set({ x: 40, y: 40, width: 120, fill: '#38bdf8' });
381
+ */
382
+ set(props: Partial<this>): this;
366
383
  /**
367
384
  * Set the local position of this entity.
368
385
  *
@@ -309,6 +309,7 @@ export declare class Scene {
309
309
  markDirty(): void;
310
310
  /** True when any node in the subtree has a pending animation. */
311
311
  /** True when any node in the subtree is interactive (drives a11y sync). */
312
+ private syncOptionalAttribute;
312
313
  private syncA11y;
313
314
  /**
314
315
  * Mirror one entity's static text ({@link Entity.getContentProjection}) as a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },