@vectojs/core 1.38.0 → 1.39.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/dist/{chunk-QVTCKWDO.mjs → chunk-7PYD5UDX.mjs} +93 -43
- package/dist/{chunk-ZYYP2M5D.js → chunk-KRUDTGNR.js} +168 -76
- package/dist/{chunk-SRXU3ZXK.js → chunk-POPXUPKR.js} +141 -91
- package/dist/{chunk-LWEMD34D.mjs → chunk-RUM2KEDI.mjs} +119 -27
- package/dist/index.js +689 -396
- package/dist/index.mjs +375 -82
- package/dist/renderer/CanvasRenderer.d.ts +6 -0
- package/dist/renderer/SVGRenderer.d.ts +17 -2
- package/dist/renderer/WebGPUParticleSystemManager.d.ts +2 -0
- package/dist/renderer.js +2 -2
- package/dist/renderer.mjs +1 -1
- package/dist/text/MSDFTextEntity.d.ts +0 -1
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/Entity.d.ts +8 -2
- package/dist/tree/Scene.d.ts +106 -0
- package/dist/tree/scene/A11yProjectionManager.d.ts +12 -0
- package/dist/tree/scene/HitTester.d.ts +14 -2
- package/dist/tree/scene/WasmBackendFacade.d.ts +17 -0
- package/dist/tree/scene/keyboard.d.ts +56 -0
- package/dist/wasm/backend.d.ts +7 -7
- package/dist/wasm/vectojs_core.wasm +0 -0
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -11,14 +11,14 @@ import {
|
|
|
11
11
|
parseColorToRGBA,
|
|
12
12
|
sanitizeUrl,
|
|
13
13
|
setRendererDevMode
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-RUM2KEDI.mjs";
|
|
15
15
|
import {
|
|
16
16
|
Entity,
|
|
17
17
|
MSDFTextEntity,
|
|
18
18
|
SVGEntity,
|
|
19
19
|
VectoJSEvent,
|
|
20
20
|
contentLineInHint
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-7PYD5UDX.mjs";
|
|
22
22
|
|
|
23
23
|
// src/tree/ComputeParticleEntity.ts
|
|
24
24
|
var PARTICLE_STRIDE_FLOATS = 8;
|
|
@@ -224,14 +224,14 @@ var ComputeParticleEntity = class extends Entity {
|
|
|
224
224
|
const bounceDamping = Math.max(0, Math.min(1, this.bounceDamping));
|
|
225
225
|
const maxVelocity = Math.max(1, this.maxVelocity);
|
|
226
226
|
for (let i = 0; i < this.maxParticles; i++) {
|
|
227
|
-
const offset = i *
|
|
228
|
-
let px = this.particleData[offset];
|
|
229
|
-
let py = this.particleData[offset +
|
|
230
|
-
let vx = this.particleData[offset +
|
|
231
|
-
let vy = this.particleData[offset +
|
|
232
|
-
const ox = this.particleData[offset +
|
|
233
|
-
const oy = this.particleData[offset +
|
|
234
|
-
const life = this.particleData[offset +
|
|
227
|
+
const offset = i * PARTICLE_STRIDE_FLOATS;
|
|
228
|
+
let px = this.particleData[offset + PARTICLE_OFFSET_POSITION_X];
|
|
229
|
+
let py = this.particleData[offset + PARTICLE_OFFSET_POSITION_Y];
|
|
230
|
+
let vx = this.particleData[offset + PARTICLE_OFFSET_VELOCITY_X];
|
|
231
|
+
let vy = this.particleData[offset + PARTICLE_OFFSET_VELOCITY_Y];
|
|
232
|
+
const ox = this.particleData[offset + PARTICLE_OFFSET_ORIGIN_X];
|
|
233
|
+
const oy = this.particleData[offset + PARTICLE_OFFSET_ORIGIN_Y];
|
|
234
|
+
const life = this.particleData[offset + PARTICLE_OFFSET_LIFE];
|
|
235
235
|
if (isNaN(px)) px = ox;
|
|
236
236
|
if (isNaN(py)) py = oy;
|
|
237
237
|
if (isNaN(vx)) vx = 0;
|
|
@@ -289,11 +289,11 @@ var ComputeParticleEntity = class extends Entity {
|
|
|
289
289
|
if (life >= 0) {
|
|
290
290
|
nlife = Math.max(0, life - safeDt * 0.5);
|
|
291
291
|
}
|
|
292
|
-
this.particleData[offset] = npx;
|
|
293
|
-
this.particleData[offset +
|
|
294
|
-
this.particleData[offset +
|
|
295
|
-
this.particleData[offset +
|
|
296
|
-
this.particleData[offset +
|
|
292
|
+
this.particleData[offset + PARTICLE_OFFSET_POSITION_X] = npx;
|
|
293
|
+
this.particleData[offset + PARTICLE_OFFSET_POSITION_Y] = npy;
|
|
294
|
+
this.particleData[offset + PARTICLE_OFFSET_VELOCITY_X] = nvx;
|
|
295
|
+
this.particleData[offset + PARTICLE_OFFSET_VELOCITY_Y] = nvy;
|
|
296
|
+
this.particleData[offset + PARTICLE_OFFSET_LIFE] = nlife;
|
|
297
297
|
}
|
|
298
298
|
this.pendingExplosion = null;
|
|
299
299
|
}
|
|
@@ -593,7 +593,12 @@ var ContentGridProjector = class {
|
|
|
593
593
|
lineIndex === 0
|
|
594
594
|
);
|
|
595
595
|
const reusable = existingLines[domIndex];
|
|
596
|
-
if (reusable !== void 0 && reusable.dataset.vectoGridLineSig === lineSignature
|
|
596
|
+
if (reusable !== void 0 && reusable.dataset.vectoGridLineSig === lineSignature) {
|
|
597
|
+
const indexLabel = `${lineIndex}`;
|
|
598
|
+
if (reusable.dataset.vectoGridLine !== indexLabel) {
|
|
599
|
+
reusable.dataset.vectoGridLine = indexLabel;
|
|
600
|
+
reusable.style.top = `${(projectedLine?.y ?? lineIndex * grid.lineHeight) + baseline - cssLineBoxBaseline(lineFont, lineHeight)}px`;
|
|
601
|
+
}
|
|
597
602
|
continue;
|
|
598
603
|
}
|
|
599
604
|
if (selectionLine !== null && selectionLine === lineIndex) rebuiltSelectionLine = true;
|
|
@@ -779,6 +784,12 @@ function projectionCaretAt(root, absoluteOffset, affinity) {
|
|
|
779
784
|
}
|
|
780
785
|
|
|
781
786
|
// src/tree/scene/A11yProjectionManager.ts
|
|
787
|
+
function orderBandBottom(r) {
|
|
788
|
+
return r.container ? r.top + 4 : r.top + Math.max(Number.parseFloat(r.el.style.height) || 0, 4);
|
|
789
|
+
}
|
|
790
|
+
var byTopThenIndex = (p, q) => p.top - q.top || p.i - q.i;
|
|
791
|
+
var byLeftAscThenIndex = (p, q) => p.left - q.left || p.i - q.i;
|
|
792
|
+
var byLeftDescThenIndex = (p, q) => q.left - p.left || p.i - q.i;
|
|
782
793
|
var A11yProjectionManager = class {
|
|
783
794
|
/**
|
|
784
795
|
* Set by anything that changes which elements exist or how they nest, and
|
|
@@ -820,6 +831,18 @@ var A11yProjectionManager = class {
|
|
|
820
831
|
* implicit root region. See {@link sortNormalElementsVisually}.
|
|
821
832
|
*/
|
|
822
833
|
orderRegions = /* @__PURE__ */ new Map();
|
|
834
|
+
/**
|
|
835
|
+
* Decoration records for {@link sortNormalElementsVisually}: one per ordered
|
|
836
|
+
* element, pooled across passes and rewritten in place — the sort runs on
|
|
837
|
+
* structure-change frames, where the module's zero-GC policy applies.
|
|
838
|
+
*/
|
|
839
|
+
orderDecorated = [];
|
|
840
|
+
/** Region → decorated elements, reused by {@link sortNormalElementsVisually}. */
|
|
841
|
+
orderBucketByRegion = /* @__PURE__ */ new Map();
|
|
842
|
+
/** Pool of bucket arrays backing {@link orderBucketByRegion} across passes. */
|
|
843
|
+
orderBuckets = [];
|
|
844
|
+
/** Scratch for one visual row inside {@link sortNormalElementsVisually}. */
|
|
845
|
+
orderRowScratch = [];
|
|
823
846
|
/** Mark the projected DOM as needing a reorder on the next pass. */
|
|
824
847
|
markNeedsReorder() {
|
|
825
848
|
this.needsReorder = true;
|
|
@@ -877,7 +900,8 @@ var A11yProjectionManager = class {
|
|
|
877
900
|
this.orderCursors.set(parent, at + 1);
|
|
878
901
|
const current = parent.childNodes[at];
|
|
879
902
|
if (current !== expected) {
|
|
880
|
-
const
|
|
903
|
+
const active = document.activeElement;
|
|
904
|
+
const refocusTarget = active && (active === expected || expected.contains(active)) ? active : null;
|
|
881
905
|
if (!selectionSnapshotTaken) {
|
|
882
906
|
selectionSnapshotTaken = true;
|
|
883
907
|
const live = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
@@ -893,7 +917,7 @@ var A11yProjectionManager = class {
|
|
|
893
917
|
selectionMoved = true;
|
|
894
918
|
}
|
|
895
919
|
parent.insertBefore(expected, current || null);
|
|
896
|
-
if (
|
|
920
|
+
if (refocusTarget) refocusTarget.focus({ preventScroll: true });
|
|
897
921
|
}
|
|
898
922
|
}
|
|
899
923
|
if (selectionMoved && selection && selAnchorNode && selFocusNode) {
|
|
@@ -940,7 +964,6 @@ var A11yProjectionManager = class {
|
|
|
940
964
|
sortNormalElementsVisually(rtl) {
|
|
941
965
|
const els = this.normalElements;
|
|
942
966
|
if (els.length < 2) return;
|
|
943
|
-
const heightOf = (el) => Math.max(Number.parseFloat(el.style.height) || 0, 4);
|
|
944
967
|
const members = this.orderMembers;
|
|
945
968
|
const containers = this.orderContainers;
|
|
946
969
|
members.clear();
|
|
@@ -951,52 +974,70 @@ var A11yProjectionManager = class {
|
|
|
951
974
|
if (members.has(p)) containers.add(p);
|
|
952
975
|
}
|
|
953
976
|
}
|
|
954
|
-
const
|
|
977
|
+
const decorated = this.orderDecorated;
|
|
978
|
+
while (decorated.length < els.length) {
|
|
979
|
+
decorated.push({ el: els[0], i: 0, top: 0, left: 0, container: false });
|
|
980
|
+
}
|
|
981
|
+
for (let i = 0; i < els.length; i++) {
|
|
982
|
+
const rec = decorated[i];
|
|
955
983
|
let top = 0;
|
|
956
984
|
let left = 0;
|
|
957
|
-
for (let node =
|
|
985
|
+
for (let node = els[i]; node; node = node.parentElement) {
|
|
958
986
|
top += Number.parseFloat(node.style.top) || 0;
|
|
959
987
|
left += Number.parseFloat(node.style.left) || 0;
|
|
960
988
|
const parent = node.parentElement;
|
|
961
989
|
if (!parent || !members.has(parent)) break;
|
|
962
990
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
991
|
+
rec.el = els[i];
|
|
992
|
+
rec.i = i;
|
|
993
|
+
rec.top = top;
|
|
994
|
+
rec.left = left;
|
|
995
|
+
rec.container = containers.has(els[i]);
|
|
996
|
+
}
|
|
969
997
|
const regions = this.orderRegions;
|
|
970
|
-
const buckets =
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
998
|
+
const buckets = this.orderBucketByRegion;
|
|
999
|
+
const bucketPool = this.orderBuckets;
|
|
1000
|
+
let bucketsUsed = 0;
|
|
1001
|
+
buckets.clear();
|
|
1002
|
+
for (let i = 0; i < els.length; i++) {
|
|
1003
|
+
const rec = decorated[i];
|
|
1004
|
+
const key = regions.get(rec.el) ?? null;
|
|
1005
|
+
let bucket = buckets.get(key);
|
|
1006
|
+
if (!bucket) {
|
|
1007
|
+
bucket = bucketPool[bucketsUsed];
|
|
1008
|
+
if (bucket === void 0) {
|
|
1009
|
+
bucket = [];
|
|
1010
|
+
bucketPool.push(bucket);
|
|
1011
|
+
}
|
|
1012
|
+
bucketsUsed++;
|
|
1013
|
+
bucket.length = 0;
|
|
1014
|
+
buckets.set(key, bucket);
|
|
1015
|
+
}
|
|
1016
|
+
bucket.push(rec);
|
|
1017
|
+
}
|
|
1018
|
+
let out = 0;
|
|
979
1019
|
for (const order of buckets.values()) {
|
|
980
|
-
order.sort(
|
|
1020
|
+
order.sort(byTopThenIndex);
|
|
981
1021
|
let rowStart = 0;
|
|
982
|
-
let rowBottom = order.length ?
|
|
1022
|
+
let rowBottom = order.length ? orderBandBottom(order[0]) : 0;
|
|
983
1023
|
const flushRow = (end) => {
|
|
984
|
-
const row =
|
|
985
|
-
row.
|
|
986
|
-
for (
|
|
1024
|
+
const row = this.orderRowScratch;
|
|
1025
|
+
row.length = 0;
|
|
1026
|
+
for (let m = rowStart; m < end; m++) row.push(order[m]);
|
|
1027
|
+
row.sort(rtl ? byLeftDescThenIndex : byLeftAscThenIndex);
|
|
1028
|
+
for (const r of row) els[out++] = r.el;
|
|
987
1029
|
};
|
|
988
1030
|
for (let k = 1; k < order.length; k++) {
|
|
989
1031
|
if (order[k].top < rowBottom) {
|
|
990
|
-
rowBottom = Math.max(rowBottom,
|
|
1032
|
+
rowBottom = Math.max(rowBottom, orderBandBottom(order[k]));
|
|
991
1033
|
} else {
|
|
992
1034
|
flushRow(k);
|
|
993
1035
|
rowStart = k;
|
|
994
|
-
rowBottom =
|
|
1036
|
+
rowBottom = orderBandBottom(order[k]);
|
|
995
1037
|
}
|
|
996
1038
|
}
|
|
997
1039
|
flushRow(order.length);
|
|
998
1040
|
}
|
|
999
|
-
for (let i = 0; i < sorted.length; i++) els[i] = sorted[i];
|
|
1000
1041
|
}
|
|
1001
1042
|
};
|
|
1002
1043
|
|
|
@@ -1327,6 +1368,8 @@ var DriverTicker = class {
|
|
|
1327
1368
|
tD.length = tweenCount;
|
|
1328
1369
|
backend.ensure(springCount, tweenCount);
|
|
1329
1370
|
backend.revalidateViews();
|
|
1371
|
+
let springsRejected = false;
|
|
1372
|
+
let tweensRejected = false;
|
|
1330
1373
|
if (springCount > 0) {
|
|
1331
1374
|
const sv = backend.springView();
|
|
1332
1375
|
for (let i = 0; i < springCount; i++) {
|
|
@@ -1342,8 +1385,7 @@ var DriverTicker = class {
|
|
|
1342
1385
|
for (let i = 0; i < springCount; i++) sD[i].syncExternal(sv.val[i], sv.vel[i]);
|
|
1343
1386
|
} else {
|
|
1344
1387
|
for (let i = 0; i < springCount; i++) sD[i].tick(dt);
|
|
1345
|
-
|
|
1346
|
-
this.backends.animBatchedLastFrame = false;
|
|
1388
|
+
springsRejected = true;
|
|
1347
1389
|
}
|
|
1348
1390
|
}
|
|
1349
1391
|
if (tweenCount > 0) {
|
|
@@ -1361,10 +1403,13 @@ var DriverTicker = class {
|
|
|
1361
1403
|
for (let i = 0; i < tweenCount; i++) tD[i].syncExternal(tv.val[i], tv.elapsed[i]);
|
|
1362
1404
|
} else {
|
|
1363
1405
|
for (let i = 0; i < tweenCount; i++) tD[i].tick(dt);
|
|
1364
|
-
|
|
1365
|
-
this.backends.animBatchedLastFrame = false;
|
|
1406
|
+
tweensRejected = true;
|
|
1366
1407
|
}
|
|
1367
1408
|
}
|
|
1409
|
+
if (springsRejected || tweensRejected) {
|
|
1410
|
+
this.backends.animReason = springsRejected && tweensRejected ? "rejected" : springsRejected ? "springs-rejected" : "tweens-rejected";
|
|
1411
|
+
this.backends.animBatchedLastFrame = springCount > 0 && !springsRejected || tweenCount > 0 && !tweensRejected;
|
|
1412
|
+
}
|
|
1368
1413
|
for (let i = 0; i < springCount; i++) sE[i]._applyDriverTick(sP[i], sD[i]);
|
|
1369
1414
|
for (let i = 0; i < tweenCount; i++) tE[i]._applyDriverTick(tP[i], tD[i]);
|
|
1370
1415
|
}
|
|
@@ -1525,9 +1570,6 @@ function intersectBounds(a, b) {
|
|
|
1525
1570
|
height: Math.max(0, bottom - y)
|
|
1526
1571
|
};
|
|
1527
1572
|
}
|
|
1528
|
-
function pointInBounds(b, x, y) {
|
|
1529
|
-
return x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height;
|
|
1530
|
-
}
|
|
1531
1573
|
var HitTester = class {
|
|
1532
1574
|
root;
|
|
1533
1575
|
overlayRoot;
|
|
@@ -1574,7 +1616,7 @@ var HitTester = class {
|
|
|
1574
1616
|
this.backends.hitReason = "not-installed";
|
|
1575
1617
|
return false;
|
|
1576
1618
|
}
|
|
1577
|
-
if (this.backends.hitGridFrame === frame) {
|
|
1619
|
+
if (this.backends.hitGridFrame === frame && this.backends.hitGridStructureVersion === this.backends.structureVersion) {
|
|
1578
1620
|
return this.backends.hitGridOk;
|
|
1579
1621
|
}
|
|
1580
1622
|
let gathered = null;
|
|
@@ -1605,6 +1647,7 @@ var HitTester = class {
|
|
|
1605
1647
|
this.slotEntity = gathered.slotEntity;
|
|
1606
1648
|
this.boundless = gathered.boundless;
|
|
1607
1649
|
this.backends.hitGridFrame = frame;
|
|
1650
|
+
this.backends.hitGridStructureVersion = this.backends.structureVersion;
|
|
1608
1651
|
this.backends.hitGridOk = ok;
|
|
1609
1652
|
this.backends.hitReason = ok ? "active" : "rejected";
|
|
1610
1653
|
return ok;
|
|
@@ -1655,7 +1698,7 @@ var HitTester = class {
|
|
|
1655
1698
|
const hit = this.findHitRecursively(node.children[i], x, y, childClip);
|
|
1656
1699
|
if (hit) return hit;
|
|
1657
1700
|
}
|
|
1658
|
-
if (node.isPointInside && node.isPointInside(x, y) && (
|
|
1701
|
+
if (node.isPointInside && node.isPointInside(x, y) && this.isInsideAllClippers(node, x, y) && !this.isPointerTransparent(node)) {
|
|
1659
1702
|
return node;
|
|
1660
1703
|
}
|
|
1661
1704
|
return null;
|
|
@@ -1667,6 +1710,25 @@ var HitTester = class {
|
|
|
1667
1710
|
const attrs = node.getA11yAttributes();
|
|
1668
1711
|
return attrs.disabled === true || attrs.pointerEvents === "none";
|
|
1669
1712
|
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Exact rotation-aware `clipChildren` test shared by BOTH hit paths: the
|
|
1715
|
+
* point must lie inside every clipChildren ancestor's local rect — the same
|
|
1716
|
+
* shape rendering clips to — not merely inside the ancestor's world AABB.
|
|
1717
|
+
* For a rotated clipper those disagree, and until both paths used the exact
|
|
1718
|
+
* rect a query's answer depended on which backend was active (#680). The
|
|
1719
|
+
* clip-stack AABB intersection in {@link findHitRecursively} remains purely
|
|
1720
|
+
* as a subtree-pruning pre-filter; this is the authoritative gate.
|
|
1721
|
+
*/
|
|
1722
|
+
isInsideAllClippers(node, x, y) {
|
|
1723
|
+
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
|
|
1724
|
+
if (!ancestor.clipChildren) continue;
|
|
1725
|
+
const local = ancestor.worldToLocal(x, y);
|
|
1726
|
+
if (!local || local.x < 0 || local.y < 0 || local.x > ancestor.width || local.y > ancestor.height) {
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
return true;
|
|
1731
|
+
}
|
|
1670
1732
|
/**
|
|
1671
1733
|
* Whether a confirmed geometric hit on `node` at world `(x, y)` is a REAL hit,
|
|
1672
1734
|
* applying the same visibility/input gating as {@link findHitRecursively} but
|
|
@@ -1681,14 +1743,8 @@ var HitTester = class {
|
|
|
1681
1743
|
if (node.opacity <= 0) return false;
|
|
1682
1744
|
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
|
|
1683
1745
|
if (ancestor.opacity <= 0) return false;
|
|
1684
|
-
if (ancestor.clipChildren && ancestor.width > 0 && ancestor.height > 0) {
|
|
1685
|
-
const local = ancestor.worldToLocal(x, y);
|
|
1686
|
-
if (!local || local.x < 0 || local.y < 0 || local.x > ancestor.width || local.y > ancestor.height) {
|
|
1687
|
-
return false;
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
1746
|
}
|
|
1691
|
-
return
|
|
1747
|
+
return this.isInsideAllClippers(node, x, y);
|
|
1692
1748
|
}
|
|
1693
1749
|
};
|
|
1694
1750
|
|
|
@@ -2441,7 +2497,8 @@ var WasmTransformBackend = class {
|
|
|
2441
2497
|
this.lastStatus = WASM_STATUS.CAPACITY;
|
|
2442
2498
|
return;
|
|
2443
2499
|
}
|
|
2444
|
-
const
|
|
2500
|
+
const simd = kernel === "simd" && typeof this.ex.compose_simd === "function";
|
|
2501
|
+
const status = simd ? this.ex.compose_simd() : this.ex.compose_scalar();
|
|
2445
2502
|
this.lastStatus = status;
|
|
2446
2503
|
if (status !== WASM_STATUS.OK) return;
|
|
2447
2504
|
store.wa.set(this.vwa.subarray(0, n));
|
|
@@ -2463,7 +2520,8 @@ var WasmTransformBackend = class {
|
|
|
2463
2520
|
* {@link uploadRuns} after a topology change.
|
|
2464
2521
|
*/
|
|
2465
2522
|
runKernel(kernel = "simd") {
|
|
2466
|
-
const
|
|
2523
|
+
const simd = kernel === "simd" && typeof this.ex.compose_simd === "function";
|
|
2524
|
+
const status = simd ? this.ex.compose_simd() : this.ex.compose_scalar();
|
|
2467
2525
|
this.lastStatus = status;
|
|
2468
2526
|
return status;
|
|
2469
2527
|
}
|
|
@@ -2515,21 +2573,21 @@ var WasmTransformBackend = class {
|
|
|
2515
2573
|
/**
|
|
2516
2574
|
* Compute world-space AABBs for `store` in WASM (G1+), writing back into its
|
|
2517
2575
|
* `aminx/aminy/amaxx/amaxy` arrays. Uploads the local bounds, runs the AABB
|
|
2518
|
-
* pass, reads results back. Result is bit-identical to `computeAabbsJS(store)
|
|
2519
|
-
* `compose` (or `runKernel`) must have populated the world
|
|
2520
|
-
* this pass reads them. For the resident (no-copy)
|
|
2521
|
-
* via {@link boundsView} and read via
|
|
2522
|
-
* {@link runAabbs} instead.
|
|
2576
|
+
* pass, reads results back. Result is bit-identical to `computeAabbsJS(store)`
|
|
2577
|
+
* for both kernels. `compose` (or `runKernel`) must have populated the world
|
|
2578
|
+
* matrices first — this pass reads them. For the resident (no-copy)
|
|
2579
|
+
* integration, write bounds via {@link boundsView} and read via
|
|
2580
|
+
* {@link aabbView} + call {@link runAabbs} instead.
|
|
2523
2581
|
*/
|
|
2524
|
-
computeAabbs(store) {
|
|
2582
|
+
computeAabbs(store, kernel = "simd") {
|
|
2525
2583
|
this.ensure(store.count, store.runCount);
|
|
2526
2584
|
const n = store.count;
|
|
2527
2585
|
this.vbx.set(store.bx.subarray(0, n));
|
|
2528
2586
|
this.vby.set(store.by.subarray(0, n));
|
|
2529
2587
|
this.vbw.set(store.bw.subarray(0, n));
|
|
2530
2588
|
this.vbh.set(store.bh.subarray(0, n));
|
|
2531
|
-
|
|
2532
|
-
if (
|
|
2589
|
+
const ok = this.runAabbs(n, kernel);
|
|
2590
|
+
if (!ok) return;
|
|
2533
2591
|
store.aminx.set(this.vaminx.subarray(0, n));
|
|
2534
2592
|
store.aminy.set(this.vaminy.subarray(0, n));
|
|
2535
2593
|
store.amaxx.set(this.vamaxx.subarray(0, n));
|
|
@@ -2540,8 +2598,9 @@ var WasmTransformBackend = class {
|
|
|
2540
2598
|
* composed). No upload/readback — the per-frame resident path. Returns
|
|
2541
2599
|
* `false` if the kernel rejected `count` (beyond capacity, or uninitialized),
|
|
2542
2600
|
* in which case {@link aabbView} still holds the previous frame's bounds. */
|
|
2543
|
-
runAabbs(count) {
|
|
2544
|
-
|
|
2601
|
+
runAabbs(count, kernel = "simd") {
|
|
2602
|
+
const simd = kernel === "simd" && typeof this.ex.compute_aabbs_simd === "function";
|
|
2603
|
+
this.lastStatus = simd ? this.ex.compute_aabbs_simd(count) : this.ex.compute_aabbs(count);
|
|
2545
2604
|
return this.lastStatus === WASM_STATUS.OK;
|
|
2546
2605
|
}
|
|
2547
2606
|
/** Resident wasm local-bounds input views (`bx,by,bw,bh`) for the AABB pass. */
|
|
@@ -3204,6 +3263,7 @@ var WasmBackendFacade = class {
|
|
|
3204
3263
|
setHit(backend) {
|
|
3205
3264
|
this._hit = backend;
|
|
3206
3265
|
this.hitGridFrame = -1;
|
|
3266
|
+
this.hitGridStructureVersion = -1;
|
|
3207
3267
|
this.hitReason = backend ? "not-applicable" : "not-installed";
|
|
3208
3268
|
}
|
|
3209
3269
|
/** The installed batched-animation backend, or `null` for the JS tick. */
|
|
@@ -3260,6 +3320,13 @@ var WasmBackendFacade = class {
|
|
|
3260
3320
|
// domain.
|
|
3261
3321
|
/** Frame the hit grid was last built for; `-1` forces a rebuild. */
|
|
3262
3322
|
hitGridFrame = -1;
|
|
3323
|
+
/**
|
|
3324
|
+
* Structure version the hit grid was built for; `-1` forces a rebuild.
|
|
3325
|
+
* Half of the cache key the comment above promises: without it, a pointer
|
|
3326
|
+
* query in the same frame as a structural mutation would resolve against
|
|
3327
|
+
* pre-mutation geometry while the JS walk sees live state.
|
|
3328
|
+
*/
|
|
3329
|
+
hitGridStructureVersion = -1;
|
|
3263
3330
|
/** Whether that build succeeded (did not overflow its item budget). */
|
|
3264
3331
|
hitGridOk = false;
|
|
3265
3332
|
/**
|
|
@@ -3399,6 +3466,38 @@ var WasmBackendFacade = class {
|
|
|
3399
3466
|
}
|
|
3400
3467
|
};
|
|
3401
3468
|
|
|
3469
|
+
// src/tree/scene/keyboard.ts
|
|
3470
|
+
function normalizeChord(input) {
|
|
3471
|
+
if (typeof input === "string") {
|
|
3472
|
+
const parts = input.split("+").map((p) => p.trim()).filter(Boolean);
|
|
3473
|
+
const mods = /* @__PURE__ */ new Set();
|
|
3474
|
+
let key2 = "";
|
|
3475
|
+
for (const p of parts) {
|
|
3476
|
+
const low = p.toLowerCase();
|
|
3477
|
+
if (low === "ctrl" || low === "control") mods.add("Control");
|
|
3478
|
+
else if (low === "alt") mods.add("Alt");
|
|
3479
|
+
else if (low === "shift") mods.add("Shift");
|
|
3480
|
+
else if (low === "meta" || low === "cmd" || low === "super") mods.add("Meta");
|
|
3481
|
+
else key2 = p.length === 1 ? p.toUpperCase() : p;
|
|
3482
|
+
}
|
|
3483
|
+
const ordered2 = ["Control", "Alt", "Shift", "Meta"].filter((m) => mods.has(m));
|
|
3484
|
+
return key2 ? [...ordered2, key2].join("+") : ordered2.join("+");
|
|
3485
|
+
}
|
|
3486
|
+
const ordered = [];
|
|
3487
|
+
if (input.ctrlKey) ordered.push("Control");
|
|
3488
|
+
if (input.altKey) ordered.push("Alt");
|
|
3489
|
+
if (input.shiftKey) ordered.push("Shift");
|
|
3490
|
+
if (input.metaKey) ordered.push("Meta");
|
|
3491
|
+
let key = input.key;
|
|
3492
|
+
if (key === " ") key = "Space";
|
|
3493
|
+
else if (key.length === 1) key = key.toUpperCase();
|
|
3494
|
+
if (["Control", "Alt", "Shift", "Meta"].includes(key)) {
|
|
3495
|
+
return ordered.join("+");
|
|
3496
|
+
}
|
|
3497
|
+
ordered.push(key);
|
|
3498
|
+
return ordered.join("+");
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3402
3501
|
// src/tree/Scene.ts
|
|
3403
3502
|
var RANGE_VALUE_ROLES = /* @__PURE__ */ new Set(["slider", "spinbutton", "progressbar", "scrollbar", "meter"]);
|
|
3404
3503
|
var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
|
|
@@ -3412,6 +3511,24 @@ var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
|
|
|
3412
3511
|
"slider",
|
|
3413
3512
|
"combobox"
|
|
3414
3513
|
]);
|
|
3514
|
+
var KEYBOARD_OWNING_ROLES = /* @__PURE__ */ new Set([
|
|
3515
|
+
...INTERACTIVE_A11Y_ROLES,
|
|
3516
|
+
"option",
|
|
3517
|
+
"listbox",
|
|
3518
|
+
"textbox",
|
|
3519
|
+
"searchbox",
|
|
3520
|
+
"spinbutton"
|
|
3521
|
+
]);
|
|
3522
|
+
function ownsKeyboard(el) {
|
|
3523
|
+
if (!el) return false;
|
|
3524
|
+
if (el === document.body || el === document.documentElement) return false;
|
|
3525
|
+
if (el.hasAttribute("data-vecto-a11y-root")) return false;
|
|
3526
|
+
const tag = el.tagName;
|
|
3527
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
3528
|
+
if (el.isContentEditable) return true;
|
|
3529
|
+
const role = el.getAttribute("role");
|
|
3530
|
+
return role !== null && KEYBOARD_OWNING_ROLES.has(role);
|
|
3531
|
+
}
|
|
3415
3532
|
var A11Y_REQUIRED_OWNED = /* @__PURE__ */ new Map([
|
|
3416
3533
|
["grid", /* @__PURE__ */ new Set(["row", "rowgroup"])],
|
|
3417
3534
|
["table", /* @__PURE__ */ new Set(["row", "rowgroup"])],
|
|
@@ -3950,6 +4067,9 @@ var Scene = class _Scene {
|
|
|
3950
4067
|
// server-side (e.g. headless layout / vector export) without jsdom.
|
|
3951
4068
|
a11yRoot;
|
|
3952
4069
|
a11yElements = /* @__PURE__ */ new Map();
|
|
4070
|
+
/** Elements that already have the synthetic Enter/Space activation handler
|
|
4071
|
+
* (see the syncA11y refresh pass) — install-once bookkeeping (#694). */
|
|
4072
|
+
keyboardActivatedA11y = /* @__PURE__ */ new WeakSet();
|
|
3953
4073
|
// --- domain: content-projection — projected DOM, sync state, calibration, selection ---
|
|
3954
4074
|
/** DOM nodes mirroring static text content, keyed by entity id. */
|
|
3955
4075
|
contentElements = /* @__PURE__ */ new Map();
|
|
@@ -4574,6 +4694,15 @@ var Scene = class _Scene {
|
|
|
4574
4694
|
_registerActiveDriverSubtree(entity) {
|
|
4575
4695
|
this._driverTicker.registerSubtree(entity);
|
|
4576
4696
|
}
|
|
4697
|
+
/**
|
|
4698
|
+
* The `dt` of the frame currently executing its update walk (the WASM-mode
|
|
4699
|
+
* pre-pass or the JS-mode interleaved render walk), or `null` outside it.
|
|
4700
|
+
* Read by `Entity._spawnDriver`: a driver spawned mid-walk onto an entity the
|
|
4701
|
+
* batched pass already claimed this frame must be advanced once here, or
|
|
4702
|
+
* `tickDrivers()`'s per-frame stamp would skip it a full frame — a one-frame
|
|
4703
|
+
* JS/WASM divergence. Written in {@link render} next to `_tickBatchedDrivers`.
|
|
4704
|
+
*/
|
|
4705
|
+
_updateWalkDt = null;
|
|
4577
4706
|
// --- domain: render-scheduler — batched driver tick (extraction 6), reads the anim backend ---
|
|
4578
4707
|
/**
|
|
4579
4708
|
* Advance every registered entity's active drivers for this frame, batching
|
|
@@ -4661,6 +4790,13 @@ var Scene = class _Scene {
|
|
|
4661
4790
|
/** Element the pointer listeners are bound to (parent container if present,
|
|
4662
4791
|
* else the canvas). Stored so `destroy()` detaches from the same element. */
|
|
4663
4792
|
pointerEventTarget = null;
|
|
4793
|
+
// --- scene-level keyboard channel state (see on/off/registerShortcut) ---
|
|
4794
|
+
keydownHandlers = [];
|
|
4795
|
+
keyupHandlers = [];
|
|
4796
|
+
/** Chord-normalized shortcut table, matched on keydown only. */
|
|
4797
|
+
shortcuts = [];
|
|
4798
|
+
windowKeyDownHandler = null;
|
|
4799
|
+
windowKeyUpHandler = null;
|
|
4664
4800
|
hasWarnedZeroSize = false;
|
|
4665
4801
|
/**
|
|
4666
4802
|
* Latch for {@link Scene.resize}'s invalid-dimension warning.
|
|
@@ -5267,6 +5403,19 @@ var Scene = class _Scene {
|
|
|
5267
5403
|
window.removeEventListener("blur", this.contentSelectionEndListener);
|
|
5268
5404
|
this.contentSelectionEndListener = null;
|
|
5269
5405
|
}
|
|
5406
|
+
if (typeof window !== "undefined") {
|
|
5407
|
+
if (this.windowKeyDownHandler) {
|
|
5408
|
+
window.removeEventListener("keydown", this.windowKeyDownHandler);
|
|
5409
|
+
}
|
|
5410
|
+
if (this.windowKeyUpHandler) {
|
|
5411
|
+
window.removeEventListener("keyup", this.windowKeyUpHandler);
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
this.windowKeyDownHandler = null;
|
|
5415
|
+
this.windowKeyUpHandler = null;
|
|
5416
|
+
this.keydownHandlers.length = 0;
|
|
5417
|
+
this.keyupHandlers.length = 0;
|
|
5418
|
+
this.shortcuts.length = 0;
|
|
5270
5419
|
if (typeof window !== "undefined" && this.pointerEventTarget && typeof this.pointerEventTarget.removeEventListener === "function") {
|
|
5271
5420
|
if (this.pointerMoveListener) {
|
|
5272
5421
|
this.pointerEventTarget.removeEventListener("pointermove", this.pointerMoveListener);
|
|
@@ -5366,6 +5515,7 @@ var Scene = class _Scene {
|
|
|
5366
5515
|
this.lastTime = typeof performance !== "undefined" ? performance.now() : 0;
|
|
5367
5516
|
this.watchCanvasVisibility();
|
|
5368
5517
|
this.scheduleFrame();
|
|
5518
|
+
this.attachWindowKeyListeners();
|
|
5369
5519
|
const isTextFocused = this.focusedA11yElement instanceof HTMLInputElement || this.focusedA11yElement instanceof HTMLTextAreaElement;
|
|
5370
5520
|
if (isTextFocused && this.renderMode === "onDemand" && !this.caretBlinkTimer) {
|
|
5371
5521
|
this.caretBlinkTimer = setInterval(() => {
|
|
@@ -5433,6 +5583,132 @@ var Scene = class _Scene {
|
|
|
5433
5583
|
}
|
|
5434
5584
|
this._canvasOnScreen = true;
|
|
5435
5585
|
}
|
|
5586
|
+
// --- domain: input — scene-level keyboard channel ---
|
|
5587
|
+
/**
|
|
5588
|
+
* Register a listener on the scene-level keyboard channel.
|
|
5589
|
+
*
|
|
5590
|
+
* Mirrors {@link Entity.on} minus the options parameter (bubble-only, and
|
|
5591
|
+
* there is no capture phase for a single window tap). Handlers fire only
|
|
5592
|
+
* when ALL of these hold:
|
|
5593
|
+
*
|
|
5594
|
+
* 1. the native event was not already `defaultPrevented`,
|
|
5595
|
+
* 2. it is not an auto-repeat (`e.repeat`),
|
|
5596
|
+
* 3. `document.activeElement` does not own the keyboard
|
|
5597
|
+
* (see {@link ownsKeyboard}) — typing in an `<input>` or focusing a
|
|
5598
|
+
* slider-role element suppresses the scene channel, unconditionally,
|
|
5599
|
+
* including modifier chords.
|
|
5600
|
+
*
|
|
5601
|
+
* The listeners are window-level BUBBLE-phase taps, so an entity handler
|
|
5602
|
+
* that calls `nativeEvent.stopPropagation()` keeps the key from ever
|
|
5603
|
+
* reaching this channel. Deliberately not registered in the capture phase:
|
|
5604
|
+
* a window-capture listener would preempt Modal's document-capture trap.
|
|
5605
|
+
*
|
|
5606
|
+
* Listeners attach at `start()` and survive `stop()`; only `destroy()`
|
|
5607
|
+
* removes them.
|
|
5608
|
+
*
|
|
5609
|
+
* @param event - `'keydown'` or `'keyup'`; anything else is rejected with a
|
|
5610
|
+
* dev warning (guards JS callers — TypeScript already narrows this).
|
|
5611
|
+
* @param callback - Handler invoked with a {@link SceneKeyEvent}.
|
|
5612
|
+
* @returns `this` for method chaining.
|
|
5613
|
+
*/
|
|
5614
|
+
on(event, callback) {
|
|
5615
|
+
if (event !== "keydown" && event !== "keyup") {
|
|
5616
|
+
console.warn(
|
|
5617
|
+
`[VectoJS] Scene.on: unsupported event '${String(event)}'. Only 'keydown' and 'keyup' are supported.`
|
|
5618
|
+
);
|
|
5619
|
+
return this;
|
|
5620
|
+
}
|
|
5621
|
+
(event === "keydown" ? this.keydownHandlers : this.keyupHandlers).push(callback);
|
|
5622
|
+
return this;
|
|
5623
|
+
}
|
|
5624
|
+
/**
|
|
5625
|
+
* Remove a listener previously registered with {@link Scene.on}. Silent
|
|
5626
|
+
* no-op for unknown handlers, unsupported event names, or after
|
|
5627
|
+
* {@link Scene.destroy}.
|
|
5628
|
+
*
|
|
5629
|
+
* @returns `this` for method chaining.
|
|
5630
|
+
*/
|
|
5631
|
+
off(event, callback) {
|
|
5632
|
+
if (event !== "keydown" && event !== "keyup") {
|
|
5633
|
+
console.warn(
|
|
5634
|
+
`[VectoJS] Scene.off: unsupported event '${String(event)}'. Only 'keydown' and 'keyup' are supported.`
|
|
5635
|
+
);
|
|
5636
|
+
return this;
|
|
5637
|
+
}
|
|
5638
|
+
const handlers = event === "keydown" ? this.keydownHandlers : this.keyupHandlers;
|
|
5639
|
+
const idx = handlers.indexOf(callback);
|
|
5640
|
+
if (idx !== -1) handlers.splice(idx, 1);
|
|
5641
|
+
return this;
|
|
5642
|
+
}
|
|
5643
|
+
/**
|
|
5644
|
+
* Sugar over the keyboard channel: invoke `spec.handler` whenever a
|
|
5645
|
+
* non-repeat `keydown` whose normalized chord matches `spec.chord`
|
|
5646
|
+
* (via {@link normalizeChord}) passes the same gates as {@link Scene.on} —
|
|
5647
|
+
* including the unconditional `ownsKeyboard(activeElement)` suppression.
|
|
5648
|
+
*
|
|
5649
|
+
* @param spec - `{ chord, handler }`; the chord is normalized once at
|
|
5650
|
+
* registration, so `'ctrl+n'`, `'Control+n'` and `'Ctrl+N'` are aliases.
|
|
5651
|
+
* @returns `this` for method chaining.
|
|
5652
|
+
*/
|
|
5653
|
+
registerShortcut(spec) {
|
|
5654
|
+
this.shortcuts.push({ chord: normalizeChord(spec.chord), handler: spec.handler });
|
|
5655
|
+
return this;
|
|
5656
|
+
}
|
|
5657
|
+
/**
|
|
5658
|
+
* Remove a shortcut previously registered with {@link Scene.registerShortcut}.
|
|
5659
|
+
* Matches on normalized chord + exact handler reference; silent no-op when
|
|
5660
|
+
* nothing matches.
|
|
5661
|
+
*
|
|
5662
|
+
* @param spec - The same spec object (chord spelling may differ; it is
|
|
5663
|
+
* re-normalized before matching).
|
|
5664
|
+
* @returns `this` for method chaining.
|
|
5665
|
+
*/
|
|
5666
|
+
unregisterShortcut(spec) {
|
|
5667
|
+
const chord = normalizeChord(spec.chord);
|
|
5668
|
+
const idx = this.shortcuts.findIndex((s) => s.chord === chord && s.handler === spec.handler);
|
|
5669
|
+
if (idx !== -1) this.shortcuts.splice(idx, 1);
|
|
5670
|
+
return this;
|
|
5671
|
+
}
|
|
5672
|
+
/** Attach the window bubble-phase keyboard listeners exactly once. */
|
|
5673
|
+
attachWindowKeyListeners() {
|
|
5674
|
+
if (this.windowKeyDownHandler || typeof window === "undefined") return;
|
|
5675
|
+
this.windowKeyDownHandler = (e) => this.dispatchKeyboard(e, "keydown");
|
|
5676
|
+
this.windowKeyUpHandler = (e) => this.dispatchKeyboard(e, "keyup");
|
|
5677
|
+
window.addEventListener("keydown", this.windowKeyDownHandler);
|
|
5678
|
+
window.addEventListener("keyup", this.windowKeyUpHandler);
|
|
5679
|
+
}
|
|
5680
|
+
/**
|
|
5681
|
+
* Gate a native keyboard event and fan it out to scene handlers +
|
|
5682
|
+
* chord-matched shortcuts. See {@link Scene.on} for the gate contract.
|
|
5683
|
+
*/
|
|
5684
|
+
dispatchKeyboard(e, type) {
|
|
5685
|
+
if (e.defaultPrevented) return;
|
|
5686
|
+
if (e.repeat) return;
|
|
5687
|
+
const active = typeof document !== "undefined" ? document.activeElement : null;
|
|
5688
|
+
if (ownsKeyboard(active)) return;
|
|
5689
|
+
const event = {
|
|
5690
|
+
type,
|
|
5691
|
+
key: e.key,
|
|
5692
|
+
code: e.code,
|
|
5693
|
+
repeat: e.repeat,
|
|
5694
|
+
ctrlKey: e.ctrlKey,
|
|
5695
|
+
altKey: e.altKey,
|
|
5696
|
+
shiftKey: e.shiftKey,
|
|
5697
|
+
metaKey: e.metaKey,
|
|
5698
|
+
target: e.target,
|
|
5699
|
+
nativeEvent: e,
|
|
5700
|
+
stopPropagation: () => e.stopPropagation(),
|
|
5701
|
+
preventDefault: () => e.preventDefault()
|
|
5702
|
+
};
|
|
5703
|
+
const handlers = type === "keydown" ? this.keydownHandlers : this.keyupHandlers;
|
|
5704
|
+
for (const handler of [...handlers]) handler(event);
|
|
5705
|
+
if (type === "keydown") {
|
|
5706
|
+
const chord = normalizeChord(e);
|
|
5707
|
+
for (const shortcut of [...this.shortcuts]) {
|
|
5708
|
+
if (shortcut.chord === chord) shortcut.handler(event);
|
|
5709
|
+
}
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5436
5712
|
// --- domain: scene-facade — tree accessors ---
|
|
5437
5713
|
/**
|
|
5438
5714
|
* Manually advance the scene clock by `dt` milliseconds and render synchronously.
|
|
@@ -5772,7 +6048,9 @@ var Scene = class _Scene {
|
|
|
5772
6048
|
}
|
|
5773
6049
|
};
|
|
5774
6050
|
el.addEventListener("pointerdown", (e) => {
|
|
5775
|
-
if (typeof capEl.setPointerCapture === "function")
|
|
6051
|
+
if (e.target === capEl && typeof capEl.setPointerCapture === "function") {
|
|
6052
|
+
capEl.setPointerCapture(e.pointerId);
|
|
6053
|
+
}
|
|
5776
6054
|
node.dispatchEvent(new VectoJSEvent("pointerdown", node, e));
|
|
5777
6055
|
});
|
|
5778
6056
|
el.addEventListener("pointerup", (e) => {
|
|
@@ -5880,14 +6158,6 @@ var Scene = class _Scene {
|
|
|
5880
6158
|
}
|
|
5881
6159
|
node.emit("blur", {});
|
|
5882
6160
|
});
|
|
5883
|
-
if (!isNativelyFocusable(el) && attrs.role && INTERACTIVE_A11Y_ROLES.has(attrs.role)) {
|
|
5884
|
-
el.addEventListener("keydown", (e) => {
|
|
5885
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
5886
|
-
e.preventDefault();
|
|
5887
|
-
node.dispatchEvent(new VectoJSEvent("click", node, e));
|
|
5888
|
-
}
|
|
5889
|
-
});
|
|
5890
|
-
}
|
|
5891
6161
|
if (node.a11yFullViewport) {
|
|
5892
6162
|
this.a11yRoot.insertBefore(el, this.a11yRoot.firstChild);
|
|
5893
6163
|
} else {
|
|
@@ -5906,7 +6176,24 @@ var Scene = class _Scene {
|
|
|
5906
6176
|
if (renderOrder !== void 0 && el.style.zIndex !== String(renderOrder)) {
|
|
5907
6177
|
el.style.zIndex = String(renderOrder);
|
|
5908
6178
|
}
|
|
5909
|
-
const
|
|
6179
|
+
const interactiveRole = !isNativelyFocusable(el) && !!attrs.role && INTERACTIVE_A11Y_ROLES.has(attrs.role);
|
|
6180
|
+
if (interactiveRole && !this.keyboardActivatedA11y.has(el)) {
|
|
6181
|
+
el.addEventListener("keydown", (e) => {
|
|
6182
|
+
if (e.key === "Enter") {
|
|
6183
|
+
e.preventDefault();
|
|
6184
|
+
node.dispatchEvent(new VectoJSEvent("click", node, e));
|
|
6185
|
+
} else if (e.key === " ") {
|
|
6186
|
+
e.preventDefault();
|
|
6187
|
+
}
|
|
6188
|
+
});
|
|
6189
|
+
el.addEventListener("keyup", (e) => {
|
|
6190
|
+
if (e.key === " ") {
|
|
6191
|
+
node.dispatchEvent(new VectoJSEvent("click", node, e));
|
|
6192
|
+
}
|
|
6193
|
+
});
|
|
6194
|
+
this.keyboardActivatedA11y.add(el);
|
|
6195
|
+
}
|
|
6196
|
+
const implicitTabIndex = interactiveRole ? 0 : null;
|
|
5910
6197
|
const desiredTabIndex = attrs.tabIndex ?? implicitTabIndex;
|
|
5911
6198
|
if (desiredTabIndex === null) {
|
|
5912
6199
|
if (el.hasAttribute("tabindex")) el.removeAttribute("tabindex");
|
|
@@ -6848,6 +7135,7 @@ var Scene = class _Scene {
|
|
|
6848
7135
|
this.renderOrderCounter = 0;
|
|
6849
7136
|
this.a11yRenderOrders.clear();
|
|
6850
7137
|
this.activePortalsThisFrame.clear();
|
|
7138
|
+
this._updateWalkDt = dt;
|
|
6851
7139
|
this._tickBatchedDrivers(dt);
|
|
6852
7140
|
}
|
|
6853
7141
|
const computeEntities = this._computeEntitiesFor(this._structureVersion);
|
|
@@ -7018,6 +7306,7 @@ var Scene = class _Scene {
|
|
|
7018
7306
|
};
|
|
7019
7307
|
updateWalk(this.root);
|
|
7020
7308
|
for (const overlay of this.overlayRoot.children) updateWalk(overlay);
|
|
7309
|
+
this._updateWalkDt = null;
|
|
7021
7310
|
}
|
|
7022
7311
|
const transformTiming = this.phases.userTiming ? beginVectoUserTiming(VECTO_USER_TIMING.scene.transform) : null;
|
|
7023
7312
|
const wasmT0 = this.phases.enabled ? performance.now() : 0;
|
|
@@ -7218,6 +7507,7 @@ var Scene = class _Scene {
|
|
|
7218
7507
|
for (const overlay of this.overlayRoot.children) {
|
|
7219
7508
|
renderNode(overlay, 1, 0, 0, 1, 0, 0, 1);
|
|
7220
7509
|
}
|
|
7510
|
+
if (isMainRenderer) this._updateWalkDt = null;
|
|
7221
7511
|
if (this.phases.enabled) this.phases.record("drawWalk", performance.now() - drawT0);
|
|
7222
7512
|
if (drawTiming) endVectoUserTiming(drawTiming);
|
|
7223
7513
|
if (this.phases.userTiming) {
|
|
@@ -8363,6 +8653,7 @@ export {
|
|
|
8363
8653
|
GlyphRasterAtlas,
|
|
8364
8654
|
GridTextEntity,
|
|
8365
8655
|
Group,
|
|
8656
|
+
KEYBOARD_OWNING_ROLES,
|
|
8366
8657
|
MSDFTextEntity,
|
|
8367
8658
|
PARTICLE_OFFSET_LIFE,
|
|
8368
8659
|
PARTICLE_OFFSET_ORIGIN_X,
|
|
@@ -8394,6 +8685,8 @@ export {
|
|
|
8394
8685
|
isSafeUrl,
|
|
8395
8686
|
loadSpline,
|
|
8396
8687
|
measureVectoUserTiming,
|
|
8688
|
+
normalizeChord,
|
|
8689
|
+
ownsKeyboard,
|
|
8397
8690
|
parseColorToRGBA,
|
|
8398
8691
|
polySegmentToBezier,
|
|
8399
8692
|
sanitizeUrl,
|