@tldraw/editor 5.4.0-next.b120f7febb0f → 5.4.0-next.ebc95cf01337
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-cjs/index.d.ts +47 -46
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/editor/Editor.js +17 -14
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +7 -3
- package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +63 -21
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/utils/normalizeWheel.js +2 -0
- package/dist-cjs/lib/utils/normalizeWheel.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +47 -46
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/editor/Editor.mjs +17 -14
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +7 -3
- package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +63 -21
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/utils/normalizeWheel.mjs +2 -0
- package/dist-esm/lib/utils/normalizeWheel.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/lib/editor/Editor.ts +25 -24
- package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +7 -3
- package/src/lib/primitives/geometry/Group2d.ts +63 -27
- package/src/lib/utils/normalizeWheel.test.ts +39 -0
- package/src/lib/utils/normalizeWheel.ts +6 -0
- package/src/version.ts +3 -3
|
@@ -19,9 +19,13 @@ function deriveShapeIdsInCurrentPage(store, getCurrentPageId) {
|
|
|
19
19
|
function fromScratch() {
|
|
20
20
|
const currentPageId = getCurrentPageId();
|
|
21
21
|
lastPageId = currentPageId;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const result = /* @__PURE__ */ new Set();
|
|
23
|
+
for (const id of shapesIndex.get()) {
|
|
24
|
+
if (isShapeInPage(store, currentPageId, store.get(id))) {
|
|
25
|
+
result.add(id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
25
29
|
}
|
|
26
30
|
return computed("_shapeIdsInCurrentPage", (prevValue, lastComputedEpoch) => {
|
|
27
31
|
if (isUninitialized(prevValue)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/editor/derivations/shapeIdsInCurrentPage.ts"],
|
|
4
|
-
"sourcesContent": ["import { computed, isUninitialized, RESET_VALUE, withDiff } from '@tldraw/state'\nimport { IncrementalSetConstructor } from '@tldraw/store'\nimport {\n\tisPageId,\n\tisShape,\n\tisShapeId,\n\tTLPageId,\n\tTLShape,\n\tTLShapeId,\n\tTLStore,\n} from '@tldraw/tlschema'\n\n/**\n * Get whether a shape is in the current page.\n *\n * @param store - The tldraw store.\n * @param pageId - The id of the page to check.\n * @param shape - The the shape to check.\n */\nconst isShapeInPage = (store: TLStore, pageId: TLPageId, shape: TLShape): boolean => {\n\twhile (!isPageId(shape.parentId)) {\n\t\tconst parent = store.get(shape.parentId)\n\t\tif (!parent) return false\n\t\tshape = parent\n\t}\n\n\treturn shape.parentId === pageId\n}\n\n/**\n * A derivation that returns a list of shape ids in the current page.\n *\n * @param store - The tldraw store.\n * @param getCurrentPageId - A function that returns the current page id.\n */\nexport function deriveShapeIdsInCurrentPage(store: TLStore, getCurrentPageId: () => TLPageId) {\n\tconst shapesIndex = store.query.ids('shape')\n\tlet lastPageId: null | TLPageId = null\n\tfunction fromScratch() {\n\t\tconst currentPageId = getCurrentPageId()\n\t\tlastPageId = currentPageId\n\t\
|
|
5
|
-
"mappings": "AAAA,SAAS,UAAU,iBAAiB,aAAa,gBAAgB;AACjE,SAAS,iCAAiC;AAC1C;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OAKM;AASP,MAAM,gBAAgB,CAAC,OAAgB,QAAkB,UAA4B;AACpF,SAAO,CAAC,SAAS,MAAM,QAAQ,GAAG;AACjC,UAAM,SAAS,MAAM,IAAI,MAAM,QAAQ;AACvC,QAAI,CAAC,OAAQ,QAAO;AACpB,YAAQ;AAAA,EACT;AAEA,SAAO,MAAM,aAAa;AAC3B;AAQO,SAAS,4BAA4B,OAAgB,kBAAkC;AAC7F,QAAM,cAAc,MAAM,MAAM,IAAI,OAAO;AAC3C,MAAI,aAA8B;AAClC,WAAS,cAAc;AACtB,UAAM,gBAAgB,iBAAiB;AACvC,iBAAa;AACb,
|
|
4
|
+
"sourcesContent": ["import { computed, isUninitialized, RESET_VALUE, withDiff } from '@tldraw/state'\nimport { IncrementalSetConstructor } from '@tldraw/store'\nimport {\n\tisPageId,\n\tisShape,\n\tisShapeId,\n\tTLPageId,\n\tTLShape,\n\tTLShapeId,\n\tTLStore,\n} from '@tldraw/tlschema'\n\n/**\n * Get whether a shape is in the current page.\n *\n * @param store - The tldraw store.\n * @param pageId - The id of the page to check.\n * @param shape - The the shape to check.\n */\nconst isShapeInPage = (store: TLStore, pageId: TLPageId, shape: TLShape): boolean => {\n\twhile (!isPageId(shape.parentId)) {\n\t\tconst parent = store.get(shape.parentId)\n\t\tif (!parent) return false\n\t\tshape = parent\n\t}\n\n\treturn shape.parentId === pageId\n}\n\n/**\n * A derivation that returns a list of shape ids in the current page.\n *\n * @param store - The tldraw store.\n * @param getCurrentPageId - A function that returns the current page id.\n */\nexport function deriveShapeIdsInCurrentPage(store: TLStore, getCurrentPageId: () => TLPageId) {\n\tconst shapesIndex = store.query.ids('shape')\n\tlet lastPageId: null | TLPageId = null\n\tfunction fromScratch() {\n\t\tconst currentPageId = getCurrentPageId()\n\t\tlastPageId = currentPageId\n\t\tconst result = new Set<TLShapeId>()\n\t\tfor (const id of shapesIndex.get()) {\n\t\t\tif (isShapeInPage(store, currentPageId, store.get(id)!)) {\n\t\t\t\tresult.add(id)\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\treturn computed<Set<TLShapeId>>('_shapeIdsInCurrentPage', (prevValue, lastComputedEpoch) => {\n\t\tif (isUninitialized(prevValue)) {\n\t\t\treturn fromScratch()\n\t\t}\n\n\t\tconst currentPageId = getCurrentPageId()\n\n\t\tif (currentPageId !== lastPageId) {\n\t\t\treturn fromScratch()\n\t\t}\n\n\t\tconst diff = store.history.getDiffSince(lastComputedEpoch)\n\n\t\tif (diff === RESET_VALUE) {\n\t\t\treturn fromScratch()\n\t\t}\n\n\t\tconst builder = new IncrementalSetConstructor<TLShapeId>(\n\t\t\tprevValue\n\t\t) as IncrementalSetConstructor<TLShapeId>\n\n\t\tfor (const changes of diff) {\n\t\t\tfor (const record of Object.values(changes.added)) {\n\t\t\t\tif (isShape(record) && isShapeInPage(store, currentPageId, record)) {\n\t\t\t\t\tbuilder.add(record.id)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const [_from, to] of Object.values(changes.updated)) {\n\t\t\t\tif (isShape(to)) {\n\t\t\t\t\tif (isShapeInPage(store, currentPageId, to)) {\n\t\t\t\t\t\tbuilder.add(to.id)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbuilder.remove(to.id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const id of Object.keys(changes.removed)) {\n\t\t\t\tif (isShapeId(id)) {\n\t\t\t\t\tbuilder.remove(id)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst result = builder.get()\n\t\tif (!result) {\n\t\t\treturn prevValue\n\t\t}\n\n\t\treturn withDiff(result.value, result.diff)\n\t})\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,UAAU,iBAAiB,aAAa,gBAAgB;AACjE,SAAS,iCAAiC;AAC1C;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OAKM;AASP,MAAM,gBAAgB,CAAC,OAAgB,QAAkB,UAA4B;AACpF,SAAO,CAAC,SAAS,MAAM,QAAQ,GAAG;AACjC,UAAM,SAAS,MAAM,IAAI,MAAM,QAAQ;AACvC,QAAI,CAAC,OAAQ,QAAO;AACpB,YAAQ;AAAA,EACT;AAEA,SAAO,MAAM,aAAa;AAC3B;AAQO,SAAS,4BAA4B,OAAgB,kBAAkC;AAC7F,QAAM,cAAc,MAAM,MAAM,IAAI,OAAO;AAC3C,MAAI,aAA8B;AAClC,WAAS,cAAc;AACtB,UAAM,gBAAgB,iBAAiB;AACvC,iBAAa;AACb,UAAM,SAAS,oBAAI,IAAe;AAClC,eAAW,MAAM,YAAY,IAAI,GAAG;AACnC,UAAI,cAAc,OAAO,eAAe,MAAM,IAAI,EAAE,CAAE,GAAG;AACxD,eAAO,IAAI,EAAE;AAAA,MACd;AAAA,IACD;AACA,WAAO;AAAA,EACR;AACA,SAAO,SAAyB,0BAA0B,CAAC,WAAW,sBAAsB;AAC3F,QAAI,gBAAgB,SAAS,GAAG;AAC/B,aAAO,YAAY;AAAA,IACpB;AAEA,UAAM,gBAAgB,iBAAiB;AAEvC,QAAI,kBAAkB,YAAY;AACjC,aAAO,YAAY;AAAA,IACpB;AAEA,UAAM,OAAO,MAAM,QAAQ,aAAa,iBAAiB;AAEzD,QAAI,SAAS,aAAa;AACzB,aAAO,YAAY;AAAA,IACpB;AAEA,UAAM,UAAU,IAAI;AAAA,MACnB;AAAA,IACD;AAEA,eAAW,WAAW,MAAM;AAC3B,iBAAW,UAAU,OAAO,OAAO,QAAQ,KAAK,GAAG;AAClD,YAAI,QAAQ,MAAM,KAAK,cAAc,OAAO,eAAe,MAAM,GAAG;AACnE,kBAAQ,IAAI,OAAO,EAAE;AAAA,QACtB;AAAA,MACD;AAEA,iBAAW,CAAC,OAAO,EAAE,KAAK,OAAO,OAAO,QAAQ,OAAO,GAAG;AACzD,YAAI,QAAQ,EAAE,GAAG;AAChB,cAAI,cAAc,OAAO,eAAe,EAAE,GAAG;AAC5C,oBAAQ,IAAI,GAAG,EAAE;AAAA,UAClB,OAAO;AACN,oBAAQ,OAAO,GAAG,EAAE;AAAA,UACrB;AAAA,QACD;AAAA,MACD;AAEA,iBAAW,MAAM,OAAO,KAAK,QAAQ,OAAO,GAAG;AAC9C,YAAI,UAAU,EAAE,GAAG;AAClB,kBAAQ,OAAO,EAAE;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AAEA,UAAM,SAAS,QAAQ,IAAI;AAC3B,QAAI,CAAC,QAAQ;AACZ,aAAO;AAAA,IACR;AAEA,WAAO,SAAS,OAAO,OAAO,OAAO,IAAI;AAAA,EAC1C,CAAC;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EMPTY_ARRAY } from "@tldraw/state";
|
|
2
1
|
import { assert, invLerp, lerp } from "@tldraw/utils";
|
|
3
2
|
import { Box } from "../Box.mjs";
|
|
4
3
|
import { Vec } from "../Vec.mjs";
|
|
@@ -24,7 +23,15 @@ class Group2d extends Geometry2d {
|
|
|
24
23
|
}
|
|
25
24
|
getVertices(filters) {
|
|
26
25
|
if (this.isExcludedByFilter(filters)) return [];
|
|
27
|
-
|
|
26
|
+
const vertices = [];
|
|
27
|
+
for (const child of this.children) {
|
|
28
|
+
if (child.isExcludedByFilter(filters)) continue;
|
|
29
|
+
const childVertices = child.getVertices(filters);
|
|
30
|
+
for (let i = 0, n = childVertices.length; i < n; i++) {
|
|
31
|
+
vertices.push(childVertices[i]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return vertices;
|
|
28
35
|
}
|
|
29
36
|
nearestPoint(point, filters) {
|
|
30
37
|
let dist = Infinity;
|
|
@@ -59,38 +66,73 @@ class Group2d extends Geometry2d {
|
|
|
59
66
|
return smallestDistance;
|
|
60
67
|
}
|
|
61
68
|
hitTestPoint(point, margin, hitInside, filters = Geometry2dFilters.EXCLUDE_LABELS) {
|
|
62
|
-
|
|
69
|
+
for (const child of this.children) {
|
|
70
|
+
if (child.isExcludedByFilter(filters)) continue;
|
|
71
|
+
if (child.hitTestPoint(point, margin, hitInside)) return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
63
74
|
}
|
|
64
75
|
hitTestLineSegment(A, B, zoom, filters = Geometry2dFilters.EXCLUDE_LABELS) {
|
|
65
|
-
|
|
76
|
+
for (const child of this.children) {
|
|
77
|
+
if (child.isExcludedByFilter(filters)) continue;
|
|
78
|
+
if (child.hitTestLineSegment(A, B, zoom)) return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
66
81
|
}
|
|
67
82
|
intersectLineSegment(A, B, filters) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
const result = [];
|
|
84
|
+
for (const child of this.children) {
|
|
85
|
+
if (child.isExcludedByFilter(filters)) continue;
|
|
86
|
+
const hits = child.intersectLineSegment(A, B, filters);
|
|
87
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
88
|
+
result.push(hits[i]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
72
92
|
}
|
|
73
93
|
intersectCircle(center, radius, filters) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
94
|
+
const result = [];
|
|
95
|
+
for (const child of this.children) {
|
|
96
|
+
if (child.isExcludedByFilter(filters)) continue;
|
|
97
|
+
const hits = child.intersectCircle(center, radius, filters);
|
|
98
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
99
|
+
result.push(hits[i]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
78
103
|
}
|
|
79
104
|
getBoundsVertices() {
|
|
80
105
|
if (this.excludeFromShapeBounds) return [];
|
|
81
|
-
|
|
106
|
+
const vertices = [];
|
|
107
|
+
for (const child of this.children) {
|
|
108
|
+
const childVertices = child.getBoundsVertices();
|
|
109
|
+
for (let i = 0, n = childVertices.length; i < n; i++) {
|
|
110
|
+
vertices.push(childVertices[i]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return vertices;
|
|
82
114
|
}
|
|
83
115
|
intersectPolygon(polygon, filters) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
116
|
+
const result = [];
|
|
117
|
+
for (const child of this.children) {
|
|
118
|
+
if (child.isExcludedByFilter(filters)) continue;
|
|
119
|
+
const hits = child.intersectPolygon(polygon, filters);
|
|
120
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
121
|
+
result.push(hits[i]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
88
125
|
}
|
|
89
126
|
intersectPolyline(polyline, filters) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
127
|
+
const result = [];
|
|
128
|
+
for (const child of this.children) {
|
|
129
|
+
if (child.isExcludedByFilter(filters)) continue;
|
|
130
|
+
const hits = child.intersectPolyline(polyline, filters);
|
|
131
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
132
|
+
result.push(hits[i]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
94
136
|
}
|
|
95
137
|
interpolateAlongEdge(t, filters) {
|
|
96
138
|
const totalLength = this.getLength(filters);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/primitives/geometry/Group2d.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": "AAAA,SAAS,
|
|
4
|
+
"sourcesContent": ["import { assert, invLerp, lerp } from '@tldraw/utils'\nimport { Box } from '../Box'\nimport { Mat } from '../Mat'\nimport { Vec, VecLike } from '../Vec'\nimport { Geometry2d, Geometry2dFilters, Geometry2dOptions } from './Geometry2d'\n\n/** @public */\nexport class Group2d extends Geometry2d {\n\tchildren: Geometry2d[] = []\n\tignoredChildren: Geometry2d[] = []\n\n\tconstructor(\n\t\tconfig: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {\n\t\t\tchildren: Geometry2d[]\n\t\t}\n\t) {\n\t\tsuper({ ...config, isClosed: true, isFilled: false })\n\n\t\tconst addChildren = (children: Geometry2d[]) => {\n\t\t\tfor (const child of children) {\n\t\t\t\tif (child instanceof Group2d) {\n\t\t\t\t\taddChildren(child.children)\n\t\t\t\t} else if (child.ignore) {\n\t\t\t\t\tthis.ignoredChildren.push(child)\n\t\t\t\t} else {\n\t\t\t\t\tthis.children.push(child)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\taddChildren(config.children)\n\n\t\tif (this.children.length === 0) throw Error('Group2d must have at least one child')\n\t}\n\n\toverride getVertices(filters: Geometry2dFilters): Vec[] {\n\t\tif (this.isExcludedByFilter(filters)) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childVertices = child.getVertices(filters)\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {\n\t\tlet dist = Infinity\n\t\tlet nearest: Vec | undefined\n\n\t\tconst { children } = this\n\n\t\tif (children.length === 0) {\n\t\t\tthrow Error('no children')\n\t\t}\n\n\t\tlet p: Vec\n\t\tlet d: number\n\t\tfor (const child of children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tp = child.nearestPoint(point, filters)\n\t\t\td = Vec.Dist2(p, point)\n\t\t\tif (d < dist) {\n\t\t\t\tdist = d\n\t\t\t\tnearest = p\n\t\t\t}\n\t\t}\n\t\tif (!nearest) throw Error('nearest point not found')\n\t\treturn nearest\n\t}\n\n\toverride distanceToPoint(point: VecLike, hitInside = false, filters?: Geometry2dFilters) {\n\t\tlet smallestDistance = Infinity\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst distance = child.distanceToPoint(point, hitInside, filters)\n\t\t\tif (distance < smallestDistance) {\n\t\t\t\tsmallestDistance = distance\n\t\t\t}\n\t\t}\n\t\treturn smallestDistance\n\t}\n\n\toverride hitTestPoint(\n\t\tpoint: VecLike,\n\t\tmargin: number,\n\t\thitInside: boolean,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestPoint(point, margin, hitInside)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride hitTestLineSegment(\n\t\tA: VecLike,\n\t\tB: VecLike,\n\t\tzoom: number,\n\t\tfilters = Geometry2dFilters.EXCLUDE_LABELS\n\t): boolean {\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tif (child.hitTestLineSegment(A, B, zoom)) return true\n\t\t}\n\t\treturn false\n\t}\n\n\toverride intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectLineSegment(A, B, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectCircle(center, radius, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride getBoundsVertices(): Vec[] {\n\t\tif (this.excludeFromShapeBounds) return []\n\t\tconst vertices: Vec[] = []\n\t\tfor (const child of this.children) {\n\t\t\tconst childVertices = child.getBoundsVertices()\n\t\t\tfor (let i = 0, n = childVertices.length; i < n; i++) {\n\t\t\t\tvertices.push(childVertices[i])\n\t\t\t}\n\t\t}\n\t\treturn vertices\n\t}\n\n\toverride intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolygon(polygon, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters) {\n\t\tconst result: VecLike[] = []\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst hits = child.intersectPolyline(polyline, filters)\n\t\t\tfor (let i = 0, n = hits.length; i < n; i++) {\n\t\t\t\tresult.push(hits[i])\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\toverride interpolateAlongEdge(t: number, filters?: Geometry2dFilters): Vec {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tconst distanceToTravel = t * totalLength\n\t\tlet distanceTraveled = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.length\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\t\t\tif (newDistanceTraveled >= distanceToTravel) {\n\t\t\t\treturn child.interpolateAlongEdge(\n\t\t\t\t\tinvLerp(distanceTraveled, newDistanceTraveled, distanceToTravel),\n\t\t\t\t\tfilters\n\t\t\t\t)\n\t\t\t}\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\treturn this.children[this.children.length - 1].interpolateAlongEdge(1, filters)\n\t}\n\n\toverride uninterpolateAlongEdge(point: VecLike, filters?: Geometry2dFilters): number {\n\t\tconst totalLength = this.getLength(filters)\n\n\t\tlet closestChild = null\n\t\tlet closestDistance = Infinity\n\t\tlet distanceTraveled = 0\n\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tconst childLength = child.getLength(filters)\n\t\t\tconst newDistanceTraveled = distanceTraveled + childLength\n\n\t\t\tconst distance = child.distanceToPoint(point, false, filters)\n\t\t\tif (distance < closestDistance) {\n\t\t\t\tclosestDistance = distance\n\t\t\t\tclosestChild = {\n\t\t\t\t\tstartLength: distanceTraveled,\n\t\t\t\t\tendLength: newDistanceTraveled,\n\t\t\t\t\tchild,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdistanceTraveled = newDistanceTraveled\n\t\t}\n\n\t\tassert(closestChild)\n\n\t\tconst normalizedDistanceInChild = closestChild.child.uninterpolateAlongEdge(point, filters)\n\t\tconst childTLength = lerp(\n\t\t\tclosestChild.startLength,\n\t\t\tclosestChild.endLength,\n\t\t\tnormalizedDistanceInChild\n\t\t)\n\t\treturn childTLength / totalLength\n\t}\n\n\toverride transform(transform: Mat): Geometry2d {\n\t\treturn new Group2d({\n\t\t\tchildren: this.children.map((c) => c.transform(transform)),\n\t\t\tisLabel: this.isLabel,\n\t\t\tdebugColor: this.debugColor,\n\t\t\tignore: this.ignore,\n\t\t})\n\t}\n\n\tgetArea() {\n\t\t// todo: this is a temporary solution, assuming that the first child defines the group size; we would want to flatten the group and then find the area of the hull polygon\n\t\treturn this.children[0].area\n\t}\n\n\ttoSimpleSvgPath() {\n\t\tlet path = ''\n\t\tfor (const child of this.children) {\n\t\t\tpath += child.toSimpleSvgPath()\n\t\t}\n\n\t\tconst corners = Box.FromPoints(this.boundsVertices).corners\n\t\t// draw just a few pixels around each corner, e.g. an L shape for the bottom left\n\n\t\tfor (let i = 0, n = corners.length; i < n; i++) {\n\t\t\tconst corner = corners[i]\n\t\t\tconst prevCorner = corners[(i - 1 + n) % n]\n\t\t\tconst prevDist = corner.dist(prevCorner)\n\t\t\tconst nextCorner = corners[(i + 1) % n]\n\t\t\tconst nextDist = corner.dist(nextCorner)\n\n\t\t\tconst A = corner.clone().lrp(prevCorner, 4 / prevDist)\n\t\t\tconst B = corner\n\t\t\tconst C = corner.clone().lrp(nextCorner, 4 / nextDist)\n\n\t\t\tpath += `M${A.x},${A.y} L${B.x},${B.y} L${C.x},${C.y} `\n\t\t}\n\t\treturn path\n\t}\n\n\tgetLength(filters?: Geometry2dFilters): number {\n\t\tlet length = 0\n\t\tfor (const child of this.children) {\n\t\t\tif (child.isExcludedByFilter(filters)) continue\n\t\t\tlength += child.length\n\t\t}\n\t\treturn length\n\t}\n\n\tgetSvgPathData(): string {\n\t\treturn this.children.map((c, i) => (c.isLabel ? '' : c.getSvgPathData(i === 0))).join(' ')\n\t}\n\n\toverlapsPolygon(polygon: VecLike[]): boolean {\n\t\treturn this.children.some((child) => child.overlapsPolygon(polygon))\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,8 @@ import { tlenv } from "../globals/environment.mjs";
|
|
|
2
2
|
const MAX_ZOOM_STEP = 10;
|
|
3
3
|
function normalizeWheel(event) {
|
|
4
4
|
let { deltaY, deltaX } = event;
|
|
5
|
+
if (!Number.isFinite(deltaX)) deltaX = 0;
|
|
6
|
+
if (!Number.isFinite(deltaY)) deltaY = 0;
|
|
5
7
|
let deltaZ = 0;
|
|
6
8
|
if (event.ctrlKey || event.altKey || event.metaKey) {
|
|
7
9
|
deltaZ = (Math.abs(deltaY) > MAX_ZOOM_STEP ? MAX_ZOOM_STEP * Math.sign(deltaY) : deltaY) / 100;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/utils/normalizeWheel.ts"],
|
|
4
|
-
"sourcesContent": ["import { tlenv } from '../globals/environment'\n\nconst MAX_ZOOM_STEP = 10\n\n/**\n * Normalizes a wheel event, so that the delta values are consistent across different browsers or devices. Adapted from https://stackoverflow.com/a/13650579.\n * @param event - The wheel event to normalize.\n * @returns The normalized wheel event.\n * @internal */\nexport function normalizeWheel(event: WheelEvent | React.WheelEvent<HTMLElement>) {\n\tlet { deltaY, deltaX } = event\n\tlet deltaZ = 0\n\n\t// wheeling\n\tif (event.ctrlKey || event.altKey || event.metaKey) {\n\t\tdeltaZ = (Math.abs(deltaY) > MAX_ZOOM_STEP ? MAX_ZOOM_STEP * Math.sign(deltaY) : deltaY) / 100\n\t} else {\n\t\tif (event.shiftKey && !tlenv.isDarwin && !tlenv.isIos) {\n\t\t\tdeltaX = deltaY\n\t\t\tdeltaY = 0\n\t\t}\n\t}\n\n\treturn { x: -deltaX, y: -deltaY, z: -deltaZ }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,aAAa;AAEtB,MAAM,gBAAgB;AAOf,SAAS,eAAe,OAAmD;AACjF,MAAI,EAAE,QAAQ,OAAO,IAAI;
|
|
4
|
+
"sourcesContent": ["import { tlenv } from '../globals/environment'\n\nconst MAX_ZOOM_STEP = 10\n\n/**\n * Normalizes a wheel event, so that the delta values are consistent across different browsers or devices. Adapted from https://stackoverflow.com/a/13650579.\n * @param event - The wheel event to normalize.\n * @returns The normalized wheel event.\n * @internal */\nexport function normalizeWheel(event: WheelEvent | React.WheelEvent<HTMLElement>) {\n\tlet { deltaY, deltaX } = event\n\n\t// Coerce non-finite deltas to no movement so they can't propagate into the camera and\n\t// crash validation. Also covers deltaZ below, which is derived from deltaY.\n\tif (!Number.isFinite(deltaX)) deltaX = 0\n\tif (!Number.isFinite(deltaY)) deltaY = 0\n\n\tlet deltaZ = 0\n\n\t// wheeling\n\tif (event.ctrlKey || event.altKey || event.metaKey) {\n\t\tdeltaZ = (Math.abs(deltaY) > MAX_ZOOM_STEP ? MAX_ZOOM_STEP * Math.sign(deltaY) : deltaY) / 100\n\t} else {\n\t\tif (event.shiftKey && !tlenv.isDarwin && !tlenv.isIos) {\n\t\t\tdeltaX = deltaY\n\t\t\tdeltaY = 0\n\t\t}\n\t}\n\n\treturn { x: -deltaX, y: -deltaY, z: -deltaZ }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,aAAa;AAEtB,MAAM,gBAAgB;AAOf,SAAS,eAAe,OAAmD;AACjF,MAAI,EAAE,QAAQ,OAAO,IAAI;AAIzB,MAAI,CAAC,OAAO,SAAS,MAAM,EAAG,UAAS;AACvC,MAAI,CAAC,OAAO,SAAS,MAAM,EAAG,UAAS;AAEvC,MAAI,SAAS;AAGb,MAAI,MAAM,WAAW,MAAM,UAAU,MAAM,SAAS;AACnD,cAAU,KAAK,IAAI,MAAM,IAAI,gBAAgB,gBAAgB,KAAK,KAAK,MAAM,IAAI,UAAU;AAAA,EAC5F,OAAO;AACN,QAAI,MAAM,YAAY,CAAC,MAAM,YAAY,CAAC,MAAM,OAAO;AACtD,eAAS;AACT,eAAS;AAAA,IACV;AAAA,EACD;AAEA,SAAO,EAAE,GAAG,CAAC,QAAQ,GAAG,CAAC,QAAQ,GAAG,CAAC,OAAO;AAC7C;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "5.4.0-next.
|
|
1
|
+
const version = "5.4.0-next.ebc95cf01337";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2026-05-06T16:28:18.473Z",
|
|
4
|
-
minor: "2026-08-
|
|
5
|
-
patch: "2026-08-
|
|
4
|
+
minor: "2026-08-13T15:10:11.500Z",
|
|
5
|
+
patch: "2026-08-13T15:10:11.500Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
package/dist-esm/version.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.4.0-next.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.4.0-next.ebc95cf01337'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-08-13T15:10:11.500Z',\n\tpatch: '2026-08-13T15:10:11.500Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (editor).",
|
|
4
|
-
"version": "5.4.0-next.
|
|
4
|
+
"version": "5.4.0-next.ebc95cf01337",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"@tiptap/core": "^3.12.1",
|
|
50
50
|
"@tiptap/pm": "^3.12.1",
|
|
51
51
|
"@tiptap/react": "^3.12.1",
|
|
52
|
-
"@tldraw/state": "5.4.0-next.
|
|
53
|
-
"@tldraw/state-react": "5.4.0-next.
|
|
54
|
-
"@tldraw/store": "5.4.0-next.
|
|
55
|
-
"@tldraw/tlschema": "5.4.0-next.
|
|
56
|
-
"@tldraw/utils": "5.4.0-next.
|
|
57
|
-
"@tldraw/validate": "5.4.0-next.
|
|
52
|
+
"@tldraw/state": "5.4.0-next.ebc95cf01337",
|
|
53
|
+
"@tldraw/state-react": "5.4.0-next.ebc95cf01337",
|
|
54
|
+
"@tldraw/store": "5.4.0-next.ebc95cf01337",
|
|
55
|
+
"@tldraw/tlschema": "5.4.0-next.ebc95cf01337",
|
|
56
|
+
"@tldraw/utils": "5.4.0-next.ebc95cf01337",
|
|
57
|
+
"@tldraw/validate": "5.4.0-next.ebc95cf01337",
|
|
58
58
|
"classnames": "^2.5.1",
|
|
59
59
|
"eventemitter3": "^4.0.7",
|
|
60
60
|
"idb": "^7.1.1",
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -5862,6 +5862,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5862
5862
|
const margin = this.options.hitTestMargin / this.getZoomLevel()
|
|
5863
5863
|
const sortedShapes = this.getCurrentPageShapesSorted()
|
|
5864
5864
|
|
|
5865
|
+
// iterate from the top (highest z-index) to find the top-most matching shape
|
|
5865
5866
|
for (let i = sortedShapes.length - 1; i >= 0; i--) {
|
|
5866
5867
|
const shape = sortedShapes[i]
|
|
5867
5868
|
if (shape.type === 'group') continue
|
|
@@ -5911,28 +5912,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5911
5912
|
const searchMargin = Math.max(innerMargin, outerMargin, this.getHitTestMargin())
|
|
5912
5913
|
const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, searchMargin)
|
|
5913
5914
|
|
|
5914
|
-
const shapesToCheck =
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
: this.getCurrentPageShapesSorted()
|
|
5918
|
-
).filter((shape) => {
|
|
5919
|
-
// Frame-like shapes have labels positioned above the shape (outside bounds), so always include them
|
|
5920
|
-
if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) return false
|
|
5915
|
+
const shapesToCheck = opts.renderingOnly
|
|
5916
|
+
? this.getCurrentPageRenderingShapesSorted()
|
|
5917
|
+
: this.getCurrentPageShapesSorted()
|
|
5921
5918
|
|
|
5919
|
+
for (let i = shapesToCheck.length - 1; i >= 0; i--) {
|
|
5920
|
+
const shape = shapesToCheck[i]
|
|
5921
|
+
// Frame-like shapes have labels positioned above the shape (outside bounds), so always include them
|
|
5922
|
+
if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) continue
|
|
5922
5923
|
if (
|
|
5923
5924
|
(shape.isLocked && !hitLocked) ||
|
|
5924
5925
|
this.isShapeHidden(shape) ||
|
|
5925
5926
|
this.isShapeOfType(shape, 'group')
|
|
5926
|
-
)
|
|
5927
|
-
|
|
5927
|
+
) {
|
|
5928
|
+
continue
|
|
5929
|
+
}
|
|
5928
5930
|
const pageMask = this.getShapeMask(shape)
|
|
5929
|
-
if (pageMask && !pointInPolygon(point, pageMask))
|
|
5930
|
-
if (filter && !filter(shape))
|
|
5931
|
-
return true
|
|
5932
|
-
})
|
|
5931
|
+
if (pageMask && !pointInPolygon(point, pageMask)) continue
|
|
5932
|
+
if (filter && !filter(shape)) continue
|
|
5933
5933
|
|
|
5934
|
-
for (let i = shapesToCheck.length - 1; i >= 0; i--) {
|
|
5935
|
-
const shape = shapesToCheck[i]
|
|
5936
5934
|
const geometry = this.getShapeGeometry(shape)
|
|
5937
5935
|
const isGroup = geometry instanceof Group2d
|
|
5938
5936
|
|
|
@@ -6114,15 +6112,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6114
6112
|
const margin = opts.margin ?? 0
|
|
6115
6113
|
const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, margin)
|
|
6116
6114
|
|
|
6117
|
-
// Get all page shapes in z-index order and filter to candidates that pass isPointInShape
|
|
6118
|
-
// Frame-like shapes are always checked because their labels can be outside their bounds
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
.
|
|
6115
|
+
// Get all page shapes in z-index order and filter to candidates that pass isPointInShape.
|
|
6116
|
+
// Frame-like shapes are always checked because their labels can be outside their bounds.
|
|
6117
|
+
// Iterate backwards so the result is pre-sorted in reverse z-index order (top-most first).
|
|
6118
|
+
const sorted = this.getCurrentPageShapesSorted()
|
|
6119
|
+
const result: TLShape[] = []
|
|
6120
|
+
for (let i = sorted.length - 1; i >= 0; i--) {
|
|
6121
|
+
const shape = sorted[i]
|
|
6122
|
+
if (this.isShapeHidden(shape)) continue
|
|
6123
|
+
if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) continue
|
|
6124
|
+
if (this.isPointInShape(shape, point, opts)) result.push(shape)
|
|
6125
|
+
}
|
|
6126
|
+
return result
|
|
6126
6127
|
}
|
|
6127
6128
|
|
|
6128
6129
|
/**
|
|
@@ -39,9 +39,13 @@ export function deriveShapeIdsInCurrentPage(store: TLStore, getCurrentPageId: ()
|
|
|
39
39
|
function fromScratch() {
|
|
40
40
|
const currentPageId = getCurrentPageId()
|
|
41
41
|
lastPageId = currentPageId
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const result = new Set<TLShapeId>()
|
|
43
|
+
for (const id of shapesIndex.get()) {
|
|
44
|
+
if (isShapeInPage(store, currentPageId, store.get(id)!)) {
|
|
45
|
+
result.add(id)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result
|
|
45
49
|
}
|
|
46
50
|
return computed<Set<TLShapeId>>('_shapeIdsInCurrentPage', (prevValue, lastComputedEpoch) => {
|
|
47
51
|
if (isUninitialized(prevValue)) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EMPTY_ARRAY } from '@tldraw/state'
|
|
2
1
|
import { assert, invLerp, lerp } from '@tldraw/utils'
|
|
3
2
|
import { Box } from '../Box'
|
|
4
3
|
import { Mat } from '../Mat'
|
|
@@ -36,9 +35,15 @@ export class Group2d extends Geometry2d {
|
|
|
36
35
|
|
|
37
36
|
override getVertices(filters: Geometry2dFilters): Vec[] {
|
|
38
37
|
if (this.isExcludedByFilter(filters)) return []
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
const vertices: Vec[] = []
|
|
39
|
+
for (const child of this.children) {
|
|
40
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
41
|
+
const childVertices = child.getVertices(filters)
|
|
42
|
+
for (let i = 0, n = childVertices.length; i < n; i++) {
|
|
43
|
+
vertices.push(childVertices[i])
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return vertices
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
override nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {
|
|
@@ -84,9 +89,11 @@ export class Group2d extends Geometry2d {
|
|
|
84
89
|
hitInside: boolean,
|
|
85
90
|
filters = Geometry2dFilters.EXCLUDE_LABELS
|
|
86
91
|
): boolean {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
for (const child of this.children) {
|
|
93
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
94
|
+
if (child.hitTestPoint(point, margin, hitInside)) return true
|
|
95
|
+
}
|
|
96
|
+
return false
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
override hitTestLineSegment(
|
|
@@ -95,42 +102,71 @@ export class Group2d extends Geometry2d {
|
|
|
95
102
|
zoom: number,
|
|
96
103
|
filters = Geometry2dFilters.EXCLUDE_LABELS
|
|
97
104
|
): boolean {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
for (const child of this.children) {
|
|
106
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
107
|
+
if (child.hitTestLineSegment(A, B, zoom)) return true
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
override intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
const result: VecLike[] = []
|
|
114
|
+
for (const child of this.children) {
|
|
115
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
116
|
+
const hits = child.intersectLineSegment(A, B, filters)
|
|
117
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
118
|
+
result.push(hits[i])
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return result
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
override intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
const result: VecLike[] = []
|
|
126
|
+
for (const child of this.children) {
|
|
127
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
128
|
+
const hits = child.intersectCircle(center, radius, filters)
|
|
129
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
130
|
+
result.push(hits[i])
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return result
|
|
115
134
|
}
|
|
116
135
|
|
|
117
136
|
override getBoundsVertices(): Vec[] {
|
|
118
137
|
if (this.excludeFromShapeBounds) return []
|
|
119
|
-
|
|
138
|
+
const vertices: Vec[] = []
|
|
139
|
+
for (const child of this.children) {
|
|
140
|
+
const childVertices = child.getBoundsVertices()
|
|
141
|
+
for (let i = 0, n = childVertices.length; i < n; i++) {
|
|
142
|
+
vertices.push(childVertices[i])
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return vertices
|
|
120
146
|
}
|
|
121
147
|
|
|
122
148
|
override intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
149
|
+
const result: VecLike[] = []
|
|
150
|
+
for (const child of this.children) {
|
|
151
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
152
|
+
const hits = child.intersectPolygon(polygon, filters)
|
|
153
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
154
|
+
result.push(hits[i])
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result
|
|
127
158
|
}
|
|
128
159
|
|
|
129
160
|
override intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
161
|
+
const result: VecLike[] = []
|
|
162
|
+
for (const child of this.children) {
|
|
163
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
164
|
+
const hits = child.intersectPolyline(polyline, filters)
|
|
165
|
+
for (let i = 0, n = hits.length; i < n; i++) {
|
|
166
|
+
result.push(hits[i])
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return result
|
|
134
170
|
}
|
|
135
171
|
|
|
136
172
|
override interpolateAlongEdge(t: number, filters?: Geometry2dFilters): Vec {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { normalizeWheel } from './normalizeWheel'
|
|
2
|
+
|
|
3
|
+
function wheelEvent(props: Partial<WheelEvent>): WheelEvent {
|
|
4
|
+
return {
|
|
5
|
+
deltaX: 0,
|
|
6
|
+
deltaY: 0,
|
|
7
|
+
ctrlKey: false,
|
|
8
|
+
altKey: false,
|
|
9
|
+
metaKey: false,
|
|
10
|
+
shiftKey: false,
|
|
11
|
+
...props,
|
|
12
|
+
} as WheelEvent
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('normalizeWheel', () => {
|
|
16
|
+
it('negates finite deltas for panning', () => {
|
|
17
|
+
const { x, y } = normalizeWheel(wheelEvent({ deltaX: 5, deltaY: 10 }))
|
|
18
|
+
expect(x).toBe(-5)
|
|
19
|
+
expect(y).toBe(-10)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('coerces a non-finite deltaX to zero movement', () => {
|
|
23
|
+
const { x, y } = normalizeWheel(wheelEvent({ deltaX: NaN, deltaY: 10 }))
|
|
24
|
+
expect(x === 0).toBe(true)
|
|
25
|
+
expect(y).toBe(-10)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('coerces a non-finite deltaY to zero movement', () => {
|
|
29
|
+
const { x, y } = normalizeWheel(wheelEvent({ deltaX: 5, deltaY: Infinity }))
|
|
30
|
+
expect(x).toBe(-5)
|
|
31
|
+
expect(y === 0).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('does not produce a non-finite zoom delta when wheeling with a bad deltaY', () => {
|
|
35
|
+
const { z } = normalizeWheel(wheelEvent({ deltaY: NaN, ctrlKey: true }))
|
|
36
|
+
expect(Number.isFinite(z)).toBe(true)
|
|
37
|
+
expect(z === 0).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -9,6 +9,12 @@ const MAX_ZOOM_STEP = 10
|
|
|
9
9
|
* @internal */
|
|
10
10
|
export function normalizeWheel(event: WheelEvent | React.WheelEvent<HTMLElement>) {
|
|
11
11
|
let { deltaY, deltaX } = event
|
|
12
|
+
|
|
13
|
+
// Coerce non-finite deltas to no movement so they can't propagate into the camera and
|
|
14
|
+
// crash validation. Also covers deltaZ below, which is derived from deltaY.
|
|
15
|
+
if (!Number.isFinite(deltaX)) deltaX = 0
|
|
16
|
+
if (!Number.isFinite(deltaY)) deltaY = 0
|
|
17
|
+
|
|
12
18
|
let deltaZ = 0
|
|
13
19
|
|
|
14
20
|
// wheeling
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '5.4.0-next.
|
|
4
|
+
export const version = '5.4.0-next.ebc95cf01337'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2026-05-06T16:28:18.473Z',
|
|
7
|
-
minor: '2026-08-
|
|
8
|
-
patch: '2026-08-
|
|
7
|
+
minor: '2026-08-13T15:10:11.500Z',
|
|
8
|
+
patch: '2026-08-13T15:10:11.500Z',
|
|
9
9
|
}
|