@tldraw/editor 3.14.0-canary.cf19563e117d → 3.14.0-canary.d1b8a584b27c
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 +59 -42
- package/dist-cjs/index.js +1 -3
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js +1 -12
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +3 -3
- package/dist-cjs/lib/editor/Editor.js +33 -6
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +1 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +73 -42
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +1 -1
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +1 -1
- package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +0 -6
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
- package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
- package/dist-cjs/lib/utils/richText.js +7 -2
- package/dist-cjs/lib/utils/richText.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 +59 -42
- package/dist-esm/index.mjs +1 -3
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +33 -6
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +1 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +73 -42
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +1 -1
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +1 -1
- package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +0 -6
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
- package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
- package/dist-esm/lib/utils/richText.mjs +8 -3
- package/dist-esm/lib/utils/richText.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +433 -482
- package/package.json +8 -9
- package/src/index.ts +2 -1
- package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
- package/src/lib/editor/Editor.ts +32 -3
- package/src/lib/editor/bindings/BindingUtil.ts +6 -0
- package/src/lib/editor/managers/FontManager/FontManager.ts +1 -2
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +1 -5
- package/src/lib/editor/managers/TextManager/TextManager.ts +118 -86
- package/src/lib/editor/shapes/ShapeUtil.ts +1 -0
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
- package/src/lib/editor/tools/StateNode.ts +3 -3
- package/src/lib/editor/types/external-content.ts +11 -2
- package/src/lib/hooks/useCanvasEvents.ts +0 -1
- package/src/lib/primitives/Box.ts +0 -8
- package/src/lib/utils/areShapesContentEqual.ts +1 -2
- package/src/lib/utils/richText.ts +9 -3
- package/src/version.ts +3 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "A tiny little drawing app (editor).",
|
|
4
|
-
"version": "3.14.0-canary.
|
|
4
|
+
"version": "3.14.0-canary.d1b8a584b27c",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -48,20 +48,19 @@
|
|
|
48
48
|
"@tiptap/core": "^2.9.1",
|
|
49
49
|
"@tiptap/pm": "^2.9.1",
|
|
50
50
|
"@tiptap/react": "^2.9.1",
|
|
51
|
-
"@tldraw/state": "3.14.0-canary.
|
|
52
|
-
"@tldraw/state-react": "3.14.0-canary.
|
|
53
|
-
"@tldraw/store": "3.14.0-canary.
|
|
54
|
-
"@tldraw/tlschema": "3.14.0-canary.
|
|
55
|
-
"@tldraw/utils": "3.14.0-canary.
|
|
56
|
-
"@tldraw/validate": "3.14.0-canary.
|
|
51
|
+
"@tldraw/state": "3.14.0-canary.d1b8a584b27c",
|
|
52
|
+
"@tldraw/state-react": "3.14.0-canary.d1b8a584b27c",
|
|
53
|
+
"@tldraw/store": "3.14.0-canary.d1b8a584b27c",
|
|
54
|
+
"@tldraw/tlschema": "3.14.0-canary.d1b8a584b27c",
|
|
55
|
+
"@tldraw/utils": "3.14.0-canary.d1b8a584b27c",
|
|
56
|
+
"@tldraw/validate": "3.14.0-canary.d1b8a584b27c",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
|
60
60
|
"core-js": "^3.40.0",
|
|
61
61
|
"eventemitter3": "^4.0.7",
|
|
62
62
|
"idb": "^7.1.1",
|
|
63
|
-
"is-plain-object": "^5.0.0"
|
|
64
|
-
"lodash.isequal": "^4.5.0"
|
|
63
|
+
"is-plain-object": "^5.0.0"
|
|
65
64
|
},
|
|
66
65
|
"peerDependencies": {
|
|
67
66
|
"react": "^18.2.0 || ^19.0.0",
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ import 'core-js/stable/array/flat-map.js'
|
|
|
4
4
|
import 'core-js/stable/array/flat.js'
|
|
5
5
|
import 'core-js/stable/string/at.js'
|
|
6
6
|
import 'core-js/stable/string/replace-all.js'
|
|
7
|
-
export { areShapesContentEqual } from './lib/utils/areShapesContentEqual'
|
|
8
7
|
|
|
9
8
|
// eslint-disable-next-line local/no-export-star
|
|
10
9
|
export * from '@tldraw/state'
|
|
@@ -175,6 +174,7 @@ export {
|
|
|
175
174
|
} from './lib/editor/managers/SnapManager/SnapManager'
|
|
176
175
|
export {
|
|
177
176
|
TextManager,
|
|
177
|
+
type TLMeasureTextOpts,
|
|
178
178
|
type TLMeasureTextSpanOpts,
|
|
179
179
|
} from './lib/editor/managers/TextManager/TextManager'
|
|
180
180
|
export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
|
|
@@ -253,6 +253,7 @@ export {
|
|
|
253
253
|
type TLExternalContent,
|
|
254
254
|
type TLExternalContentSource,
|
|
255
255
|
type TLFileExternalAsset,
|
|
256
|
+
type TLFileReplaceExternalContent,
|
|
256
257
|
type TLFilesExternalContent,
|
|
257
258
|
type TLSvgTextExternalContent,
|
|
258
259
|
type TLTextExternalContent,
|
|
@@ -14,12 +14,12 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
deleteFromSessionStorage,
|
|
16
16
|
getFromSessionStorage,
|
|
17
|
+
isEqual,
|
|
17
18
|
setInSessionStorage,
|
|
18
19
|
structuredClone,
|
|
19
20
|
uniqueId,
|
|
20
21
|
} from '@tldraw/utils'
|
|
21
22
|
import { T } from '@tldraw/validate'
|
|
22
|
-
import isEqual from 'lodash.isequal'
|
|
23
23
|
import { tlenv } from '../globals/environment'
|
|
24
24
|
|
|
25
25
|
const tabIdKey = 'TLDRAW_TAB_ID_v2' as const
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -348,6 +348,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
348
348
|
this.getContainer = getContainer
|
|
349
349
|
|
|
350
350
|
this.textMeasure = new TextManager(this)
|
|
351
|
+
this.disposables.add(() => this.textMeasure.dispose())
|
|
352
|
+
|
|
351
353
|
this.fonts = new FontManager(this, fontAssetUrls)
|
|
352
354
|
|
|
353
355
|
this._tickManager = new TickManager(this)
|
|
@@ -506,14 +508,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
506
508
|
shape: {
|
|
507
509
|
afterChange: (shapeBefore, shapeAfter) => {
|
|
508
510
|
for (const binding of this.getBindingsInvolvingShape(shapeAfter)) {
|
|
509
|
-
if (areShapesContentEqual(shapeBefore, shapeAfter)) continue
|
|
510
|
-
|
|
511
511
|
invalidBindingTypes.add(binding.type)
|
|
512
512
|
if (binding.fromId === shapeAfter.id) {
|
|
513
513
|
this.getBindingUtil(binding).onAfterChangeFromShape?.({
|
|
514
514
|
binding,
|
|
515
515
|
shapeBefore,
|
|
516
516
|
shapeAfter,
|
|
517
|
+
reason: 'self',
|
|
517
518
|
})
|
|
518
519
|
}
|
|
519
520
|
if (binding.toId === shapeAfter.id) {
|
|
@@ -521,6 +522,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
521
522
|
binding,
|
|
522
523
|
shapeBefore,
|
|
523
524
|
shapeAfter,
|
|
525
|
+
reason: 'self',
|
|
524
526
|
})
|
|
525
527
|
}
|
|
526
528
|
}
|
|
@@ -539,6 +541,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
539
541
|
binding,
|
|
540
542
|
shapeBefore: descendantShape,
|
|
541
543
|
shapeAfter: descendantShape,
|
|
544
|
+
reason: 'ancestry',
|
|
542
545
|
})
|
|
543
546
|
}
|
|
544
547
|
if (binding.toId === descendantShape.id) {
|
|
@@ -546,6 +549,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
546
549
|
binding,
|
|
547
550
|
shapeBefore: descendantShape,
|
|
548
551
|
shapeAfter: descendantShape,
|
|
552
|
+
reason: 'ancestry',
|
|
549
553
|
})
|
|
550
554
|
}
|
|
551
555
|
}
|
|
@@ -2118,6 +2122,20 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2118
2122
|
return this.getShapesPageBounds(this.getSelectedShapeIds())
|
|
2119
2123
|
}
|
|
2120
2124
|
|
|
2125
|
+
/**
|
|
2126
|
+
* The bounds of the selection bounding box in the current page space.
|
|
2127
|
+
*
|
|
2128
|
+
* @readonly
|
|
2129
|
+
* @public
|
|
2130
|
+
*/
|
|
2131
|
+
getSelectionScreenBounds(): Box | undefined {
|
|
2132
|
+
const bounds = this.getSelectionPageBounds()
|
|
2133
|
+
if (!bounds) return undefined
|
|
2134
|
+
const { x, y } = this.pageToScreen(bounds.point)
|
|
2135
|
+
const zoom = this.getZoomLevel()
|
|
2136
|
+
return new Box(x, y, bounds.width * zoom, bounds.height * zoom)
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2121
2139
|
/**
|
|
2122
2140
|
* @internal
|
|
2123
2141
|
*/
|
|
@@ -6193,11 +6211,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6193
6211
|
*/
|
|
6194
6212
|
duplicateShapes(shapes: TLShapeId[] | TLShape[], offset?: VecLike): this {
|
|
6195
6213
|
this.run(() => {
|
|
6196
|
-
const
|
|
6214
|
+
const _ids =
|
|
6197
6215
|
typeof shapes[0] === 'string'
|
|
6198
6216
|
? (shapes as TLShapeId[])
|
|
6199
6217
|
: (shapes as TLShape[]).map((s) => s.id)
|
|
6200
6218
|
|
|
6219
|
+
const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids)
|
|
6201
6220
|
if (ids.length <= 0) return this
|
|
6202
6221
|
|
|
6203
6222
|
const initialIds = new Set(ids)
|
|
@@ -8809,6 +8828,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8809
8828
|
} = {
|
|
8810
8829
|
text: null,
|
|
8811
8830
|
files: null,
|
|
8831
|
+
'file-replace': null,
|
|
8812
8832
|
embed: null,
|
|
8813
8833
|
'svg-text': null,
|
|
8814
8834
|
url: null,
|
|
@@ -8858,6 +8878,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8858
8878
|
return this.externalContentHandlers[info.type]?.(info as any)
|
|
8859
8879
|
}
|
|
8860
8880
|
|
|
8881
|
+
/**
|
|
8882
|
+
* Handle replacing external content.
|
|
8883
|
+
*
|
|
8884
|
+
* @param info - Info about the external content.
|
|
8885
|
+
*/
|
|
8886
|
+
async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
|
|
8887
|
+
return this.externalContentHandlers[info.type]?.(info as any)
|
|
8888
|
+
}
|
|
8889
|
+
|
|
8861
8890
|
/**
|
|
8862
8891
|
* Get content that can be exported for the given shape ids.
|
|
8863
8892
|
*
|
|
@@ -62,6 +62,12 @@ export interface BindingOnShapeChangeOptions<Binding extends TLUnknownBinding> {
|
|
|
62
62
|
shapeBefore: TLShape
|
|
63
63
|
/** The shape record after the change is made. */
|
|
64
64
|
shapeAfter: TLShape
|
|
65
|
+
/**
|
|
66
|
+
* Why did this shape change?
|
|
67
|
+
* - 'self': the shape itself changed
|
|
68
|
+
* - 'ancestry': the ancestry of the shape changed, but the shape itself may not have done
|
|
69
|
+
*/
|
|
70
|
+
reason: 'self' | 'ancestry'
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/**
|
|
@@ -96,8 +96,7 @@ export class FontManager {
|
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
areResultsEqual: areArraysShallowEqual,
|
|
99
|
-
|
|
100
|
-
areRecordsEqual: (a, b) => a.props.richText === b.props.richText,
|
|
99
|
+
areRecordsEqual: (a, b) => a.props === b.props && a.meta === b.meta,
|
|
101
100
|
}
|
|
102
101
|
)
|
|
103
102
|
|
|
@@ -99,7 +99,7 @@ describe('TextManager', () => {
|
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
it('should handle empty text', () => {
|
|
102
|
-
const result = textManager.measureText('', defaultOpts)
|
|
102
|
+
const result = textManager.measureText('', { ...defaultOpts, measureScrollWidth: true })
|
|
103
103
|
expect(result).toHaveProperty('x', 0)
|
|
104
104
|
expect(result).toHaveProperty('y', 0)
|
|
105
105
|
expect(result).toHaveProperty('w')
|
|
@@ -128,7 +128,6 @@ describe('TextManager', () => {
|
|
|
128
128
|
y: 0,
|
|
129
129
|
w: expect.any(Number),
|
|
130
130
|
h: expect.any(Number),
|
|
131
|
-
scrollWidth: expect.any(Number),
|
|
132
131
|
})
|
|
133
132
|
})
|
|
134
133
|
|
|
@@ -141,7 +140,6 @@ describe('TextManager', () => {
|
|
|
141
140
|
y: 0,
|
|
142
141
|
w: expect.any(Number),
|
|
143
142
|
h: expect.any(Number),
|
|
144
|
-
scrollWidth: expect.any(Number),
|
|
145
143
|
})
|
|
146
144
|
})
|
|
147
145
|
|
|
@@ -154,7 +152,6 @@ describe('TextManager', () => {
|
|
|
154
152
|
y: 0,
|
|
155
153
|
w: expect.any(Number),
|
|
156
154
|
h: expect.any(Number),
|
|
157
|
-
scrollWidth: expect.any(Number),
|
|
158
155
|
})
|
|
159
156
|
})
|
|
160
157
|
|
|
@@ -173,7 +170,6 @@ describe('TextManager', () => {
|
|
|
173
170
|
y: 0,
|
|
174
171
|
w: expect.any(Number),
|
|
175
172
|
h: expect.any(Number),
|
|
176
|
-
scrollWidth: expect.any(Number),
|
|
177
173
|
})
|
|
178
174
|
})
|
|
179
175
|
})
|
|
@@ -20,6 +20,28 @@ const textAlignmentsForLtr = {
|
|
|
20
20
|
'end-legacy': 'right',
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** @public */
|
|
24
|
+
export interface TLMeasureTextOpts {
|
|
25
|
+
fontStyle: string
|
|
26
|
+
fontWeight: string
|
|
27
|
+
fontFamily: string
|
|
28
|
+
fontSize: number
|
|
29
|
+
/** This must be a number, e.g. 1.35, not a pixel value. */
|
|
30
|
+
lineHeight: number
|
|
31
|
+
/**
|
|
32
|
+
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
|
33
|
+
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
34
|
+
* space are preserved.
|
|
35
|
+
*/
|
|
36
|
+
maxWidth: null | number
|
|
37
|
+
minWidth?: null | number
|
|
38
|
+
// todo: make this a number so that it is consistent with other TLMeasureTextSpanOpts
|
|
39
|
+
padding: string
|
|
40
|
+
otherStyles?: Record<string, string>
|
|
41
|
+
disableOverflowWrapBreaking?: boolean
|
|
42
|
+
measureScrollWidth?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
23
45
|
/** @public */
|
|
24
46
|
export interface TLMeasureTextSpanOpts {
|
|
25
47
|
overflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'
|
|
@@ -33,96 +55,99 @@ export interface TLMeasureTextSpanOpts {
|
|
|
33
55
|
lineHeight: number
|
|
34
56
|
textAlign: TLDefaultHorizontalAlignStyle
|
|
35
57
|
otherStyles?: Record<string, string>
|
|
58
|
+
measureScrollWidth?: boolean
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
const spaceCharacterRegex = /\s/
|
|
39
62
|
|
|
40
63
|
/** @public */
|
|
41
64
|
export class TextManager {
|
|
42
|
-
private
|
|
65
|
+
private elm: HTMLDivElement
|
|
66
|
+
private defaultStyles: Record<string, string | null>
|
|
43
67
|
|
|
44
68
|
constructor(public editor: Editor) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
const elm = document.createElement('div')
|
|
70
|
+
elm.classList.add('tl-text')
|
|
71
|
+
elm.classList.add('tl-text-measure')
|
|
72
|
+
elm.setAttribute('dir', 'auto')
|
|
73
|
+
elm.tabIndex = -1
|
|
74
|
+
this.editor.getContainer().appendChild(elm)
|
|
75
|
+
|
|
76
|
+
// we need to save the default styles so that we can restore them when we're done
|
|
77
|
+
// these must be the css names, not the js names for the styles
|
|
78
|
+
this.defaultStyles = {
|
|
79
|
+
'overflow-wrap': 'break-word',
|
|
80
|
+
'word-break': 'auto',
|
|
81
|
+
width: null,
|
|
82
|
+
height: null,
|
|
83
|
+
'max-width': null,
|
|
84
|
+
'min-width': null,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.elm = elm
|
|
49
88
|
}
|
|
50
89
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
|
61
|
-
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
62
|
-
* space are preserved.
|
|
63
|
-
*/
|
|
64
|
-
maxWidth: null | number
|
|
65
|
-
minWidth?: null | number
|
|
66
|
-
padding: string
|
|
67
|
-
disableOverflowWrapBreaking?: boolean
|
|
90
|
+
dispose() {
|
|
91
|
+
return this.elm.remove()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private resetElmStyles() {
|
|
95
|
+
const { elm, defaultStyles } = this
|
|
96
|
+
for (const key in defaultStyles) {
|
|
97
|
+
elm.style.setProperty(key, defaultStyles[key])
|
|
68
98
|
}
|
|
69
|
-
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
70
102
|
const div = document.createElement('div')
|
|
71
103
|
div.textContent = normalizeTextForDom(textToMeasure)
|
|
72
104
|
return this.measureHtml(div.innerHTML, opts)
|
|
73
105
|
}
|
|
74
106
|
|
|
75
|
-
measureHtml(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* is null, the text will be measured without wrapping, but explicit line breaks and
|
|
86
|
-
* space are preserved.
|
|
87
|
-
*/
|
|
88
|
-
maxWidth: null | number
|
|
89
|
-
minWidth?: null | number
|
|
90
|
-
otherStyles?: Record<string, string>
|
|
91
|
-
padding: string
|
|
92
|
-
disableOverflowWrapBreaking?: boolean
|
|
107
|
+
measureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
108
|
+
const { elm } = this
|
|
109
|
+
|
|
110
|
+
if (opts.otherStyles) {
|
|
111
|
+
for (const key in opts.otherStyles) {
|
|
112
|
+
if (!this.defaultStyles[key]) {
|
|
113
|
+
// we need to save the original style so that we can restore it when we're done
|
|
114
|
+
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
93
117
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
|
|
119
|
+
elm.innerHTML = html
|
|
120
|
+
|
|
121
|
+
// Apply the default styles to the element (for all styles here or that were ever seen in opts.otherStyles)
|
|
122
|
+
this.resetElmStyles()
|
|
123
|
+
|
|
124
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
125
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
126
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
127
|
+
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
128
|
+
elm.style.setProperty('line-height', opts.lineHeight.toString())
|
|
129
|
+
elm.style.setProperty('padding', opts.padding)
|
|
130
|
+
|
|
131
|
+
if (opts.maxWidth) {
|
|
132
|
+
elm.style.setProperty('max-width', opts.maxWidth + 'px')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (opts.minWidth) {
|
|
136
|
+
elm.style.setProperty('min-width', opts.minWidth + 'px')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (opts.disableOverflowWrapBreaking) {
|
|
140
|
+
elm.style.setProperty('overflow-wrap', 'normal')
|
|
141
|
+
}
|
|
142
|
+
|
|
117
143
|
if (opts.otherStyles) {
|
|
118
144
|
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
119
|
-
|
|
145
|
+
elm.style.setProperty(key, value)
|
|
120
146
|
}
|
|
121
147
|
}
|
|
122
148
|
|
|
123
|
-
const scrollWidth =
|
|
124
|
-
const rect =
|
|
125
|
-
wrapperElm.remove()
|
|
149
|
+
const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
|
|
150
|
+
const rect = elm.getBoundingClientRect()
|
|
126
151
|
|
|
127
152
|
return {
|
|
128
153
|
x: 0,
|
|
@@ -247,27 +272,29 @@ export class TextManager {
|
|
|
247
272
|
): { text: string; box: BoxModel }[] {
|
|
248
273
|
if (textToMeasure === '') return []
|
|
249
274
|
|
|
250
|
-
const elm = this
|
|
251
|
-
|
|
275
|
+
const { elm } = this
|
|
276
|
+
|
|
277
|
+
if (opts.otherStyles) {
|
|
278
|
+
for (const key in opts.otherStyles) {
|
|
279
|
+
if (!this.defaultStyles[key]) {
|
|
280
|
+
// we need to save the original style so that we can restore it when we're done
|
|
281
|
+
this.defaultStyles[key] = elm.style.getPropertyValue(key)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.resetElmStyles()
|
|
287
|
+
|
|
288
|
+
elm.style.setProperty('font-family', opts.fontFamily)
|
|
289
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
290
|
+
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
291
|
+
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
292
|
+
elm.style.setProperty('line-height', opts.lineHeight.toString())
|
|
252
293
|
|
|
253
294
|
const elementWidth = Math.ceil(opts.width - opts.padding * 2)
|
|
254
|
-
elm.setAttribute('dir', 'auto')
|
|
255
|
-
// N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers")
|
|
256
|
-
// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.
|
|
257
|
-
elm.style.setProperty('unicode-bidi', 'plaintext')
|
|
258
295
|
elm.style.setProperty('width', `${elementWidth}px`)
|
|
259
296
|
elm.style.setProperty('height', 'min-content')
|
|
260
|
-
elm.style.setProperty('font-size', `${opts.fontSize}px`)
|
|
261
|
-
elm.style.setProperty('font-family', opts.fontFamily)
|
|
262
|
-
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
263
|
-
elm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)
|
|
264
297
|
elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
|
|
265
|
-
elm.style.setProperty('font-style', opts.fontStyle)
|
|
266
|
-
if (opts.otherStyles) {
|
|
267
|
-
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
268
|
-
elm.style.setProperty(key, value)
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
298
|
|
|
272
299
|
const shouldTruncateToFirstLine =
|
|
273
300
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
@@ -277,6 +304,12 @@ export class TextManager {
|
|
|
277
304
|
elm.style.setProperty('word-break', 'break-all')
|
|
278
305
|
}
|
|
279
306
|
|
|
307
|
+
if (opts.otherStyles) {
|
|
308
|
+
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
309
|
+
elm.style.setProperty(key, value)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
280
313
|
const normalizedText = normalizeTextForDom(textToMeasure)
|
|
281
314
|
|
|
282
315
|
// Render the text into the measurement element:
|
|
@@ -313,11 +346,10 @@ export class TextManager {
|
|
|
313
346
|
h: lastSpan.box.h,
|
|
314
347
|
},
|
|
315
348
|
})
|
|
349
|
+
|
|
316
350
|
return truncatedSpans
|
|
317
351
|
}
|
|
318
352
|
|
|
319
|
-
elm.remove()
|
|
320
|
-
|
|
321
353
|
return spans
|
|
322
354
|
}
|
|
323
355
|
}
|
|
@@ -206,15 +206,15 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// todo: move this logic into transition
|
|
209
|
-
exit(info: any,
|
|
209
|
+
exit(info: any, to: string) {
|
|
210
210
|
if (debugFlags.measurePerformance.get() && this.performanceTracker.isStarted()) {
|
|
211
211
|
this.performanceTracker.stop()
|
|
212
212
|
}
|
|
213
213
|
this._isActive.set(false)
|
|
214
|
-
this.onExit?.(info,
|
|
214
|
+
this.onExit?.(info, to)
|
|
215
215
|
|
|
216
216
|
if (!this.getIsActive()) {
|
|
217
|
-
this.getCurrent()?.exit(info,
|
|
217
|
+
this.getCurrent()?.exit(info, to)
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TLAssetId } from '@tldraw/tlschema'
|
|
1
|
+
import { TLAssetId, TLShapeId } from '@tldraw/tlschema'
|
|
2
2
|
import { VecLike } from '../../primitives/Vec'
|
|
3
3
|
import { TLContent } from './clipboard-types'
|
|
4
4
|
|
|
@@ -52,7 +52,15 @@ export interface TLTextExternalContent extends TLBaseExternalContent {
|
|
|
52
52
|
export interface TLFilesExternalContent extends TLBaseExternalContent {
|
|
53
53
|
type: 'files'
|
|
54
54
|
files: File[]
|
|
55
|
-
ignoreParent
|
|
55
|
+
ignoreParent?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @public */
|
|
59
|
+
export interface TLFileReplaceExternalContent extends TLBaseExternalContent {
|
|
60
|
+
type: 'file-replace'
|
|
61
|
+
file: File
|
|
62
|
+
shapeId: TLShapeId
|
|
63
|
+
isImage: boolean
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
/** @public */
|
|
@@ -90,6 +98,7 @@ export interface TLExcalidrawExternalContent extends TLBaseExternalContent {
|
|
|
90
98
|
export type TLExternalContent<EmbedDefinition> =
|
|
91
99
|
| TLTextExternalContent
|
|
92
100
|
| TLFilesExternalContent
|
|
101
|
+
| TLFileReplaceExternalContent
|
|
93
102
|
| TLUrlExternalContent
|
|
94
103
|
| TLSvgTextExternalContent
|
|
95
104
|
| TLEmbedExternalContent<EmbedDefinition>
|
|
@@ -591,14 +591,6 @@ export class Box {
|
|
|
591
591
|
return b.x === a.x && b.y === a.y && b.w === a.w && b.h === a.h
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
-
prettyMuchEquals(other: Box | BoxModel) {
|
|
595
|
-
return this.clone().toFixed().equals(Box.From(other).toFixed())
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
static PrettyMuchEquals(a: Box | BoxModel, b: Box | BoxModel) {
|
|
599
|
-
return b.x === a.x && b.y === a.y && b.w === a.w && b.h === a.h
|
|
600
|
-
}
|
|
601
|
-
|
|
602
594
|
zeroFix() {
|
|
603
595
|
this.w = Math.max(1, this.w)
|
|
604
596
|
this.h = Math.max(1, this.h)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getSchema, JSONContent, Editor as TTEditor } from '@tiptap/core'
|
|
2
|
-
import { Node } from '@tiptap/pm/model'
|
|
2
|
+
import { Node, Schema } from '@tiptap/pm/model'
|
|
3
3
|
import { EditorProviderProps } from '@tiptap/react'
|
|
4
4
|
import { TLRichText } from '@tldraw/tlschema'
|
|
5
|
-
import { assert } from '@tldraw/utils'
|
|
5
|
+
import { assert, WeakCache } from '@tldraw/utils'
|
|
6
6
|
import { Editor } from '../editor/Editor'
|
|
7
7
|
import { TLFontFace } from '../editor/managers/FontManager/FontManager'
|
|
8
8
|
|
|
@@ -39,6 +39,11 @@ export type RichTextFontVisitor = (
|
|
|
39
39
|
addFont: (font: TLFontFace) => void
|
|
40
40
|
) => RichTextFontVisitorState
|
|
41
41
|
|
|
42
|
+
const schemaCache = new WeakCache<EditorProviderProps, Schema>()
|
|
43
|
+
export function getTipTapSchema(tipTapConfig: EditorProviderProps) {
|
|
44
|
+
return schemaCache.get(tipTapConfig, () => getSchema(tipTapConfig.extensions ?? []))
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
/** @public */
|
|
43
48
|
export function getFontsFromRichText(
|
|
44
49
|
editor: Editor,
|
|
@@ -49,7 +54,8 @@ export function getFontsFromRichText(
|
|
|
49
54
|
assert(tipTapConfig, 'textOptions.tipTapConfig must be set to use rich text')
|
|
50
55
|
assert(addFontsFromNode, 'textOptions.addFontsFromNode must be set to use rich text')
|
|
51
56
|
|
|
52
|
-
const schema =
|
|
57
|
+
const schema = getTipTapSchema(tipTapConfig)
|
|
58
|
+
|
|
53
59
|
const rootNode = Node.fromJSON(schema, richText as JSONContent)
|
|
54
60
|
|
|
55
61
|
const fonts = new Set<TLFontFace>()
|
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.14.0-canary.
|
|
4
|
+
export const version = '3.14.0-canary.d1b8a584b27c'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-06-
|
|
8
|
-
patch: '2025-06-
|
|
7
|
+
minor: '2025-06-23T14:58:35.827Z',
|
|
8
|
+
patch: '2025-06-23T14:58:35.827Z',
|
|
9
9
|
}
|