@tldraw/editor 3.13.0-canary.fd867adaa211 → 3.14.0-canary.95353287a2be
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/CHANGELOG.md +180 -0
- package/dist-cjs/index.d.ts +99 -99
- package/dist-cjs/index.js +7 -22
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +12 -8
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +31 -8
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +17 -11
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +85 -24
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager.js +10 -0
- package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +1 -1
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +0 -3
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js +12 -3
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/hooks/useEditorComponents.js +1 -2
- package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +16 -0
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Mat.js +1 -1
- package/dist-cjs/lib/primitives/Mat.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +20 -0
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +91 -20
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +55 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
- package/dist-cjs/lib/utils/areShapesContentEqual.js +25 -0
- package/dist-cjs/lib/utils/areShapesContentEqual.js.map +7 -0
- package/dist-cjs/lib/utils/debug-flags.js +5 -2
- package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
- package/dist-cjs/lib/utils/nearestMultiple.js +34 -0
- package/dist-cjs/lib/utils/nearestMultiple.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +99 -99
- package/dist-esm/index.mjs +9 -41
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +12 -8
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +31 -8
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +17 -11
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +85 -24
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager.mjs +10 -0
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +1 -1
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +0 -3
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +12 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEditorComponents.mjs +1 -4
- package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +16 -0
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Mat.mjs +1 -1
- package/dist-esm/lib/primitives/Mat.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +20 -0
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +92 -21
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +55 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
- package/dist-esm/lib/utils/areShapesContentEqual.mjs +5 -0
- package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +7 -0
- package/dist-esm/lib/utils/debug-flags.mjs +5 -2
- package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
- package/dist-esm/lib/utils/nearestMultiple.mjs +14 -0
- package/dist-esm/lib/utils/nearestMultiple.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +36 -4
- package/package.json +7 -7
- package/src/index.ts +16 -31
- package/src/lib/components/Shape.tsx +14 -10
- package/src/lib/components/default-components/DefaultCanvas.tsx +32 -8
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +17 -8
- package/src/lib/editor/Editor.test.ts +1 -1
- package/src/lib/editor/Editor.ts +96 -24
- package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +0 -1
- package/src/lib/editor/managers/TextManager.ts +12 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +10 -2
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +0 -4
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +9 -9
- package/src/lib/exports/getSvgJsx.tsx +16 -7
- package/src/lib/hooks/useEditorComponents.tsx +2 -5
- package/src/lib/primitives/Box.ts +20 -0
- package/src/lib/primitives/Mat.ts +5 -4
- package/src/lib/primitives/Vec.ts +23 -0
- package/src/lib/primitives/geometry/Arc2d.ts +5 -5
- package/src/lib/primitives/geometry/Circle2d.ts +4 -4
- package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -4
- package/src/lib/primitives/geometry/CubicSpline2d.ts +3 -3
- package/src/lib/primitives/geometry/Edge2d.ts +3 -3
- package/src/lib/primitives/geometry/Ellipse2d.ts +3 -3
- package/src/lib/primitives/geometry/Geometry2d.test.ts +42 -0
- package/src/lib/primitives/geometry/Geometry2d.ts +123 -35
- package/src/lib/primitives/geometry/Group2d.ts +70 -7
- package/src/lib/primitives/geometry/Point2d.ts +2 -2
- package/src/lib/primitives/geometry/Polyline2d.ts +3 -3
- package/src/lib/primitives/geometry/Stadium2d.ts +3 -3
- package/src/lib/test/currentToolIdMask.test.ts +1 -1
- package/src/lib/test/user.test.ts +1 -1
- package/src/lib/utils/areShapesContentEqual.ts +4 -0
- package/src/lib/utils/debug-flags.ts +7 -2
- package/src/lib/utils/nearestMultiple.ts +13 -0
- package/src/lib/utils/sync/LocalIndexedDb.test.ts +1 -1
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +1 -1
- package/src/version.ts +3 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Mat } from '../Mat'
|
|
2
|
+
import { Vec, VecLike } from '../Vec'
|
|
3
|
+
import { Geometry2dFilters } from './Geometry2d'
|
|
4
|
+
import { Rectangle2d } from './Rectangle2d'
|
|
5
|
+
|
|
6
|
+
describe('TransformedGeometry2d', () => {
|
|
7
|
+
const rect = new Rectangle2d({ width: 100, height: 50, isFilled: true }).transform(
|
|
8
|
+
Mat.Translate(50, 100).scale(2, 2)
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
test('getVertices', () => {
|
|
12
|
+
expect(rect.getVertices(Geometry2dFilters.INCLUDE_ALL)).toMatchObject([
|
|
13
|
+
{ x: 50, y: 100, z: 1 },
|
|
14
|
+
{ x: 250, y: 100, z: 1 },
|
|
15
|
+
{ x: 250, y: 200, z: 1 },
|
|
16
|
+
{ x: 50, y: 200, z: 1 },
|
|
17
|
+
])
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('nearestPoint', () => {
|
|
21
|
+
expectApproxMatch(rect.nearestPoint(new Vec(100, 300)), { x: 100, y: 200 })
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('hitTestPoint', () => {
|
|
25
|
+
// basic case - no margin / scaling:
|
|
26
|
+
expect(rect.hitTestPoint(new Vec(0, 0), 0, true)).toBe(false)
|
|
27
|
+
expect(rect.hitTestPoint(new Vec(50, 100), 0, true)).toBe(true)
|
|
28
|
+
expect(rect.hitTestPoint(new Vec(49, 100), 0, true)).toBe(false)
|
|
29
|
+
expect(rect.hitTestPoint(new Vec(100, 150), 0, true)).toBe(true)
|
|
30
|
+
|
|
31
|
+
// with margin:
|
|
32
|
+
// move away 8 px and test with 10px margin:
|
|
33
|
+
expect(rect.hitTestPoint(new Vec(42, 100), 10, true)).toBe(true)
|
|
34
|
+
// move away 12 px and test with 10px margin:
|
|
35
|
+
expect(rect.hitTestPoint(new Vec(38, 100), 10, true)).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
function expectApproxMatch(a: VecLike, b: VecLike) {
|
|
40
|
+
expect(a.x).toBeCloseTo(b.x, 0.0001)
|
|
41
|
+
expect(a.y).toBeCloseTo(b.y, 0.0001)
|
|
42
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assert } from '@tldraw/utils'
|
|
1
|
+
import { assert, invLerp } from '@tldraw/utils'
|
|
2
2
|
import { Box } from '../Box'
|
|
3
3
|
import { Mat, MatModel } from '../Mat'
|
|
4
4
|
import { Vec, VecLike } from '../Vec'
|
|
@@ -81,9 +81,9 @@ export abstract class Geometry2d {
|
|
|
81
81
|
|
|
82
82
|
abstract getVertices(filters: Geometry2dFilters): Vec[]
|
|
83
83
|
|
|
84
|
-
abstract nearestPoint(point:
|
|
84
|
+
abstract nearestPoint(point: VecLike, _filters?: Geometry2dFilters): Vec
|
|
85
85
|
|
|
86
|
-
hitTestPoint(point:
|
|
86
|
+
hitTestPoint(point: VecLike, margin = 0, hitInside = false, _filters?: Geometry2dFilters) {
|
|
87
87
|
// First check whether the point is inside
|
|
88
88
|
if (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)) {
|
|
89
89
|
return true
|
|
@@ -92,17 +92,17 @@ export abstract class Geometry2d {
|
|
|
92
92
|
return Vec.Dist2(point, this.nearestPoint(point)) <= margin * margin
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
distanceToPoint(point:
|
|
95
|
+
distanceToPoint(point: VecLike, hitInside = false, filters?: Geometry2dFilters) {
|
|
96
96
|
return (
|
|
97
|
-
|
|
97
|
+
Vec.Dist(point, this.nearestPoint(point, filters)) *
|
|
98
98
|
(this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)
|
|
99
99
|
? -1
|
|
100
100
|
: 1)
|
|
101
101
|
)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
distanceToLineSegment(A:
|
|
105
|
-
if (
|
|
104
|
+
distanceToLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
|
|
105
|
+
if (Vec.Equals(A, B)) return this.distanceToPoint(A, false, filters)
|
|
106
106
|
const { vertices } = this
|
|
107
107
|
let nearest: Vec | undefined
|
|
108
108
|
let dist = Infinity
|
|
@@ -120,7 +120,7 @@ export abstract class Geometry2d {
|
|
|
120
120
|
return this.isClosed && this.isFilled && pointInPolygon(nearest, this.vertices) ? -dist : dist
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
hitTestLineSegment(A:
|
|
123
|
+
hitTestLineSegment(A: VecLike, B: VecLike, distance = 0, filters?: Geometry2dFilters): boolean {
|
|
124
124
|
return this.distanceToLineSegment(A, B, filters) <= distance
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -148,8 +148,76 @@ export abstract class Geometry2d {
|
|
|
148
148
|
return intersectPolys(polyline, this.vertices, false, this.isClosed)
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Find a point along the edge of the geometry that is a fraction `t` along the entire way round.
|
|
153
|
+
*/
|
|
154
|
+
interpolateAlongEdge(t: number, _filters?: Geometry2dFilters): Vec {
|
|
155
|
+
const { vertices } = this
|
|
156
|
+
|
|
157
|
+
if (t <= 0) return vertices[0]
|
|
158
|
+
|
|
159
|
+
const distanceToTravel = t * this.length
|
|
160
|
+
let distanceTraveled = 0
|
|
161
|
+
|
|
162
|
+
for (let i = 0; i < (this.isClosed ? vertices.length : vertices.length - 1); i++) {
|
|
163
|
+
const curr = vertices[i]
|
|
164
|
+
const next = vertices[(i + 1) % vertices.length]
|
|
165
|
+
const dist = Vec.Dist(curr, next)
|
|
166
|
+
const newDistanceTraveled = distanceTraveled + dist
|
|
167
|
+
if (newDistanceTraveled >= distanceToTravel) {
|
|
168
|
+
const p = Vec.Lrp(
|
|
169
|
+
curr,
|
|
170
|
+
next,
|
|
171
|
+
invLerp(distanceTraveled, newDistanceTraveled, distanceToTravel)
|
|
172
|
+
)
|
|
173
|
+
return p
|
|
174
|
+
}
|
|
175
|
+
distanceTraveled = newDistanceTraveled
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return this.isClosed ? vertices[0] : vertices[vertices.length - 1]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Take `point`, find the closest point to it on the edge of the geometry, and return how far
|
|
183
|
+
* along the edge it is as a fraction of the total length.
|
|
184
|
+
*/
|
|
185
|
+
uninterpolateAlongEdge(point: VecLike, _filters?: Geometry2dFilters): number {
|
|
186
|
+
const { vertices, length } = this
|
|
187
|
+
let closestSegment = null
|
|
188
|
+
let closestDistance = Infinity
|
|
189
|
+
let distanceTraveled = 0
|
|
190
|
+
|
|
191
|
+
for (let i = 0; i < (this.isClosed ? vertices.length : vertices.length - 1); i++) {
|
|
192
|
+
const curr = vertices[i]
|
|
193
|
+
const next = vertices[(i + 1) % vertices.length]
|
|
194
|
+
|
|
195
|
+
const nearestPoint = Vec.NearestPointOnLineSegment(curr, next, point, true)
|
|
196
|
+
const distance = Vec.Dist(nearestPoint, point)
|
|
197
|
+
|
|
198
|
+
if (distance < closestDistance) {
|
|
199
|
+
closestDistance = distance
|
|
200
|
+
closestSegment = {
|
|
201
|
+
start: curr,
|
|
202
|
+
end: next,
|
|
203
|
+
nearestPoint,
|
|
204
|
+
distanceToStart: distanceTraveled,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
distanceTraveled += Vec.Dist(curr, next)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
assert(closestSegment)
|
|
212
|
+
|
|
213
|
+
const distanceAlongRoute =
|
|
214
|
+
closestSegment.distanceToStart + Vec.Dist(closestSegment.start, closestSegment.nearestPoint)
|
|
215
|
+
|
|
216
|
+
return distanceAlongRoute / length
|
|
217
|
+
}
|
|
218
|
+
|
|
151
219
|
/** @deprecated Iterate the vertices instead. */
|
|
152
|
-
nearestPointOnLineSegment(A:
|
|
220
|
+
nearestPointOnLineSegment(A: VecLike, B: VecLike): Vec {
|
|
153
221
|
const { vertices } = this
|
|
154
222
|
let nearest: Vec | undefined
|
|
155
223
|
let dist = Infinity
|
|
@@ -167,7 +235,7 @@ export abstract class Geometry2d {
|
|
|
167
235
|
return nearest
|
|
168
236
|
}
|
|
169
237
|
|
|
170
|
-
isPointInBounds(point:
|
|
238
|
+
isPointInBounds(point: VecLike, margin = 0) {
|
|
171
239
|
const { bounds } = this
|
|
172
240
|
return !(
|
|
173
241
|
point.x < bounds.minX - margin ||
|
|
@@ -261,21 +329,24 @@ export abstract class Geometry2d {
|
|
|
261
329
|
// eslint-disable-next-line no-restricted-syntax
|
|
262
330
|
get length() {
|
|
263
331
|
if (this._length) return this._length
|
|
264
|
-
this._length = this.getLength()
|
|
332
|
+
this._length = this.getLength(Geometry2dFilters.EXCLUDE_LABELS)
|
|
265
333
|
return this._length
|
|
266
334
|
}
|
|
267
335
|
|
|
268
|
-
getLength() {
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
336
|
+
getLength(_filters?: Geometry2dFilters) {
|
|
337
|
+
const vertices = this.getVertices(_filters ?? Geometry2dFilters.EXCLUDE_LABELS)
|
|
338
|
+
if (vertices.length === 0) return 0
|
|
339
|
+
let prev = vertices[0]
|
|
340
|
+
let length = 0
|
|
273
341
|
for (let i = 1; i < vertices.length; i++) {
|
|
274
|
-
|
|
275
|
-
length += Vec.
|
|
276
|
-
|
|
342
|
+
const next = vertices[i]
|
|
343
|
+
length += Vec.Dist(prev, next)
|
|
344
|
+
prev = next
|
|
345
|
+
}
|
|
346
|
+
if (this.isClosed) {
|
|
347
|
+
length += Vec.Dist(vertices[vertices.length - 1], vertices[0])
|
|
277
348
|
}
|
|
278
|
-
return
|
|
349
|
+
return length
|
|
279
350
|
}
|
|
280
351
|
|
|
281
352
|
abstract getSvgPathData(first: boolean): string
|
|
@@ -317,7 +388,7 @@ export class TransformedGeometry2d extends Geometry2d {
|
|
|
317
388
|
return this.geometry.getVertices(filters).map((v) => Mat.applyToPoint(this.matrix, v))
|
|
318
389
|
}
|
|
319
390
|
|
|
320
|
-
nearestPoint(point:
|
|
391
|
+
nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {
|
|
321
392
|
return Mat.applyToPoint(
|
|
322
393
|
this.matrix,
|
|
323
394
|
this.geometry.nearestPoint(Mat.applyToPoint(this.inverse, point), filters)
|
|
@@ -325,7 +396,7 @@ export class TransformedGeometry2d extends Geometry2d {
|
|
|
325
396
|
}
|
|
326
397
|
|
|
327
398
|
override hitTestPoint(
|
|
328
|
-
point:
|
|
399
|
+
point: VecLike,
|
|
329
400
|
margin = 0,
|
|
330
401
|
hitInside?: boolean,
|
|
331
402
|
filters?: Geometry2dFilters
|
|
@@ -338,14 +409,14 @@ export class TransformedGeometry2d extends Geometry2d {
|
|
|
338
409
|
)
|
|
339
410
|
}
|
|
340
411
|
|
|
341
|
-
override distanceToPoint(point:
|
|
412
|
+
override distanceToPoint(point: VecLike, hitInside = false, filters?: Geometry2dFilters) {
|
|
342
413
|
return (
|
|
343
414
|
this.geometry.distanceToPoint(Mat.applyToPoint(this.inverse, point), hitInside, filters) *
|
|
344
415
|
this.decomposed.scaleX
|
|
345
416
|
)
|
|
346
417
|
}
|
|
347
418
|
|
|
348
|
-
override distanceToLineSegment(A:
|
|
419
|
+
override distanceToLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
|
|
349
420
|
return (
|
|
350
421
|
this.geometry.distanceToLineSegment(
|
|
351
422
|
Mat.applyToPoint(this.inverse, A),
|
|
@@ -355,7 +426,12 @@ export class TransformedGeometry2d extends Geometry2d {
|
|
|
355
426
|
)
|
|
356
427
|
}
|
|
357
428
|
|
|
358
|
-
override hitTestLineSegment(
|
|
429
|
+
override hitTestLineSegment(
|
|
430
|
+
A: VecLike,
|
|
431
|
+
B: VecLike,
|
|
432
|
+
distance = 0,
|
|
433
|
+
filters?: Geometry2dFilters
|
|
434
|
+
): boolean {
|
|
359
435
|
return this.geometry.hitTestLineSegment(
|
|
360
436
|
Mat.applyToPoint(this.inverse, A),
|
|
361
437
|
Mat.applyToPoint(this.inverse, B),
|
|
@@ -365,27 +441,39 @@ export class TransformedGeometry2d extends Geometry2d {
|
|
|
365
441
|
}
|
|
366
442
|
|
|
367
443
|
override intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
|
|
368
|
-
return
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
444
|
+
return Mat.applyToPoints(
|
|
445
|
+
this.matrix,
|
|
446
|
+
this.geometry.intersectLineSegment(
|
|
447
|
+
Mat.applyToPoint(this.inverse, A),
|
|
448
|
+
Mat.applyToPoint(this.inverse, B),
|
|
449
|
+
filters
|
|
450
|
+
)
|
|
372
451
|
)
|
|
373
452
|
}
|
|
374
453
|
|
|
375
454
|
override intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {
|
|
376
|
-
return
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
455
|
+
return Mat.applyToPoints(
|
|
456
|
+
this.matrix,
|
|
457
|
+
this.geometry.intersectCircle(
|
|
458
|
+
Mat.applyToPoint(this.inverse, center),
|
|
459
|
+
radius / this.decomposed.scaleX,
|
|
460
|
+
filters
|
|
461
|
+
)
|
|
380
462
|
)
|
|
381
463
|
}
|
|
382
464
|
|
|
383
465
|
override intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
384
|
-
return
|
|
466
|
+
return Mat.applyToPoints(
|
|
467
|
+
this.matrix,
|
|
468
|
+
this.geometry.intersectPolygon(Mat.applyToPoints(this.inverse, polygon), filters)
|
|
469
|
+
)
|
|
385
470
|
}
|
|
386
471
|
|
|
387
472
|
override intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
388
|
-
return
|
|
473
|
+
return Mat.applyToPoints(
|
|
474
|
+
this.matrix,
|
|
475
|
+
this.geometry.intersectPolyline(Mat.applyToPoints(this.inverse, polyline), filters)
|
|
476
|
+
)
|
|
389
477
|
}
|
|
390
478
|
|
|
391
479
|
override transform(transform: MatModel, opts?: TransformedGeometry2dOptions): Geometry2d {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EMPTY_ARRAY } from '@tldraw/state'
|
|
2
|
+
import { assert, invLerp, lerp } from '@tldraw/utils'
|
|
2
3
|
import { Box } from '../Box'
|
|
3
4
|
import { Mat } from '../Mat'
|
|
4
5
|
import { Vec, VecLike } from '../Vec'
|
|
@@ -34,7 +35,7 @@ export class Group2d extends Geometry2d {
|
|
|
34
35
|
.flatMap((c) => c.getVertices(filters))
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
override nearestPoint(point:
|
|
38
|
+
override nearestPoint(point: VecLike, filters?: Geometry2dFilters): Vec {
|
|
38
39
|
let dist = Infinity
|
|
39
40
|
let nearest: Vec | undefined
|
|
40
41
|
|
|
@@ -59,7 +60,7 @@ export class Group2d extends Geometry2d {
|
|
|
59
60
|
return nearest
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
override distanceToPoint(point:
|
|
63
|
+
override distanceToPoint(point: VecLike, hitInside = false, filters?: Geometry2dFilters) {
|
|
63
64
|
let smallestDistance = Infinity
|
|
64
65
|
for (const child of this.children) {
|
|
65
66
|
if (child.isExcludedByFilter(filters)) continue
|
|
@@ -72,7 +73,7 @@ export class Group2d extends Geometry2d {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
override hitTestPoint(
|
|
75
|
-
point:
|
|
76
|
+
point: VecLike,
|
|
76
77
|
margin: number,
|
|
77
78
|
hitInside: boolean,
|
|
78
79
|
filters = Geometry2dFilters.EXCLUDE_LABELS
|
|
@@ -83,8 +84,8 @@ export class Group2d extends Geometry2d {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
override hitTestLineSegment(
|
|
86
|
-
A:
|
|
87
|
-
B:
|
|
87
|
+
A: VecLike,
|
|
88
|
+
B: VecLike,
|
|
88
89
|
zoom: number,
|
|
89
90
|
filters = Geometry2dFilters.EXCLUDE_LABELS
|
|
90
91
|
): boolean {
|
|
@@ -121,6 +122,63 @@ export class Group2d extends Geometry2d {
|
|
|
121
122
|
})
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
override interpolateAlongEdge(t: number, filters?: Geometry2dFilters): Vec {
|
|
126
|
+
const totalLength = this.getLength(filters)
|
|
127
|
+
|
|
128
|
+
const distanceToTravel = t * totalLength
|
|
129
|
+
let distanceTraveled = 0
|
|
130
|
+
for (const child of this.children) {
|
|
131
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
132
|
+
const childLength = child.length
|
|
133
|
+
const newDistanceTraveled = distanceTraveled + childLength
|
|
134
|
+
if (newDistanceTraveled >= distanceToTravel) {
|
|
135
|
+
return child.interpolateAlongEdge(
|
|
136
|
+
invLerp(distanceTraveled, newDistanceTraveled, distanceToTravel),
|
|
137
|
+
filters
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
distanceTraveled = newDistanceTraveled
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return this.children[this.children.length - 1].interpolateAlongEdge(1, filters)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
override uninterpolateAlongEdge(point: VecLike, filters?: Geometry2dFilters): number {
|
|
147
|
+
const totalLength = this.getLength(filters)
|
|
148
|
+
|
|
149
|
+
let closestChild = null
|
|
150
|
+
let closestDistance = Infinity
|
|
151
|
+
let distanceTraveled = 0
|
|
152
|
+
|
|
153
|
+
for (const child of this.children) {
|
|
154
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
155
|
+
const childLength = child.getLength(filters)
|
|
156
|
+
const newDistanceTraveled = distanceTraveled + childLength
|
|
157
|
+
|
|
158
|
+
const distance = child.distanceToPoint(point, false, filters)
|
|
159
|
+
if (distance < closestDistance) {
|
|
160
|
+
closestDistance = distance
|
|
161
|
+
closestChild = {
|
|
162
|
+
startLength: distanceTraveled,
|
|
163
|
+
endLength: newDistanceTraveled,
|
|
164
|
+
child,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
distanceTraveled = newDistanceTraveled
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
assert(closestChild)
|
|
172
|
+
|
|
173
|
+
const normalizedDistanceInChild = closestChild.child.uninterpolateAlongEdge(point, filters)
|
|
174
|
+
const childTLength = lerp(
|
|
175
|
+
closestChild.startLength,
|
|
176
|
+
closestChild.endLength,
|
|
177
|
+
normalizedDistanceInChild
|
|
178
|
+
)
|
|
179
|
+
return childTLength / totalLength
|
|
180
|
+
}
|
|
181
|
+
|
|
124
182
|
override transform(transform: Mat): Geometry2d {
|
|
125
183
|
return new Group2d({
|
|
126
184
|
children: this.children.map((c) => c.transform(transform)),
|
|
@@ -160,8 +218,13 @@ export class Group2d extends Geometry2d {
|
|
|
160
218
|
return path
|
|
161
219
|
}
|
|
162
220
|
|
|
163
|
-
getLength(): number {
|
|
164
|
-
|
|
221
|
+
getLength(filters?: Geometry2dFilters): number {
|
|
222
|
+
let length = 0
|
|
223
|
+
for (const child of this.children) {
|
|
224
|
+
if (child.isExcludedByFilter(filters)) continue
|
|
225
|
+
length += child.length
|
|
226
|
+
}
|
|
227
|
+
return length
|
|
165
228
|
}
|
|
166
229
|
|
|
167
230
|
getSvgPathData(): string {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vec } from '../Vec'
|
|
1
|
+
import { Vec, VecLike } from '../Vec'
|
|
2
2
|
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
|
3
3
|
|
|
4
4
|
/** @public */
|
|
@@ -22,7 +22,7 @@ export class Point2d extends Geometry2d {
|
|
|
22
22
|
return this.point
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
hitTestLineSegment(A:
|
|
25
|
+
hitTestLineSegment(A: VecLike, B: VecLike, margin: number): boolean {
|
|
26
26
|
return Vec.DistanceToLineSegment(A, B, this.point) < margin
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vec } from '../Vec'
|
|
1
|
+
import { Vec, VecLike } from '../Vec'
|
|
2
2
|
import { Edge2d } from './Edge2d'
|
|
3
3
|
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
|
4
4
|
|
|
@@ -41,7 +41,7 @@ export class Polyline2d extends Geometry2d {
|
|
|
41
41
|
return this.points
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
nearestPoint(A:
|
|
44
|
+
nearestPoint(A: VecLike): Vec {
|
|
45
45
|
const { segments } = this
|
|
46
46
|
let nearest = this.points[0]
|
|
47
47
|
let dist = Infinity
|
|
@@ -59,7 +59,7 @@ export class Polyline2d extends Geometry2d {
|
|
|
59
59
|
return nearest
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
hitTestLineSegment(A:
|
|
62
|
+
hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
|
|
63
63
|
const { segments } = this
|
|
64
64
|
for (let i = 0, n = segments.length; i < n; i++) {
|
|
65
65
|
if (segments[i].hitTestLineSegment(A, B, distance)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Box } from '../Box'
|
|
2
|
-
import { Vec } from '../Vec'
|
|
2
|
+
import { Vec, VecLike } from '../Vec'
|
|
3
3
|
import { PI } from '../utils'
|
|
4
4
|
import { Arc2d } from './Arc2d'
|
|
5
5
|
import { Edge2d } from './Edge2d'
|
|
@@ -65,7 +65,7 @@ export class Stadium2d extends Geometry2d {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
nearestPoint(A:
|
|
68
|
+
nearestPoint(A: VecLike): Vec {
|
|
69
69
|
let nearest: Vec | undefined
|
|
70
70
|
let dist = Infinity
|
|
71
71
|
let _d: number
|
|
@@ -84,7 +84,7 @@ export class Stadium2d extends Geometry2d {
|
|
|
84
84
|
return nearest
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
hitTestLineSegment(A:
|
|
87
|
+
hitTestLineSegment(A: VecLike, B: VecLike): boolean {
|
|
88
88
|
const { a, b, c, d } = this
|
|
89
89
|
return [a, b, c, d].some((edge) => edge.hitTestLineSegment(A, B))
|
|
90
90
|
}
|
|
@@ -54,6 +54,7 @@ export const debugFlags = {
|
|
|
54
54
|
hideShapes: createDebugValue('hideShapes', { defaults: { all: false } }),
|
|
55
55
|
editOnType: createDebugValue('editOnType', { defaults: { all: false } }),
|
|
56
56
|
a11y: createDebugValue('a11y', { defaults: { all: false } }),
|
|
57
|
+
debugElbowArrows: createDebugValue('debugElbowArrows', { defaults: { all: false } }),
|
|
57
58
|
} as const
|
|
58
59
|
|
|
59
60
|
declare global {
|
|
@@ -149,7 +150,9 @@ function createDebugValueBase<T>(def: DebugFlagDef<T>): DebugFlag<T> {
|
|
|
149
150
|
})
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
return Object.assign(valueAtom, def
|
|
153
|
+
return Object.assign(valueAtom, def, {
|
|
154
|
+
reset: () => valueAtom.set(defaultValue),
|
|
155
|
+
})
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
function getStoredInitialValue(name: string) {
|
|
@@ -206,4 +209,6 @@ export interface DebugFlagDef<T> {
|
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
/** @internal */
|
|
209
|
-
export
|
|
212
|
+
export interface DebugFlag<T> extends DebugFlagDef<T>, Atom<T> {
|
|
213
|
+
reset(): void
|
|
214
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Euclidean algorithm to find the GCD
|
|
2
|
+
function gcd(a: number, b: number): number {
|
|
3
|
+
return b === 0 ? a : gcd(b, a % b)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Returns the lowest value that the given number can be multiplied by to reach an integer
|
|
7
|
+
export function nearestMultiple(float: number) {
|
|
8
|
+
const decimal = float.toString().split('.')[1]
|
|
9
|
+
if (!decimal) return 1
|
|
10
|
+
const denominator = Math.pow(10, decimal.length)
|
|
11
|
+
const numerator = parseInt(decimal, 10)
|
|
12
|
+
return denominator / gcd(numerator, denominator)
|
|
13
|
+
}
|
|
@@ -3,7 +3,7 @@ import { openDB } from 'idb'
|
|
|
3
3
|
import { hardReset } from './hardReset'
|
|
4
4
|
import { getAllIndexDbNames, LocalIndexedDb } from './LocalIndexedDb'
|
|
5
5
|
|
|
6
|
-
const schema = createTLSchema({ shapes: {} })
|
|
6
|
+
const schema = createTLSchema({ shapes: {}, bindings: {} })
|
|
7
7
|
describe('LocalIndexedDb', () => {
|
|
8
8
|
beforeEach(() => {
|
|
9
9
|
jest.useRealTimers()
|
|
@@ -19,7 +19,7 @@ class BroadcastChannelMock {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function testClient(channel = new BroadcastChannelMock('test')) {
|
|
22
|
-
const store = createTLStore({ shapeUtils: [] })
|
|
22
|
+
const store = createTLStore({ shapeUtils: [], bindingUtils: [] })
|
|
23
23
|
const onLoad = jest.fn(() => {
|
|
24
24
|
return
|
|
25
25
|
})
|
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.
|
|
4
|
+
export const version = '3.14.0-canary.95353287a2be'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-05-22T14:14:36.289Z',
|
|
8
|
+
patch: '2025-05-22T14:14:36.289Z',
|
|
9
9
|
}
|