@tldraw/editor 3.14.0-canary.744f8a453221 → 3.14.0-canary.766a02a82239
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 +50 -149
- package/dist-cjs/index.js +1 -4
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +25 -82
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +1 -3
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +42 -73
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +10 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +6 -13
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
- 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/emit-types.js.map +1 -1
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -1
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +2 -6
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +6 -11
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +1 -1
- package/dist-cjs/lib/utils/dom.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 +50 -149
- package/dist-esm/index.mjs +1 -4
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +25 -82
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +1 -3
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +42 -73
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +10 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +6 -13
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
- 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 +2 -1
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +2 -6
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +6 -11
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +1 -1
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +483 -440
- package/package.json +7 -7
- package/src/index.ts +0 -7
- package/src/lib/editor/Editor.ts +36 -103
- package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +1 -3
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +5 -1
- package/src/lib/editor/managers/TextManager/TextManager.ts +86 -118
- package/src/lib/editor/shapes/ShapeUtil.ts +15 -47
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +17 -25
- package/src/lib/editor/tools/StateNode.ts +3 -3
- package/src/lib/editor/types/emit-types.ts +0 -4
- package/src/lib/editor/types/external-content.ts +2 -11
- package/src/lib/hooks/useCanvasEvents.ts +1 -0
- package/src/lib/primitives/geometry/Geometry2d.ts +2 -7
- package/src/lib/primitives/geometry/Group2d.ts +5 -11
- package/src/lib/utils/dom.ts +1 -1
- package/src/version.ts +3 -3
- package/dist-cjs/lib/utils/reparenting.js +0 -232
- package/dist-cjs/lib/utils/reparenting.js.map +0 -7
- package/dist-esm/lib/utils/reparenting.mjs +0 -216
- package/dist-esm/lib/utils/reparenting.mjs.map +0 -7
- package/src/lib/utils/reparenting.ts +0 -383
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.766a02a82239",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -48,12 +48,12 @@
|
|
|
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.766a02a82239",
|
|
52
|
+
"@tldraw/state-react": "3.14.0-canary.766a02a82239",
|
|
53
|
+
"@tldraw/store": "3.14.0-canary.766a02a82239",
|
|
54
|
+
"@tldraw/tlschema": "3.14.0-canary.766a02a82239",
|
|
55
|
+
"@tldraw/utils": "3.14.0-canary.766a02a82239",
|
|
56
|
+
"@tldraw/validate": "3.14.0-canary.766a02a82239",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
package/src/index.ts
CHANGED
|
@@ -174,7 +174,6 @@ export {
|
|
|
174
174
|
} from './lib/editor/managers/SnapManager/SnapManager'
|
|
175
175
|
export {
|
|
176
176
|
TextManager,
|
|
177
|
-
type TLMeasureTextOpts,
|
|
178
177
|
type TLMeasureTextSpanOpts,
|
|
179
178
|
} from './lib/editor/managers/TextManager/TextManager'
|
|
180
179
|
export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
|
|
@@ -182,10 +181,6 @@ export { BaseBoxShapeUtil, type TLBaseBoxShape } from './lib/editor/shapes/BaseB
|
|
|
182
181
|
export {
|
|
183
182
|
ShapeUtil,
|
|
184
183
|
type TLCropInfo,
|
|
185
|
-
type TLDragShapesInInfo,
|
|
186
|
-
type TLDragShapesOutInfo,
|
|
187
|
-
type TLDragShapesOverInfo,
|
|
188
|
-
type TLDropShapesOverInfo,
|
|
189
184
|
type TLGeometryOpts,
|
|
190
185
|
type TLHandleDragInfo,
|
|
191
186
|
type TLResizeInfo,
|
|
@@ -257,7 +252,6 @@ export {
|
|
|
257
252
|
type TLExternalContent,
|
|
258
253
|
type TLExternalContentSource,
|
|
259
254
|
type TLFileExternalAsset,
|
|
260
|
-
type TLFileReplaceExternalContent,
|
|
261
255
|
type TLFilesExternalContent,
|
|
262
256
|
type TLSvgTextExternalContent,
|
|
263
257
|
type TLTextExternalContent,
|
|
@@ -450,7 +444,6 @@ export { hardResetEditor } from './lib/utils/hardResetEditor'
|
|
|
450
444
|
export { isAccelKey } from './lib/utils/keyboard'
|
|
451
445
|
export { normalizeWheel } from './lib/utils/normalizeWheel'
|
|
452
446
|
export { refreshPage } from './lib/utils/refreshPage'
|
|
453
|
-
export { getDroppedShapesToNewParents, kickoutOccludedShapes } from './lib/utils/reparenting'
|
|
454
447
|
export {
|
|
455
448
|
getFontsFromRichText,
|
|
456
449
|
type RichTextFontVisitor,
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -348,8 +348,6 @@ 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
|
-
|
|
353
351
|
this.fonts = new FontManager(this, fontAssetUrls)
|
|
354
352
|
|
|
355
353
|
this._tickManager = new TickManager(this)
|
|
@@ -2122,20 +2120,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2122
2120
|
return this.getShapesPageBounds(this.getSelectedShapeIds())
|
|
2123
2121
|
}
|
|
2124
2122
|
|
|
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
|
-
|
|
2139
2123
|
/**
|
|
2140
2124
|
* @internal
|
|
2141
2125
|
*/
|
|
@@ -3662,7 +3646,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3662
3646
|
* @public
|
|
3663
3647
|
*/
|
|
3664
3648
|
updateViewportScreenBounds(screenBounds: Box | HTMLElement, center = false): this {
|
|
3665
|
-
if (
|
|
3649
|
+
if (screenBounds instanceof HTMLElement) {
|
|
3666
3650
|
const rect = screenBounds.getBoundingClientRect()
|
|
3667
3651
|
screenBounds = new Box(
|
|
3668
3652
|
rect.left || rect.x,
|
|
@@ -5528,7 +5512,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5528
5512
|
if (!id) return undefined
|
|
5529
5513
|
const freshShape = this.getShape(id)
|
|
5530
5514
|
if (freshShape === undefined || !isShapeId(freshShape.parentId)) return undefined
|
|
5531
|
-
return this.
|
|
5515
|
+
return this.store.get(freshShape.parentId)
|
|
5532
5516
|
}
|
|
5533
5517
|
|
|
5534
5518
|
/**
|
|
@@ -5711,10 +5695,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5711
5695
|
const newPoint = invertedParentTransform.applyToPoint(pagePoint)
|
|
5712
5696
|
const newRotation = pageTransform.rotation() - parentPageRotation
|
|
5713
5697
|
|
|
5714
|
-
if (shape.id === parentId) {
|
|
5715
|
-
throw Error('Attempted to reparent a shape to itself!')
|
|
5716
|
-
}
|
|
5717
|
-
|
|
5718
5698
|
changes.push({
|
|
5719
5699
|
id: shape.id,
|
|
5720
5700
|
type: shape.type,
|
|
@@ -5818,11 +5798,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5818
5798
|
return shapeIds
|
|
5819
5799
|
}
|
|
5820
5800
|
|
|
5821
|
-
/** @deprecated Use {@link Editor.getDraggingOverShape} instead */
|
|
5822
|
-
getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
|
|
5823
|
-
return this.getDraggingOverShape(point, droppingShapes)
|
|
5824
|
-
}
|
|
5825
|
-
|
|
5826
5801
|
/**
|
|
5827
5802
|
* Get the shape that some shapes should be dropped on at a given point.
|
|
5828
5803
|
*
|
|
@@ -5833,33 +5808,35 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5833
5808
|
*
|
|
5834
5809
|
* @public
|
|
5835
5810
|
*/
|
|
5836
|
-
|
|
5837
|
-
//
|
|
5838
|
-
const
|
|
5839
|
-
|
|
5840
|
-
|
|
5811
|
+
getDroppingOverShape(point: VecLike, droppingShapes: TLShape[] = []) {
|
|
5812
|
+
// starting from the top...
|
|
5813
|
+
const currentPageShapesSorted = this.getCurrentPageShapesSorted()
|
|
5814
|
+
for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
|
|
5815
|
+
const shape = currentPageShapesSorted[i]
|
|
5841
5816
|
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
!
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5817
|
+
if (
|
|
5818
|
+
// ignore hidden shapes
|
|
5819
|
+
this.isShapeHidden(shape) ||
|
|
5820
|
+
// don't allow dropping on selected shapes
|
|
5821
|
+
this.getSelectedShapeIds().includes(shape.id) ||
|
|
5822
|
+
// only allow shapes that can receive children
|
|
5823
|
+
!this.getShapeUtil(shape).canDropShapes(shape, droppingShapes) ||
|
|
5824
|
+
// don't allow dropping a shape on itself or one of it's children
|
|
5825
|
+
droppingShapes.find((s) => s.id === shape.id || this.hasAncestor(shape, s.id))
|
|
5826
|
+
) {
|
|
5827
|
+
continue
|
|
5828
|
+
}
|
|
5829
|
+
|
|
5830
|
+
// Only allow dropping into the masked page bounds of the shape, e.g. when a frame is
|
|
5831
|
+
// partially clipped by its own parent frame
|
|
5832
|
+
const maskedPageBounds = this.getShapeMaskedPageBounds(shape.id)
|
|
5852
5833
|
|
|
5853
|
-
for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
|
|
5854
|
-
const shapeUtil = this.getShapeUtil(maybeDraggingOverShape)
|
|
5855
|
-
// Any shape that can handle any dragging interactions is a valid target
|
|
5856
5834
|
if (
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
shapeUtil.onDropShapesOver
|
|
5835
|
+
maskedPageBounds &&
|
|
5836
|
+
maskedPageBounds.containsPoint(point) &&
|
|
5837
|
+
this.getShapeGeometry(shape).hitTestPoint(this.getPointInShapeSpace(shape, point), 0, true)
|
|
5861
5838
|
) {
|
|
5862
|
-
return
|
|
5839
|
+
return shape
|
|
5863
5840
|
}
|
|
5864
5841
|
}
|
|
5865
5842
|
}
|
|
@@ -6218,12 +6195,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6218
6195
|
*/
|
|
6219
6196
|
duplicateShapes(shapes: TLShapeId[] | TLShape[], offset?: VecLike): this {
|
|
6220
6197
|
this.run(() => {
|
|
6221
|
-
const
|
|
6198
|
+
const ids =
|
|
6222
6199
|
typeof shapes[0] === 'string'
|
|
6223
6200
|
? (shapes as TLShapeId[])
|
|
6224
6201
|
: (shapes as TLShape[]).map((s) => s.id)
|
|
6225
6202
|
|
|
6226
|
-
const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids)
|
|
6227
6203
|
if (ids.length <= 0) return this
|
|
6228
6204
|
|
|
6229
6205
|
const initialIds = new Set(ids)
|
|
@@ -6303,7 +6279,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6303
6279
|
})
|
|
6304
6280
|
const shapesToCreate = shapesToCreateWithOriginals.map(({ shape }) => shape)
|
|
6305
6281
|
|
|
6306
|
-
|
|
6282
|
+
const maxShapesReached =
|
|
6283
|
+
shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage
|
|
6284
|
+
|
|
6285
|
+
if (maxShapesReached) {
|
|
6307
6286
|
alertMaxShapes(this)
|
|
6308
6287
|
return
|
|
6309
6288
|
}
|
|
@@ -7732,32 +7711,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7732
7711
|
return {}
|
|
7733
7712
|
}
|
|
7734
7713
|
|
|
7735
|
-
/**
|
|
7736
|
-
* Get whether the provided shape can be created.
|
|
7737
|
-
*
|
|
7738
|
-
* @param shape - The shape or shape IDs to check.
|
|
7739
|
-
*
|
|
7740
|
-
* @public
|
|
7741
|
-
*/
|
|
7742
|
-
canCreateShape<T extends TLUnknownShape>(
|
|
7743
|
-
shape: OptionalKeys<TLShapePartial<T>, 'id'> | T['id']
|
|
7744
|
-
): boolean {
|
|
7745
|
-
return this.canCreateShapes([shape])
|
|
7746
|
-
}
|
|
7747
|
-
|
|
7748
|
-
/**
|
|
7749
|
-
* Get whether the provided shapes can be created.
|
|
7750
|
-
*
|
|
7751
|
-
* @param shapes - The shapes or shape IDs to create.
|
|
7752
|
-
*
|
|
7753
|
-
* @public
|
|
7754
|
-
*/
|
|
7755
|
-
canCreateShapes<T extends TLUnknownShape>(
|
|
7756
|
-
shapes: (T['id'] | OptionalKeys<TLShapePartial<T>, 'id'>)[]
|
|
7757
|
-
): boolean {
|
|
7758
|
-
return shapes.length + this.getCurrentPageShapeIds().size <= this.options.maxShapesPerPage
|
|
7759
|
-
}
|
|
7760
|
-
|
|
7761
7714
|
/**
|
|
7762
7715
|
* Create a single shape.
|
|
7763
7716
|
*
|
|
@@ -7804,7 +7757,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7804
7757
|
if (maxShapesReached) {
|
|
7805
7758
|
// can't create more shapes than fit on the page
|
|
7806
7759
|
alertMaxShapes(this)
|
|
7807
|
-
// todo: throw an error here? Otherwise we'll need to check every time whether the shapes were actually created
|
|
7808
7760
|
return this
|
|
7809
7761
|
}
|
|
7810
7762
|
|
|
@@ -7837,10 +7789,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7837
7789
|
|
|
7838
7790
|
for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
|
|
7839
7791
|
const parent = currentPageShapesSorted[i]
|
|
7840
|
-
const util = this.getShapeUtil(parent)
|
|
7841
7792
|
if (
|
|
7842
|
-
util.canReceiveNewChildrenOfType(parent, partial.type) &&
|
|
7843
7793
|
!this.isShapeHidden(parent) &&
|
|
7794
|
+
this.getShapeUtil(parent).canReceiveNewChildrenOfType(parent, partial.type) &&
|
|
7844
7795
|
this.isPointInShape(
|
|
7845
7796
|
parent,
|
|
7846
7797
|
// If no parent is provided, then we can treat the
|
|
@@ -7859,7 +7810,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7859
7810
|
|
|
7860
7811
|
const prevParentId = partial.parentId
|
|
7861
7812
|
|
|
7862
|
-
// a shape cannot be
|
|
7813
|
+
// a shape cannot be it's own parent. This was a rare issue with frames/groups in the syncFuzz tests.
|
|
7863
7814
|
if (parentId === partial.id) {
|
|
7864
7815
|
parentId = focusedGroupId
|
|
7865
7816
|
}
|
|
@@ -7969,8 +7920,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7969
7920
|
}
|
|
7970
7921
|
})
|
|
7971
7922
|
|
|
7972
|
-
this.emit('created-shapes', shapeRecordsToCreate)
|
|
7973
|
-
this.emit('edit')
|
|
7974
7923
|
this.store.put(shapeRecordsToCreate)
|
|
7975
7924
|
})
|
|
7976
7925
|
|
|
@@ -8365,8 +8314,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8365
8314
|
updates.push(updated)
|
|
8366
8315
|
}
|
|
8367
8316
|
|
|
8368
|
-
this.emit('edited-shapes', updates)
|
|
8369
|
-
this.emit('edit')
|
|
8370
8317
|
this.store.put(updates)
|
|
8371
8318
|
})
|
|
8372
8319
|
}
|
|
@@ -8416,8 +8363,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8416
8363
|
})
|
|
8417
8364
|
}
|
|
8418
8365
|
|
|
8419
|
-
this.emit('deleted-shapes', [...allShapeIdsToDelete])
|
|
8420
|
-
this.emit('edit')
|
|
8421
8366
|
return this.run(() => this.store.remove([...allShapeIdsToDelete]))
|
|
8422
8367
|
}
|
|
8423
8368
|
|
|
@@ -8866,7 +8811,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8866
8811
|
} = {
|
|
8867
8812
|
text: null,
|
|
8868
8813
|
files: null,
|
|
8869
|
-
'file-replace': null,
|
|
8870
8814
|
embed: null,
|
|
8871
8815
|
'svg-text': null,
|
|
8872
8816
|
url: null,
|
|
@@ -8916,15 +8860,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8916
8860
|
return this.externalContentHandlers[info.type]?.(info as any)
|
|
8917
8861
|
}
|
|
8918
8862
|
|
|
8919
|
-
/**
|
|
8920
|
-
* Handle replacing external content.
|
|
8921
|
-
*
|
|
8922
|
-
* @param info - Info about the external content.
|
|
8923
|
-
*/
|
|
8924
|
-
async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
|
|
8925
|
-
return this.externalContentHandlers[info.type]?.(info as any)
|
|
8926
|
-
}
|
|
8927
|
-
|
|
8928
8863
|
/**
|
|
8929
8864
|
* Get content that can be exported for the given shape ids.
|
|
8930
8865
|
*
|
|
@@ -9544,8 +9479,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9544
9479
|
previousPagePoint,
|
|
9545
9480
|
currentScreenPoint,
|
|
9546
9481
|
currentPagePoint,
|
|
9547
|
-
originScreenPoint,
|
|
9548
|
-
originPagePoint,
|
|
9549
9482
|
} = this.inputs
|
|
9550
9483
|
|
|
9551
9484
|
const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
@@ -9574,8 +9507,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9574
9507
|
// Reset velocity on pointer down, or when a pinch starts or ends
|
|
9575
9508
|
if (info.name === 'pointer_down' || this.inputs.isPinching) {
|
|
9576
9509
|
pointerVelocity.set(0, 0)
|
|
9577
|
-
originScreenPoint.setTo(currentScreenPoint)
|
|
9578
|
-
originPagePoint.setTo(currentPagePoint)
|
|
9510
|
+
this.inputs.originScreenPoint.setTo(currentScreenPoint)
|
|
9511
|
+
this.inputs.originPagePoint.setTo(currentPagePoint)
|
|
9579
9512
|
}
|
|
9580
9513
|
|
|
9581
9514
|
// todo: We only have to do this if there are multiple users in the document
|
|
@@ -241,9 +241,7 @@ export class HistoryManager<R extends UnknownRecord> {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
bailToMark(id: string) {
|
|
244
|
-
|
|
245
|
-
this._undo({ pushToRedoStack: false, toMark: id })
|
|
246
|
-
}
|
|
244
|
+
this._undo({ pushToRedoStack: false, toMark: id })
|
|
247
245
|
|
|
248
246
|
return this
|
|
249
247
|
}
|
|
@@ -99,7 +99,7 @@ describe('TextManager', () => {
|
|
|
99
99
|
})
|
|
100
100
|
|
|
101
101
|
it('should handle empty text', () => {
|
|
102
|
-
const result = textManager.measureText('',
|
|
102
|
+
const result = textManager.measureText('', defaultOpts)
|
|
103
103
|
expect(result).toHaveProperty('x', 0)
|
|
104
104
|
expect(result).toHaveProperty('y', 0)
|
|
105
105
|
expect(result).toHaveProperty('w')
|
|
@@ -128,6 +128,7 @@ describe('TextManager', () => {
|
|
|
128
128
|
y: 0,
|
|
129
129
|
w: expect.any(Number),
|
|
130
130
|
h: expect.any(Number),
|
|
131
|
+
scrollWidth: expect.any(Number),
|
|
131
132
|
})
|
|
132
133
|
})
|
|
133
134
|
|
|
@@ -140,6 +141,7 @@ describe('TextManager', () => {
|
|
|
140
141
|
y: 0,
|
|
141
142
|
w: expect.any(Number),
|
|
142
143
|
h: expect.any(Number),
|
|
144
|
+
scrollWidth: expect.any(Number),
|
|
143
145
|
})
|
|
144
146
|
})
|
|
145
147
|
|
|
@@ -152,6 +154,7 @@ describe('TextManager', () => {
|
|
|
152
154
|
y: 0,
|
|
153
155
|
w: expect.any(Number),
|
|
154
156
|
h: expect.any(Number),
|
|
157
|
+
scrollWidth: expect.any(Number),
|
|
155
158
|
})
|
|
156
159
|
})
|
|
157
160
|
|
|
@@ -170,6 +173,7 @@ describe('TextManager', () => {
|
|
|
170
173
|
y: 0,
|
|
171
174
|
w: expect.any(Number),
|
|
172
175
|
h: expect.any(Number),
|
|
176
|
+
scrollWidth: expect.any(Number),
|
|
173
177
|
})
|
|
174
178
|
})
|
|
175
179
|
})
|
|
@@ -20,28 +20,6 @@ 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
|
-
|
|
45
23
|
/** @public */
|
|
46
24
|
export interface TLMeasureTextSpanOpts {
|
|
47
25
|
overflow: 'wrap' | 'truncate-ellipsis' | 'truncate-clip'
|
|
@@ -55,99 +33,96 @@ export interface TLMeasureTextSpanOpts {
|
|
|
55
33
|
lineHeight: number
|
|
56
34
|
textAlign: TLDefaultHorizontalAlignStyle
|
|
57
35
|
otherStyles?: Record<string, string>
|
|
58
|
-
measureScrollWidth?: boolean
|
|
59
36
|
}
|
|
60
37
|
|
|
61
38
|
const spaceCharacterRegex = /\s/
|
|
62
39
|
|
|
63
40
|
/** @public */
|
|
64
41
|
export class TextManager {
|
|
65
|
-
private
|
|
66
|
-
private defaultStyles: Record<string, string | null>
|
|
42
|
+
private baseElem: HTMLDivElement
|
|
67
43
|
|
|
68
44
|
constructor(public editor: Editor) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
dispose() {
|
|
91
|
-
return this.elm.remove()
|
|
45
|
+
this.baseElem = document.createElement('div')
|
|
46
|
+
this.baseElem.classList.add('tl-text')
|
|
47
|
+
this.baseElem.classList.add('tl-text-measure')
|
|
48
|
+
this.baseElem.tabIndex = -1
|
|
92
49
|
}
|
|
93
50
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
51
|
+
measureText(
|
|
52
|
+
textToMeasure: string,
|
|
53
|
+
opts: {
|
|
54
|
+
fontStyle: string
|
|
55
|
+
fontWeight: string
|
|
56
|
+
fontFamily: string
|
|
57
|
+
fontSize: number
|
|
58
|
+
lineHeight: number
|
|
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
|
|
98
68
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
69
|
+
): BoxModel & { scrollWidth: number } {
|
|
102
70
|
const div = document.createElement('div')
|
|
103
71
|
div.textContent = normalizeTextForDom(textToMeasure)
|
|
104
72
|
return this.measureHtml(div.innerHTML, opts)
|
|
105
73
|
}
|
|
106
74
|
|
|
107
|
-
measureHtml(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
75
|
+
measureHtml(
|
|
76
|
+
html: string,
|
|
77
|
+
opts: {
|
|
78
|
+
fontStyle: string
|
|
79
|
+
fontWeight: string
|
|
80
|
+
fontFamily: string
|
|
81
|
+
fontSize: number
|
|
82
|
+
lineHeight: number
|
|
83
|
+
/**
|
|
84
|
+
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
|
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
|
|
117
93
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
94
|
+
): BoxModel & { scrollWidth: number } {
|
|
95
|
+
// Duplicate our base element; we don't need to clone deep
|
|
96
|
+
const wrapperElm = this.baseElem.cloneNode() as HTMLDivElement
|
|
97
|
+
this.editor.getContainer().appendChild(wrapperElm)
|
|
98
|
+
wrapperElm.innerHTML = html
|
|
99
|
+
this.baseElem.insertAdjacentElement('afterend', wrapperElm)
|
|
100
|
+
|
|
101
|
+
wrapperElm.setAttribute('dir', 'auto')
|
|
102
|
+
// N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers")
|
|
103
|
+
// is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs.
|
|
104
|
+
wrapperElm.style.setProperty('unicode-bidi', 'plaintext')
|
|
105
|
+
wrapperElm.style.setProperty('font-family', opts.fontFamily)
|
|
106
|
+
wrapperElm.style.setProperty('font-style', opts.fontStyle)
|
|
107
|
+
wrapperElm.style.setProperty('font-weight', opts.fontWeight)
|
|
108
|
+
wrapperElm.style.setProperty('font-size', opts.fontSize + 'px')
|
|
109
|
+
wrapperElm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
|
|
110
|
+
wrapperElm.style.setProperty('max-width', opts.maxWidth === null ? null : opts.maxWidth + 'px')
|
|
111
|
+
wrapperElm.style.setProperty('min-width', opts.minWidth === null ? null : opts.minWidth + 'px')
|
|
112
|
+
wrapperElm.style.setProperty('padding', opts.padding)
|
|
113
|
+
wrapperElm.style.setProperty(
|
|
114
|
+
'overflow-wrap',
|
|
115
|
+
opts.disableOverflowWrapBreaking ? 'normal' : 'break-word'
|
|
116
|
+
)
|
|
143
117
|
if (opts.otherStyles) {
|
|
144
118
|
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
145
|
-
|
|
119
|
+
wrapperElm.style.setProperty(key, value)
|
|
146
120
|
}
|
|
147
121
|
}
|
|
148
122
|
|
|
149
|
-
const scrollWidth =
|
|
150
|
-
const rect =
|
|
123
|
+
const scrollWidth = wrapperElm.scrollWidth
|
|
124
|
+
const rect = wrapperElm.getBoundingClientRect()
|
|
125
|
+
wrapperElm.remove()
|
|
151
126
|
|
|
152
127
|
return {
|
|
153
128
|
x: 0,
|
|
@@ -272,29 +247,27 @@ export class TextManager {
|
|
|
272
247
|
): { text: string; box: BoxModel }[] {
|
|
273
248
|
if (textToMeasure === '') return []
|
|
274
249
|
|
|
275
|
-
const
|
|
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())
|
|
250
|
+
const elm = this.baseElem.cloneNode() as HTMLDivElement
|
|
251
|
+
this.editor.getContainer().appendChild(elm)
|
|
293
252
|
|
|
294
253
|
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')
|
|
295
258
|
elm.style.setProperty('width', `${elementWidth}px`)
|
|
296
259
|
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`)
|
|
297
264
|
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
|
+
}
|
|
298
271
|
|
|
299
272
|
const shouldTruncateToFirstLine =
|
|
300
273
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
@@ -304,12 +277,6 @@ export class TextManager {
|
|
|
304
277
|
elm.style.setProperty('word-break', 'break-all')
|
|
305
278
|
}
|
|
306
279
|
|
|
307
|
-
if (opts.otherStyles) {
|
|
308
|
-
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
309
|
-
elm.style.setProperty(key, value)
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
280
|
const normalizedText = normalizeTextForDom(textToMeasure)
|
|
314
281
|
|
|
315
282
|
// Render the text into the measurement element:
|
|
@@ -346,10 +313,11 @@ export class TextManager {
|
|
|
346
313
|
h: lastSpan.box.h,
|
|
347
314
|
},
|
|
348
315
|
})
|
|
349
|
-
|
|
350
316
|
return truncatedSpans
|
|
351
317
|
}
|
|
352
318
|
|
|
319
|
+
elm.remove()
|
|
320
|
+
|
|
353
321
|
return spans
|
|
354
322
|
}
|
|
355
323
|
}
|