@tldraw/editor 3.12.0-canary.abefd067ae60 → 3.12.0-canary.af1163458558
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 +157 -17
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +4 -0
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/GeometryDebuggingView.js +2 -2
- package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +10 -14
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +224 -32
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +1 -1
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager.js +1 -1
- package/dist-cjs/lib/editor/managers/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +9 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +4 -13
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +4 -1
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/selection-types.js.map +1 -1
- package/dist-cjs/lib/exports/StyleEmbedder.js +19 -5
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/cssRules.js +127 -0
- package/dist-cjs/lib/exports/cssRules.js.map +7 -0
- package/dist-cjs/lib/exports/parseCss.js +0 -69
- package/dist-cjs/lib/exports/parseCss.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +12 -7
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +3 -3
- package/dist-cjs/lib/hooks/useDocumentEvents.js +16 -0
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useGestureEvents.js +12 -6
- package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +2 -0
- package/dist-cjs/lib/license/Watermark.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +133 -16
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +3 -3
- package/dist-cjs/lib/primitives/geometry/Group2d.js +53 -10
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/intersect.js +20 -0
- package/dist-cjs/lib/primitives/intersect.js.map +2 -2
- package/dist-cjs/lib/utils/debug-flags.js +2 -1
- package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
- package/dist-cjs/lib/utils/reorderShapes.js +2 -8
- package/dist-cjs/lib/utils/reorderShapes.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 +157 -17
- package/dist-esm/index.mjs +8 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +4 -0
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/GeometryDebuggingView.mjs +3 -3
- package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +11 -15
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +225 -32
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +1 -1
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager.mjs +1 -1
- package/dist-esm/lib/editor/managers/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +9 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +4 -13
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +4 -1
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs +21 -12
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/cssRules.mjs +107 -0
- package/dist-esm/lib/exports/cssRules.mjs.map +7 -0
- package/dist-esm/lib/exports/parseCss.mjs +0 -69
- package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +12 -7
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +3 -3
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +16 -0
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs +12 -6
- package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +2 -0
- package/dist-esm/lib/license/Watermark.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +137 -14
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +54 -11
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/intersect.mjs +20 -0
- package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
- package/dist-esm/lib/utils/debug-flags.mjs +2 -1
- package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
- package/dist-esm/lib/utils/reorderShapes.mjs +2 -8
- package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +17 -11
- package/package.json +7 -7
- package/src/index.ts +11 -2
- package/src/lib/TldrawEditor.tsx +29 -3
- package/src/lib/components/GeometryDebuggingView.tsx +3 -3
- package/src/lib/components/Shape.tsx +15 -19
- package/src/lib/editor/Editor.ts +334 -39
- package/src/lib/editor/derivations/notVisibleShapes.ts +1 -1
- package/src/lib/editor/managers/FontManager.ts +1 -1
- package/src/lib/editor/shapes/ShapeUtil.ts +10 -0
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +7 -15
- package/src/lib/editor/tools/StateNode.ts +6 -1
- package/src/lib/editor/types/selection-types.ts +3 -0
- package/src/lib/exports/StyleEmbedder.ts +25 -15
- package/src/lib/exports/cssRules.ts +126 -0
- package/src/lib/exports/parseCss.ts +0 -79
- package/src/lib/hooks/useCanvasEvents.ts +14 -7
- package/src/lib/hooks/useDocumentEvents.ts +18 -0
- package/src/lib/hooks/useGestureEvents.ts +12 -6
- package/src/lib/license/Watermark.tsx +2 -0
- package/src/lib/primitives/geometry/Geometry2d.ts +196 -16
- package/src/lib/primitives/geometry/Group2d.ts +75 -12
- package/src/lib/primitives/intersect.ts +41 -0
- package/src/lib/utils/debug-flags.ts +1 -0
- package/src/lib/utils/reorderShapes.ts +2 -9
- package/src/version.ts +3 -3
|
@@ -1,12 +1,44 @@
|
|
|
1
|
+
import { assert } from '@tldraw/utils'
|
|
1
2
|
import { Box } from '../Box'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { Mat, MatModel } from '../Mat'
|
|
4
|
+
import { Vec, VecLike } from '../Vec'
|
|
5
|
+
import {
|
|
6
|
+
intersectCirclePolygon,
|
|
7
|
+
intersectCirclePolyline,
|
|
8
|
+
intersectLineSegmentPolygon,
|
|
9
|
+
intersectLineSegmentPolyline,
|
|
10
|
+
intersectPolys,
|
|
11
|
+
} from '../intersect'
|
|
12
|
+
import { approximately, pointInPolygon } from '../utils'
|
|
13
|
+
|
|
14
|
+
/** @public */
|
|
15
|
+
export interface Geometry2dFilters {
|
|
16
|
+
readonly includeLabels?: boolean
|
|
17
|
+
readonly includeInternal?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @public */
|
|
21
|
+
export const Geometry2dFilters: {
|
|
22
|
+
EXCLUDE_NON_STANDARD: Geometry2dFilters
|
|
23
|
+
INCLUDE_ALL: Geometry2dFilters
|
|
24
|
+
EXCLUDE_LABELS: Geometry2dFilters
|
|
25
|
+
EXCLUDE_INTERNAL: Geometry2dFilters
|
|
26
|
+
} = {
|
|
27
|
+
EXCLUDE_NON_STANDARD: {
|
|
28
|
+
includeLabels: false,
|
|
29
|
+
includeInternal: false,
|
|
30
|
+
},
|
|
31
|
+
INCLUDE_ALL: { includeLabels: true, includeInternal: true },
|
|
32
|
+
EXCLUDE_LABELS: { includeLabels: false, includeInternal: true },
|
|
33
|
+
EXCLUDE_INTERNAL: { includeLabels: true, includeInternal: false },
|
|
34
|
+
}
|
|
4
35
|
|
|
5
36
|
/** @public */
|
|
6
37
|
export interface Geometry2dOptions {
|
|
7
38
|
isFilled: boolean
|
|
8
39
|
isClosed: boolean
|
|
9
40
|
isLabel?: boolean
|
|
41
|
+
isInternal?: boolean
|
|
10
42
|
debugColor?: string
|
|
11
43
|
ignore?: boolean
|
|
12
44
|
}
|
|
@@ -16,6 +48,7 @@ export abstract class Geometry2d {
|
|
|
16
48
|
isFilled = false
|
|
17
49
|
isClosed = true
|
|
18
50
|
isLabel = false
|
|
51
|
+
isInternal = false
|
|
19
52
|
debugColor?: string
|
|
20
53
|
ignore?: boolean
|
|
21
54
|
|
|
@@ -23,20 +56,24 @@ export abstract class Geometry2d {
|
|
|
23
56
|
this.isFilled = opts.isFilled
|
|
24
57
|
this.isClosed = opts.isClosed
|
|
25
58
|
this.isLabel = opts.isLabel ?? false
|
|
59
|
+
this.isInternal = opts.isInternal ?? false
|
|
26
60
|
this.debugColor = opts.debugColor
|
|
27
61
|
this.ignore = opts.ignore
|
|
28
62
|
}
|
|
29
63
|
|
|
30
|
-
|
|
64
|
+
isExcludedByFilter(filters?: Geometry2dFilters) {
|
|
65
|
+
if (!filters) return false
|
|
66
|
+
if (this.isLabel && !filters.includeLabels) return true
|
|
67
|
+
if (this.isInternal && !filters.includeInternal) return true
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
31
70
|
|
|
32
|
-
abstract
|
|
71
|
+
abstract getVertices(filters: Geometry2dFilters): Vec[]
|
|
33
72
|
|
|
34
|
-
|
|
35
|
-
// // We've removed the broad phase here; that should be done outside of the call
|
|
36
|
-
// return this.distanceToPoint(point, hitInside) <= margin
|
|
37
|
-
// }
|
|
73
|
+
abstract nearestPoint(point: Vec, filters?: Geometry2dFilters): Vec
|
|
38
74
|
|
|
39
|
-
hitTestPoint(point: Vec, margin = 0, hitInside = false) {
|
|
75
|
+
hitTestPoint(point: Vec, margin = 0, hitInside = false, filters?: Geometry2dFilters) {
|
|
76
|
+
if (this.isExcludedByFilter(filters)) return false
|
|
40
77
|
// First check whether the point is inside
|
|
41
78
|
if (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)) {
|
|
42
79
|
return true
|
|
@@ -45,17 +82,17 @@ export abstract class Geometry2d {
|
|
|
45
82
|
return Vec.Dist2(point, this.nearestPoint(point)) <= margin * margin
|
|
46
83
|
}
|
|
47
84
|
|
|
48
|
-
distanceToPoint(point: Vec, hitInside = false) {
|
|
85
|
+
distanceToPoint(point: Vec, hitInside = false, filters?: Geometry2dFilters) {
|
|
49
86
|
return (
|
|
50
|
-
point.dist(this.nearestPoint(point)) *
|
|
87
|
+
point.dist(this.nearestPoint(point, filters)) *
|
|
51
88
|
(this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)
|
|
52
89
|
? -1
|
|
53
90
|
: 1)
|
|
54
91
|
)
|
|
55
92
|
}
|
|
56
93
|
|
|
57
|
-
distanceToLineSegment(A: Vec, B: Vec) {
|
|
58
|
-
if (A.equals(B)) return this.distanceToPoint(A)
|
|
94
|
+
distanceToLineSegment(A: Vec, B: Vec, filters?: Geometry2dFilters) {
|
|
95
|
+
if (A.equals(B)) return this.distanceToPoint(A, false, filters)
|
|
59
96
|
const { vertices } = this
|
|
60
97
|
let nearest: Vec | undefined
|
|
61
98
|
let dist = Infinity
|
|
@@ -73,10 +110,41 @@ export abstract class Geometry2d {
|
|
|
73
110
|
return this.isClosed && this.isFilled && pointInPolygon(nearest, this.vertices) ? -dist : dist
|
|
74
111
|
}
|
|
75
112
|
|
|
76
|
-
hitTestLineSegment(A: Vec, B: Vec, distance = 0): boolean {
|
|
77
|
-
return this.distanceToLineSegment(A, B) <= distance
|
|
113
|
+
hitTestLineSegment(A: Vec, B: Vec, distance = 0, filters?: Geometry2dFilters): boolean {
|
|
114
|
+
return this.distanceToLineSegment(A, B, filters) <= distance
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters): VecLike[] {
|
|
118
|
+
if (this.isExcludedByFilter(filters)) return []
|
|
119
|
+
|
|
120
|
+
const intersections = this.isClosed
|
|
121
|
+
? intersectLineSegmentPolygon(A, B, this.vertices)
|
|
122
|
+
: intersectLineSegmentPolyline(A, B, this.vertices)
|
|
123
|
+
|
|
124
|
+
return intersections ?? []
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters): VecLike[] {
|
|
128
|
+
if (this.isExcludedByFilter(filters)) return []
|
|
129
|
+
const intersections = this.isClosed
|
|
130
|
+
? intersectCirclePolygon(center, radius, this.vertices)
|
|
131
|
+
: intersectCirclePolyline(center, radius, this.vertices)
|
|
132
|
+
|
|
133
|
+
return intersections ?? []
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
137
|
+
if (this.isExcludedByFilter(filters)) return []
|
|
138
|
+
|
|
139
|
+
return intersectPolys(polygon, this.vertices, true, this.isClosed)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
143
|
+
if (this.isExcludedByFilter(filters)) return []
|
|
144
|
+
return intersectPolys(polyline, this.vertices, false, this.isClosed)
|
|
78
145
|
}
|
|
79
146
|
|
|
147
|
+
/** @deprecated Iterate the vertices instead. */
|
|
80
148
|
nearestPointOnLineSegment(A: Vec, B: Vec): Vec {
|
|
81
149
|
const { vertices } = this
|
|
82
150
|
let nearest: Vec | undefined
|
|
@@ -105,12 +173,16 @@ export abstract class Geometry2d {
|
|
|
105
173
|
)
|
|
106
174
|
}
|
|
107
175
|
|
|
176
|
+
transform(transform: MatModel): Geometry2d {
|
|
177
|
+
return new TransformedGeometry2d(this, transform)
|
|
178
|
+
}
|
|
179
|
+
|
|
108
180
|
private _vertices: Vec[] | undefined
|
|
109
181
|
|
|
110
182
|
// eslint-disable-next-line no-restricted-syntax
|
|
111
183
|
get vertices(): Vec[] {
|
|
112
184
|
if (!this._vertices) {
|
|
113
|
-
this._vertices = this.getVertices()
|
|
185
|
+
this._vertices = this.getVertices(Geometry2dFilters.EXCLUDE_LABELS)
|
|
114
186
|
}
|
|
115
187
|
|
|
116
188
|
return this._vertices
|
|
@@ -204,3 +276,111 @@ export abstract class Geometry2d {
|
|
|
204
276
|
|
|
205
277
|
abstract getSvgPathData(first: boolean): string
|
|
206
278
|
}
|
|
279
|
+
|
|
280
|
+
// =================================================================================================
|
|
281
|
+
// Because Geometry2d.transform depends on TransformedGeometry2d, we need to define it here instead
|
|
282
|
+
// of in its own files. This prevents a circular import error.
|
|
283
|
+
// =================================================================================================
|
|
284
|
+
|
|
285
|
+
/** @public */
|
|
286
|
+
export class TransformedGeometry2d extends Geometry2d {
|
|
287
|
+
private readonly inverse: MatModel
|
|
288
|
+
private readonly decomposed
|
|
289
|
+
|
|
290
|
+
constructor(
|
|
291
|
+
private readonly geometry: Geometry2d,
|
|
292
|
+
private readonly matrix: MatModel
|
|
293
|
+
) {
|
|
294
|
+
super(geometry)
|
|
295
|
+
this.inverse = Mat.Inverse(matrix)
|
|
296
|
+
this.decomposed = Mat.Decompose(matrix)
|
|
297
|
+
|
|
298
|
+
assert(
|
|
299
|
+
approximately(this.decomposed.scaleX, this.decomposed.scaleY),
|
|
300
|
+
'non-uniform scaling is not yet supported'
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
getVertices(filters: Geometry2dFilters): Vec[] {
|
|
305
|
+
return this.geometry.getVertices(filters).map((v) => Mat.applyToPoint(this.matrix, v))
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
nearestPoint(point: Vec, filters?: Geometry2dFilters): Vec {
|
|
309
|
+
return Mat.applyToPoint(
|
|
310
|
+
this.matrix,
|
|
311
|
+
this.geometry.nearestPoint(Mat.applyToPoint(this.inverse, point), filters)
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
override hitTestPoint(
|
|
316
|
+
point: Vec,
|
|
317
|
+
margin = 0,
|
|
318
|
+
hitInside?: boolean,
|
|
319
|
+
filters?: Geometry2dFilters
|
|
320
|
+
): boolean {
|
|
321
|
+
return this.geometry.hitTestPoint(
|
|
322
|
+
Mat.applyToPoint(this.inverse, point),
|
|
323
|
+
margin / this.decomposed.scaleX,
|
|
324
|
+
hitInside,
|
|
325
|
+
filters
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
override distanceToPoint(point: Vec, hitInside = false, filters?: Geometry2dFilters) {
|
|
330
|
+
return (
|
|
331
|
+
this.geometry.distanceToPoint(Mat.applyToPoint(this.inverse, point), hitInside, filters) *
|
|
332
|
+
this.decomposed.scaleX
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
override distanceToLineSegment(A: Vec, B: Vec, filters?: Geometry2dFilters) {
|
|
337
|
+
return (
|
|
338
|
+
this.geometry.distanceToLineSegment(
|
|
339
|
+
Mat.applyToPoint(this.inverse, A),
|
|
340
|
+
Mat.applyToPoint(this.inverse, B),
|
|
341
|
+
filters
|
|
342
|
+
) * this.decomposed.scaleX
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
override hitTestLineSegment(A: Vec, B: Vec, distance = 0, filters?: Geometry2dFilters): boolean {
|
|
347
|
+
return this.geometry.hitTestLineSegment(
|
|
348
|
+
Mat.applyToPoint(this.inverse, A),
|
|
349
|
+
Mat.applyToPoint(this.inverse, B),
|
|
350
|
+
distance / this.decomposed.scaleX,
|
|
351
|
+
filters
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
override intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
|
|
356
|
+
return this.geometry.intersectLineSegment(
|
|
357
|
+
Mat.applyToPoint(this.inverse, A),
|
|
358
|
+
Mat.applyToPoint(this.inverse, B),
|
|
359
|
+
filters
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
override intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {
|
|
364
|
+
return this.geometry.intersectCircle(
|
|
365
|
+
Mat.applyToPoint(this.inverse, center),
|
|
366
|
+
radius / this.decomposed.scaleX,
|
|
367
|
+
filters
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
override intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
372
|
+
return this.geometry.intersectPolygon(Mat.applyToPoints(this.inverse, polygon), filters)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
override intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
376
|
+
return this.geometry.intersectPolyline(Mat.applyToPoints(this.inverse, polyline), filters)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
override transform(transform: MatModel): Geometry2d {
|
|
380
|
+
return new TransformedGeometry2d(this.geometry, Mat.Multiply(transform, this.matrix))
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
getSvgPathData(): string {
|
|
384
|
+
throw new Error('Cannot get SVG path data for transformed geometry.')
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { EMPTY_ARRAY } from '@tldraw/state'
|
|
1
2
|
import { Box } from '../Box'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
3
|
+
import { Mat } from '../Mat'
|
|
4
|
+
import { Vec, VecLike } from '../Vec'
|
|
5
|
+
import { Geometry2d, Geometry2dFilters, Geometry2dOptions } from './Geometry2d'
|
|
4
6
|
|
|
5
7
|
/** @public */
|
|
6
8
|
export class Group2d extends Geometry2d {
|
|
@@ -25,11 +27,14 @@ export class Group2d extends Geometry2d {
|
|
|
25
27
|
if (this.children.length === 0) throw Error('Group2d must have at least one child')
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
override getVertices(): Vec[] {
|
|
29
|
-
|
|
30
|
+
override getVertices(filters: Geometry2dFilters): Vec[] {
|
|
31
|
+
if (this.isExcludedByFilter(filters)) return []
|
|
32
|
+
return this.children
|
|
33
|
+
.filter((c) => !c.isExcludedByFilter(filters))
|
|
34
|
+
.flatMap((c) => c.getVertices(filters))
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
override nearestPoint(point: Vec): Vec {
|
|
37
|
+
override nearestPoint(point: Vec, filters?: Geometry2dFilters): Vec {
|
|
33
38
|
let dist = Infinity
|
|
34
39
|
let nearest: Vec | undefined
|
|
35
40
|
|
|
@@ -42,7 +47,8 @@ export class Group2d extends Geometry2d {
|
|
|
42
47
|
let p: Vec
|
|
43
48
|
let d: number
|
|
44
49
|
for (const child of children) {
|
|
45
|
-
|
|
50
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
51
|
+
p = child.nearestPoint(point, filters)
|
|
46
52
|
d = Vec.Dist2(p, point)
|
|
47
53
|
if (d < dist) {
|
|
48
54
|
dist = d
|
|
@@ -53,18 +59,75 @@ export class Group2d extends Geometry2d {
|
|
|
53
59
|
return nearest
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
override distanceToPoint(point: Vec, hitInside = false) {
|
|
57
|
-
|
|
62
|
+
override distanceToPoint(point: Vec, hitInside = false, filters?: Geometry2dFilters) {
|
|
63
|
+
let smallestDistance = Infinity
|
|
64
|
+
for (const child of this.children) {
|
|
65
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
66
|
+
const distance = child.distanceToPoint(point, hitInside, filters)
|
|
67
|
+
if (distance < smallestDistance) {
|
|
68
|
+
smallestDistance = distance
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return smallestDistance
|
|
58
72
|
}
|
|
59
73
|
|
|
60
|
-
override hitTestPoint(
|
|
74
|
+
override hitTestPoint(
|
|
75
|
+
point: Vec,
|
|
76
|
+
margin: number,
|
|
77
|
+
hitInside: boolean,
|
|
78
|
+
filters = Geometry2dFilters.EXCLUDE_LABELS
|
|
79
|
+
): boolean {
|
|
61
80
|
return !!this.children
|
|
62
|
-
.filter((c) => !c.
|
|
81
|
+
.filter((c) => !c.isExcludedByFilter(filters))
|
|
63
82
|
.find((c) => c.hitTestPoint(point, margin, hitInside))
|
|
64
83
|
}
|
|
65
84
|
|
|
66
|
-
override hitTestLineSegment(
|
|
67
|
-
|
|
85
|
+
override hitTestLineSegment(
|
|
86
|
+
A: Vec,
|
|
87
|
+
B: Vec,
|
|
88
|
+
zoom: number,
|
|
89
|
+
filters = Geometry2dFilters.EXCLUDE_LABELS
|
|
90
|
+
): boolean {
|
|
91
|
+
return !!this.children
|
|
92
|
+
.filter((c) => !c.isExcludedByFilter(filters))
|
|
93
|
+
.find((c) => c.hitTestLineSegment(A, B, zoom))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
|
|
97
|
+
return this.children.flatMap((child) => {
|
|
98
|
+
if (child.isExcludedByFilter(filters)) return EMPTY_ARRAY
|
|
99
|
+
return child.intersectLineSegment(A, B, filters)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {
|
|
104
|
+
return this.children.flatMap((child) => {
|
|
105
|
+
if (child.isExcludedByFilter(filters)) return EMPTY_ARRAY
|
|
106
|
+
return child.intersectCircle(center, radius, filters)
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters) {
|
|
111
|
+
return this.children.flatMap((child) => {
|
|
112
|
+
if (child.isExcludedByFilter(filters)) return EMPTY_ARRAY
|
|
113
|
+
return child.intersectPolygon(polygon, filters)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters) {
|
|
118
|
+
return this.children.flatMap((child) => {
|
|
119
|
+
if (child.isExcludedByFilter(filters)) return EMPTY_ARRAY
|
|
120
|
+
return child.intersectPolyline(polyline, filters)
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override transform(transform: Mat): Geometry2d {
|
|
125
|
+
return new Group2d({
|
|
126
|
+
children: this.children.map((c) => c.transform(transform)),
|
|
127
|
+
isLabel: this.isLabel,
|
|
128
|
+
debugColor: this.debugColor,
|
|
129
|
+
ignore: this.ignore,
|
|
130
|
+
})
|
|
68
131
|
}
|
|
69
132
|
|
|
70
133
|
getArea() {
|
|
@@ -293,6 +293,47 @@ export function intersectPolygonPolygon(
|
|
|
293
293
|
return orderClockwise([...result.values()])
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Find all the points where `polyA` and `polyB` intersect and returns them in an undefined order.
|
|
298
|
+
* To find the polygon that's the intersection of polyA and polyB, use `intersectPolygonPolygon`
|
|
299
|
+
* instead, which orders the points and includes internal points.
|
|
300
|
+
*
|
|
301
|
+
* @param polyA - The first polygon.
|
|
302
|
+
* @param polyB - The second polygon.
|
|
303
|
+
* @param isAClosed - Whether `polyA` is a closed polygon or a polyline.
|
|
304
|
+
* @param isBClosed - Whether `polyB` is a closed polygon or a polyline.
|
|
305
|
+
* @public
|
|
306
|
+
*/
|
|
307
|
+
export function intersectPolys(
|
|
308
|
+
polyA: VecLike[],
|
|
309
|
+
polyB: VecLike[],
|
|
310
|
+
isAClosed: boolean,
|
|
311
|
+
isBClosed: boolean
|
|
312
|
+
): VecLike[] {
|
|
313
|
+
const result: Map<string, VecLike> = new Map()
|
|
314
|
+
|
|
315
|
+
// Add all intersection points to result
|
|
316
|
+
for (let i = 0, n = isAClosed ? polyA.length : polyA.length - 1; i < n; i++) {
|
|
317
|
+
const currentA = polyA[i]
|
|
318
|
+
const nextA = polyA[(i + 1) % polyA.length]
|
|
319
|
+
|
|
320
|
+
for (let j = 0, m = isBClosed ? polyB.length : polyB.length - 1; j < m; j++) {
|
|
321
|
+
const currentB = polyB[j]
|
|
322
|
+
const nextB = polyB[(j + 1) % polyB.length]
|
|
323
|
+
const intersection = intersectLineSegmentLineSegment(currentA, nextA, currentB, nextB)
|
|
324
|
+
|
|
325
|
+
if (intersection !== null) {
|
|
326
|
+
const id = getPointId(intersection)
|
|
327
|
+
if (!result.has(id)) {
|
|
328
|
+
result.set(id, intersection)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return [...result.values()]
|
|
335
|
+
}
|
|
336
|
+
|
|
296
337
|
function getPointId(point: VecLike) {
|
|
297
338
|
return `${point.x},${point.y}`
|
|
298
339
|
}
|
|
@@ -53,6 +53,7 @@ export const debugFlags = {
|
|
|
53
53
|
debugGeometry: createDebugValue('debugGeometry', { defaults: { all: false } }),
|
|
54
54
|
hideShapes: createDebugValue('hideShapes', { defaults: { all: false } }),
|
|
55
55
|
editOnType: createDebugValue('editOnType', { defaults: { all: false } }),
|
|
56
|
+
a11y: createDebugValue('a11y', { defaults: { all: false } }),
|
|
56
57
|
} as const
|
|
57
58
|
|
|
58
59
|
declare global {
|
|
@@ -154,24 +154,17 @@ function reorderToFront(moving: Set<TLShape>, children: TLShape[], changes: TLSh
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
function getVerticesInPageSpace(editor: Editor, shape: TLShape) {
|
|
158
|
-
const geo = editor.getShapeGeometry(shape)
|
|
159
|
-
const pageTransform = editor.getShapePageTransform(shape)
|
|
160
|
-
if (!geo || !pageTransform) return null
|
|
161
|
-
return pageTransform.applyToPoints(geo.vertices)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
157
|
function getOverlapChecker(editor: Editor, moving: Set<TLShape>) {
|
|
165
158
|
const movingVertices = Array.from(moving)
|
|
166
159
|
.map((shape) => {
|
|
167
|
-
const vertices =
|
|
160
|
+
const vertices = editor.getShapePageGeometry(shape).vertices
|
|
168
161
|
if (!vertices) return null
|
|
169
162
|
return { shape, vertices }
|
|
170
163
|
})
|
|
171
164
|
.filter(Boolean) as { shape: TLShape; vertices: Vec[] }[]
|
|
172
165
|
|
|
173
166
|
const isOverlapping = (child: TLShape) => {
|
|
174
|
-
const vertices =
|
|
167
|
+
const vertices = editor.getShapePageGeometry(child).vertices
|
|
175
168
|
if (!vertices) return false
|
|
176
169
|
return movingVertices.some((other) => {
|
|
177
170
|
return polygonsIntersect(other.vertices, vertices)
|
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 = '3.12.0-canary.
|
|
4
|
+
export const version = '3.12.0-canary.af1163458558'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-04-07T13:25:06.554Z',
|
|
8
|
+
patch: '2025-04-07T13:25:06.554Z',
|
|
9
9
|
}
|