@tldraw/editor 5.4.0 → 5.5.0-next.6d9e51e9f3f7
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 +5 -2
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/constants.js +2 -0
- package/dist-cjs/lib/constants.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +3 -2
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +10 -6
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +7 -3
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Arc2d.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/Edge2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -5
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Point2d.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 +5 -2
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/constants.mjs +2 -0
- package/dist-esm/lib/constants.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +4 -2
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +10 -6
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +7 -3
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Arc2d.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/Edge2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +12 -6
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Point2d.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/constants.ts +8 -0
- package/src/lib/editor/Editor.ts +7 -3
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +35 -4
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +13 -7
- package/src/lib/editor/managers/ThemeManager/ThemeManager.test.ts +53 -0
- package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +9 -3
- package/src/lib/primitives/geometry/Arc2d.test.ts +9 -0
- package/src/lib/primitives/geometry/Arc2d.ts +1 -1
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +13 -0
- package/src/lib/primitives/geometry/CubicBezier2d.ts +1 -1
- package/src/lib/primitives/geometry/Edge2d.test.ts +21 -3
- package/src/lib/primitives/geometry/Edge2d.ts +1 -1
- package/src/lib/primitives/geometry/Group2d.test.ts +26 -5
- package/src/lib/primitives/geometry/Group2d.ts +18 -7
- package/src/lib/primitives/geometry/Point2d.test.ts +14 -3
- package/src/lib/primitives/geometry/Point2d.ts +1 -1
- package/src/version.ts +3 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { TLTheme } from '@tldraw/tlschema'
|
|
2
|
+
import { MockInstance, vi } from 'vitest'
|
|
3
|
+
import { TestEditor } from '../../../test/TestEditor'
|
|
4
|
+
import { DEFAULT_THEME } from './defaultThemes'
|
|
5
|
+
|
|
6
|
+
// Optional so that other tests' theme objects still satisfy TLThemes.
|
|
7
|
+
declare module '@tldraw/tlschema' {
|
|
8
|
+
interface TLThemes {
|
|
9
|
+
ocean?: TLTheme
|
|
10
|
+
sunset?: TLTheme
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const oceanTheme: TLTheme = { ...DEFAULT_THEME, id: 'ocean' }
|
|
15
|
+
|
|
16
|
+
let editor: TestEditor
|
|
17
|
+
let warn: MockInstance
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
editor = new TestEditor({ themes: { ocean: oceanTheme } })
|
|
21
|
+
warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
warn.mockRestore()
|
|
26
|
+
editor.dispose()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('setCurrentTheme', () => {
|
|
30
|
+
it('switches to a registered theme', () => {
|
|
31
|
+
editor.setCurrentTheme('ocean')
|
|
32
|
+
|
|
33
|
+
expect(editor.getCurrentThemeId()).toBe('ocean')
|
|
34
|
+
expect(editor.getCurrentTheme()).toBe(oceanTheme)
|
|
35
|
+
expect(warn).not.toHaveBeenCalled()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('warns and keeps the current theme when the id is not registered', () => {
|
|
39
|
+
editor.setCurrentTheme('sunset')
|
|
40
|
+
|
|
41
|
+
expect(warn).toHaveBeenCalledWith("Theme 'sunset' not found. Available themes: default, ocean")
|
|
42
|
+
expect(editor.getCurrentThemeId()).toBe('default')
|
|
43
|
+
expect(editor.getCurrentTheme()).toBe(DEFAULT_THEME)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('keeps the previous theme rather than falling back to default', () => {
|
|
47
|
+
editor.setCurrentTheme('ocean')
|
|
48
|
+
editor.setCurrentTheme('sunset')
|
|
49
|
+
|
|
50
|
+
expect(editor.getCurrentThemeId()).toBe('ocean')
|
|
51
|
+
expect(editor.getCurrentTheme()).toBe(oceanTheme)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -69,14 +69,20 @@ export class ThemeManager {
|
|
|
69
69
|
return this._themes.get()[this.getCurrentThemeId()]!
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Set the current theme by id. The theme must have been previously registered; an
|
|
74
|
+
* unregistered id is ignored and the current theme is kept.
|
|
75
|
+
*/
|
|
73
76
|
setCurrentTheme(id: TLThemeId): void {
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
// Storing an unregistered id would make getCurrentTheme() return
|
|
78
|
+
// undefined despite its TLTheme return type.
|
|
79
|
+
if (!(id in this._themes.get())) {
|
|
80
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
76
81
|
console.warn(
|
|
77
82
|
`Theme '${id}' not found. Available themes: ${Object.keys(this._themes.get()).join(', ')}`
|
|
78
83
|
)
|
|
79
84
|
}
|
|
85
|
+
return
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
this._currentThemeId.set(id)
|
|
@@ -225,4 +225,13 @@ describe('Arc2d.getSvgPathData', () => {
|
|
|
225
225
|
})
|
|
226
226
|
expect(arc.getSvgPathData()).toBe('M10, 0 A10 10 0 0 1 7.07, 7.07')
|
|
227
227
|
})
|
|
228
|
+
|
|
229
|
+
it('does not mutate the stored points', () => {
|
|
230
|
+
const start = new Vec(10, 0)
|
|
231
|
+
const end = new Vec(10 / Math.SQRT2, 10 / Math.SQRT2)
|
|
232
|
+
const arc = new Arc2d({ center: new Vec(0, 0), start, end, sweepFlag: 1, largeArcFlag: 0 })
|
|
233
|
+
arc.getSvgPathData()
|
|
234
|
+
expect(start).toMatchObject({ x: 10, y: 0 })
|
|
235
|
+
expect(end).toMatchObject({ x: 10 / Math.SQRT2, y: 10 / Math.SQRT2 })
|
|
236
|
+
})
|
|
228
237
|
})
|
|
@@ -105,7 +105,7 @@ export class Arc2d extends Geometry2d {
|
|
|
105
105
|
_largeArcFlag: largeArcFlag,
|
|
106
106
|
_sweepFlag: sweepFlag,
|
|
107
107
|
} = this
|
|
108
|
-
return `${first ? `M${start
|
|
108
|
+
return `${first ? `M${start}` : ``} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end}`
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
override getLength() {
|
|
@@ -172,4 +172,17 @@ describe('CubicBezier2d.getSvgPathData', () => {
|
|
|
172
172
|
})
|
|
173
173
|
expect(b.getSvgPathData()).toBe('M 0.12, 0 C1.01, 2 3, 5 10, 10')
|
|
174
174
|
})
|
|
175
|
+
|
|
176
|
+
it('does not mutate the stored points', () => {
|
|
177
|
+
const start = new Vec(0.123, 0)
|
|
178
|
+
const cp1 = new Vec(1.006, 2)
|
|
179
|
+
const cp2 = new Vec(3, 4.999)
|
|
180
|
+
const end = new Vec(10, 10.005)
|
|
181
|
+
const b = new CubicBezier2d({ start, cp1, cp2, end })
|
|
182
|
+
b.getSvgPathData()
|
|
183
|
+
expect(start).toMatchObject({ x: 0.123, y: 0 })
|
|
184
|
+
expect(cp1).toMatchObject({ x: 1.006, y: 2 })
|
|
185
|
+
expect(cp2).toMatchObject({ x: 3, y: 4.999 })
|
|
186
|
+
expect(end).toMatchObject({ x: 10, y: 10.005 })
|
|
187
|
+
})
|
|
175
188
|
})
|
|
@@ -81,7 +81,7 @@ export class CubicBezier2d extends Polyline2d {
|
|
|
81
81
|
|
|
82
82
|
getSvgPathData(first = true) {
|
|
83
83
|
const { _a: a, _b: b, _c: c, _d: d } = this
|
|
84
|
-
return `${first ? `M ${a
|
|
84
|
+
return `${first ? `M ${a} ` : ``} C${b} ${c} ${d}`
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
static GetAtT(segment: CubicBezier2d, t: number) {
|
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Vec } from '../Vec'
|
|
2
|
+
import { Edge2d } from './Edge2d'
|
|
3
|
+
|
|
4
|
+
describe('Edge2d.getSvgPathData', () => {
|
|
5
|
+
it('emits a move and a line command, rounded to two decimals', () => {
|
|
6
|
+
const edge = new Edge2d({ start: new Vec(0.123456, 0), end: new Vec(10, 10.987654) })
|
|
7
|
+
expect(edge.getSvgPathData()).toBe('M0.12, 0 L10, 10.99')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('omits the move command when not first', () => {
|
|
11
|
+
const edge = new Edge2d({ start: new Vec(0.123456, 0), end: new Vec(10, 10.987654) })
|
|
12
|
+
expect(edge.getSvgPathData(false)).toBe(' L10, 10.99')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('does not mutate the stored points', () => {
|
|
16
|
+
const start = new Vec(0.123456, 0)
|
|
17
|
+
const end = new Vec(10, 10.987654)
|
|
18
|
+
const edge = new Edge2d({ start, end })
|
|
19
|
+
edge.getSvgPathData()
|
|
20
|
+
expect(start).toMatchObject({ x: 0.123456, y: 0 })
|
|
21
|
+
expect(end).toMatchObject({ x: 10, y: 10.987654 })
|
|
4
22
|
})
|
|
5
23
|
})
|
|
@@ -340,20 +340,38 @@ describe('Group2d.transform', () => {
|
|
|
340
340
|
expect(t.vertices.length).toBe(8)
|
|
341
341
|
})
|
|
342
342
|
|
|
343
|
-
it('carries the
|
|
343
|
+
it('carries all the flags forward', () => {
|
|
344
344
|
const g = new Group2d({
|
|
345
345
|
children: [rectA()],
|
|
346
346
|
isLabel: true,
|
|
347
|
+
isEmptyLabel: true,
|
|
348
|
+
isInternal: true,
|
|
349
|
+
excludeFromShapeBounds: true,
|
|
347
350
|
debugColor: 'red',
|
|
348
351
|
ignore: true,
|
|
349
352
|
})
|
|
350
353
|
expect(g.transform(Mat.Identity())).toMatchObject({
|
|
351
354
|
isLabel: true,
|
|
355
|
+
isEmptyLabel: true,
|
|
356
|
+
isInternal: true,
|
|
357
|
+
excludeFromShapeBounds: true,
|
|
352
358
|
debugColor: 'red',
|
|
353
359
|
ignore: true,
|
|
354
360
|
})
|
|
355
361
|
})
|
|
356
362
|
|
|
363
|
+
it('applies the transform options over the carried flags', () => {
|
|
364
|
+
const g = new Group2d({ children: [rectA()], isLabel: true, isInternal: true })
|
|
365
|
+
expect(
|
|
366
|
+
g.transform(Mat.Identity(), {
|
|
367
|
+
isLabel: false,
|
|
368
|
+
isInternal: false,
|
|
369
|
+
debugColor: 'red',
|
|
370
|
+
ignore: true,
|
|
371
|
+
})
|
|
372
|
+
).toMatchObject({ isLabel: false, isInternal: false, debugColor: 'red', ignore: true })
|
|
373
|
+
})
|
|
374
|
+
|
|
357
375
|
it('scales hit testing with the transform', () => {
|
|
358
376
|
const t = group().transform(Mat.Scale(2, 2))
|
|
359
377
|
expect(t.hitTestPoint(new Vec(100, 50), 0, false)).toBe(true)
|
|
@@ -361,12 +379,15 @@ describe('Group2d.transform', () => {
|
|
|
361
379
|
expect(t.distanceToPoint(new Vec(300, 50))).toBe(100)
|
|
362
380
|
})
|
|
363
381
|
|
|
364
|
-
//
|
|
365
|
-
it('
|
|
382
|
+
// #10562: transform used to drop the ignored children.
|
|
383
|
+
it('keeps the transformed ignored children', () => {
|
|
366
384
|
const ignored = new Rectangle2d({ x: 500, width: 10, height: 10, isFilled: true, ignore: true })
|
|
367
385
|
const g = new Group2d({ children: [rectA(), ignored] })
|
|
368
|
-
|
|
369
|
-
expect(
|
|
386
|
+
const t = g.transform(Mat.Translate(10, 20)) as Group2d
|
|
387
|
+
expect(t.children.length).toBe(1)
|
|
388
|
+
expect(t.ignoredChildren.length).toBe(1)
|
|
389
|
+
expect(t.ignoredChildren[0].ignore).toBe(true)
|
|
390
|
+
expect(t.ignoredChildren[0].bounds).toMatchObject({ x: 510, y: 20, w: 10, h: 10 })
|
|
370
391
|
})
|
|
371
392
|
})
|
|
372
393
|
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { assert, invLerp, lerp } from '@tldraw/utils'
|
|
2
2
|
import { Box } from '../Box'
|
|
3
|
-
import {
|
|
3
|
+
import { MatModel } from '../Mat'
|
|
4
4
|
import { Vec, VecLike } from '../Vec'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Geometry2d,
|
|
7
|
+
Geometry2dFilters,
|
|
8
|
+
Geometry2dOptions,
|
|
9
|
+
TransformedGeometry2dOptions,
|
|
10
|
+
} from './Geometry2d'
|
|
6
11
|
|
|
7
12
|
/** @public */
|
|
8
13
|
export class Group2d extends Geometry2d {
|
|
@@ -226,12 +231,18 @@ export class Group2d extends Geometry2d {
|
|
|
226
231
|
return childTLength / totalLength
|
|
227
232
|
}
|
|
228
233
|
|
|
229
|
-
override transform(transform:
|
|
234
|
+
override transform(transform: MatModel, opts?: TransformedGeometry2dOptions): Geometry2d {
|
|
235
|
+
// Each child's own transform preserves its `ignore` flag, so the constructor
|
|
236
|
+
// re-partitions ignored children back into `ignoredChildren` instead of dropping
|
|
237
|
+
// them (#10562).
|
|
230
238
|
return new Group2d({
|
|
231
|
-
children: this.children.map((c) => c.transform(transform)),
|
|
232
|
-
isLabel: this.isLabel,
|
|
233
|
-
|
|
234
|
-
|
|
239
|
+
children: [...this.children, ...this.ignoredChildren].map((c) => c.transform(transform)),
|
|
240
|
+
isLabel: opts?.isLabel ?? this.isLabel,
|
|
241
|
+
isEmptyLabel: this.isEmptyLabel,
|
|
242
|
+
isInternal: opts?.isInternal ?? this.isInternal,
|
|
243
|
+
debugColor: opts?.debugColor ?? this.debugColor,
|
|
244
|
+
ignore: opts?.ignore ?? this.ignore,
|
|
245
|
+
excludeFromShapeBounds: this.excludeFromShapeBounds,
|
|
235
246
|
})
|
|
236
247
|
}
|
|
237
248
|
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Vec } from '../Vec'
|
|
2
|
+
import { Point2d } from './Point2d'
|
|
3
|
+
|
|
4
|
+
describe('Point2d.getSvgPathData', () => {
|
|
5
|
+
it('emits a move command rounded to two decimals', () => {
|
|
6
|
+
const point = new Point2d({ margin: 0, point: new Vec(0.123456, 7.891234) })
|
|
7
|
+
expect(point.getSvgPathData()).toBe('M0.12, 7.89')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('does not mutate the stored point', () => {
|
|
11
|
+
const vec = new Vec(0.123456, 7.891234)
|
|
12
|
+
const point = new Point2d({ margin: 0, point: vec })
|
|
13
|
+
point.getSvgPathData()
|
|
14
|
+
expect(vec).toMatchObject({ x: 0.123456, y: 7.891234 })
|
|
4
15
|
})
|
|
5
16
|
})
|
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
|
+
export const version = '5.5.0-next.6d9e51e9f3f7'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2026-05-06T16:28:18.473Z',
|
|
7
|
-
minor: '2026-09-02T11:
|
|
8
|
-
patch: '2026-09-02T11:
|
|
7
|
+
minor: '2026-09-02T11:26:03.383Z',
|
|
8
|
+
patch: '2026-09-02T11:26:03.383Z',
|
|
9
9
|
}
|