@tldraw/editor 3.15.0-next.f1dfcef63951 → 3.16.0-next.c30b1b5e551a
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 +159 -44
- package/dist-cjs/index.js +20 -16
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/SVGContainer.js +1 -1
- package/dist-cjs/lib/components/SVGContainer.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +4 -26
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultBrush.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultBrush.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCursor.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCursor.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultGrid.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultGrid.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultHandles.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultHandles.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js +53 -0
- package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js.map +7 -0
- package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js +27 -15
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +3 -3
- package/dist-cjs/lib/config/TLUserPreferences.js +7 -1
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +88 -43
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +96 -101
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +7 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +20 -1
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/hooks/useEditorComponents.js +2 -0
- package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +2 -2
- package/dist-cjs/lib/license/Watermark.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/Circle2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +3 -1
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/geometry-constants.js +2 -2
- package/dist-cjs/lib/primitives/geometry/geometry-constants.js.map +2 -2
- package/dist-cjs/lib/primitives/intersect.js +4 -4
- package/dist-cjs/lib/primitives/intersect.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +4 -0
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +0 -1
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.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 +159 -44
- package/dist-esm/index.mjs +47 -41
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/SVGContainer.mjs +1 -1
- package/dist-esm/lib/components/SVGContainer.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +4 -26
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultBrush.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultBrush.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCursor.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCursor.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultGrid.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultGrid.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultHandles.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultHandles.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs +23 -0
- package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs.map +7 -0
- package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +17 -15
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +7 -1
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +88 -43
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +96 -101
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +7 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +20 -1
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEditorComponents.mjs +4 -0
- package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +2 -2
- package/dist-esm/lib/license/Watermark.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 +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +3 -1
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/geometry-constants.mjs +2 -2
- package/dist-esm/lib/primitives/geometry/geometry-constants.mjs.map +2 -2
- package/dist-esm/lib/primitives/intersect.mjs +5 -5
- package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +4 -0
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +0 -1
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +21 -27
- package/package.json +9 -8
- package/src/index.ts +68 -62
- package/src/lib/components/SVGContainer.tsx +1 -1
- package/src/lib/components/Shape.tsx +6 -21
- package/src/lib/components/default-components/DefaultBrush.tsx +1 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
- package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
- package/src/lib/components/default-components/DefaultCursor.tsx +1 -1
- package/src/lib/components/default-components/DefaultGrid.tsx +1 -1
- package/src/lib/components/default-components/DefaultHandles.tsx +5 -1
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +1 -1
- package/src/lib/components/default-components/DefaultShapeWrapper.tsx +35 -0
- package/src/lib/components/default-components/DefaultSnapIndictor.tsx +1 -1
- package/src/lib/components/default-components/DefaultSpinner.tsx +12 -12
- package/src/lib/config/TLUserPreferences.ts +7 -0
- package/src/lib/editor/Editor.test.ts +407 -0
- package/src/lib/editor/Editor.ts +106 -44
- package/src/lib/editor/managers/TextManager/TextManager.ts +108 -128
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +21 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +8 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +57 -0
- package/src/lib/editor/tools/StateNode.test.ts +285 -0
- package/src/lib/editor/tools/StateNode.ts +27 -1
- package/src/lib/editor/types/misc-types.ts +19 -0
- package/src/lib/hooks/useEditorComponents.tsx +8 -2
- package/src/lib/license/LicenseManager.test.ts +1 -1
- package/src/lib/license/Watermark.tsx +2 -2
- package/src/lib/primitives/geometry/Arc2d.ts +2 -2
- package/src/lib/primitives/geometry/Circle2d.ts +2 -2
- package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -1
- package/src/lib/primitives/geometry/Ellipse2d.ts +2 -2
- package/src/lib/primitives/geometry/geometry-constants.ts +2 -1
- package/src/lib/primitives/intersect.test.ts +946 -0
- package/src/lib/primitives/intersect.ts +12 -5
- package/src/lib/primitives/utils.ts +11 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +0 -1
- package/src/version.ts +3 -3
- package/src/lib/test/currentToolIdMask.test.ts +0 -49
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -178,6 +178,7 @@ import {
|
|
|
178
178
|
TLCameraOptions,
|
|
179
179
|
TLImageExportOptions,
|
|
180
180
|
TLSvgExportOptions,
|
|
181
|
+
TLUpdatePointerOptions,
|
|
181
182
|
} from './types/misc-types'
|
|
182
183
|
import { TLAdjacentDirection, TLResizeHandle } from './types/selection-types'
|
|
183
184
|
|
|
@@ -1803,7 +1804,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1803
1804
|
}
|
|
1804
1805
|
|
|
1805
1806
|
/**
|
|
1806
|
-
* Select all
|
|
1807
|
+
* Select all shapes. If the user has selected shapes that share a parent,
|
|
1808
|
+
* select all shapes within that parent. If the user has not selected any shapes,
|
|
1809
|
+
* or if the shapes shapes are only on select all shapes on the current page.
|
|
1807
1810
|
*
|
|
1808
1811
|
* @example
|
|
1809
1812
|
* ```ts
|
|
@@ -1813,11 +1816,34 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1813
1816
|
* @public
|
|
1814
1817
|
*/
|
|
1815
1818
|
selectAll(): this {
|
|
1816
|
-
|
|
1817
|
-
|
|
1819
|
+
let parentToSelectWithinId: TLParentId | null = null
|
|
1820
|
+
|
|
1821
|
+
const selectedShapeIds = this.getSelectedShapeIds()
|
|
1822
|
+
|
|
1823
|
+
// If we have selected shapes, try to find a parent to select within
|
|
1824
|
+
if (selectedShapeIds.length > 0) {
|
|
1825
|
+
for (const id of selectedShapeIds) {
|
|
1826
|
+
const shape = this.getShape(id)
|
|
1827
|
+
if (!shape) continue
|
|
1828
|
+
if (parentToSelectWithinId === null) {
|
|
1829
|
+
// If we haven't found a parent yet, set this parent as the parent to select within
|
|
1830
|
+
parentToSelectWithinId = shape.parentId
|
|
1831
|
+
} else if (parentToSelectWithinId !== shape.parentId) {
|
|
1832
|
+
// If we've found two different parents, we can't select all, do nothing
|
|
1833
|
+
return this
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// If we haven't found a parent from our selected shapes, select the current page
|
|
1839
|
+
if (!parentToSelectWithinId) {
|
|
1840
|
+
parentToSelectWithinId = this.getCurrentPageId()
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Select all the unlocked shapes within the parent
|
|
1844
|
+
const ids = this.getSortedChildIdsForParent(parentToSelectWithinId)
|
|
1818
1845
|
if (ids.length <= 0) return this
|
|
1819
1846
|
this.setSelectedShapes(this._getUnlockedShapeIds(ids))
|
|
1820
|
-
|
|
1821
1847
|
return this
|
|
1822
1848
|
}
|
|
1823
1849
|
|
|
@@ -1838,10 +1864,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1838
1864
|
firstParentId &&
|
|
1839
1865
|
selectedShapeIds.every((shapeId) => this.getShape(shapeId)?.parentId === firstParentId) &&
|
|
1840
1866
|
!isPageId(firstParentId)
|
|
1867
|
+
const filteredShapes = isSelectedWithinContainer
|
|
1868
|
+
? this.getCurrentPageShapes().filter((shape) => shape.parentId === firstParentId)
|
|
1869
|
+
: this.getCurrentPageShapes().filter((shape) => isPageId(shape.parentId))
|
|
1841
1870
|
const readingOrderShapes = isSelectedWithinContainer
|
|
1842
|
-
? this._getShapesInReadingOrder(
|
|
1843
|
-
this.getCurrentPageShapes().filter((shape) => shape.parentId === firstParentId)
|
|
1844
|
-
)
|
|
1871
|
+
? this._getShapesInReadingOrder(filteredShapes)
|
|
1845
1872
|
: this.getCurrentPageShapesInReadingOrder()
|
|
1846
1873
|
const currentShapeId: TLShapeId | undefined =
|
|
1847
1874
|
selectedShapeIds.length === 1
|
|
@@ -1858,7 +1885,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1858
1885
|
adjacentShapeId = shapeIds[adjacentIndex]
|
|
1859
1886
|
} else {
|
|
1860
1887
|
if (!currentShapeId) return
|
|
1861
|
-
adjacentShapeId = this.getNearestAdjacentShape(currentShapeId, direction)
|
|
1888
|
+
adjacentShapeId = this.getNearestAdjacentShape(filteredShapes, currentShapeId, direction)
|
|
1862
1889
|
}
|
|
1863
1890
|
|
|
1864
1891
|
const shape = this.getShape(adjacentShapeId)
|
|
@@ -1957,6 +1984,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1957
1984
|
* @public
|
|
1958
1985
|
*/
|
|
1959
1986
|
getNearestAdjacentShape(
|
|
1987
|
+
shapes: TLShape[],
|
|
1960
1988
|
currentShapeId: TLShapeId,
|
|
1961
1989
|
direction: 'left' | 'right' | 'up' | 'down'
|
|
1962
1990
|
): TLShapeId {
|
|
@@ -1964,7 +1992,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1964
1992
|
const currentShape = this.getShape(currentShapeId)
|
|
1965
1993
|
if (!currentShape) return currentShapeId
|
|
1966
1994
|
|
|
1967
|
-
const shapes = this.getCurrentPageShapes()
|
|
1968
1995
|
const tabbableShapes = shapes.filter(
|
|
1969
1996
|
(shape) => this.getShapeUtil(shape).canTabTo(shape) && shape.id !== currentShapeId
|
|
1970
1997
|
)
|
|
@@ -3046,7 +3073,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3046
3073
|
// Dispatch a new pointer move because the pointer's page will have changed
|
|
3047
3074
|
// (its screen position will compute to a new page position given the new camera position)
|
|
3048
3075
|
const { currentScreenPoint, currentPagePoint } = this.inputs
|
|
3049
|
-
const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
3050
3076
|
|
|
3051
3077
|
// compare the next page point (derived from the current camera) to the current page point
|
|
3052
3078
|
if (
|
|
@@ -3054,27 +3080,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3054
3080
|
currentScreenPoint.y / z - y !== currentPagePoint.y
|
|
3055
3081
|
) {
|
|
3056
3082
|
// If it's changed, dispatch a pointer event
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
target: 'canvas',
|
|
3060
|
-
name: 'pointer_move',
|
|
3061
|
-
// weird but true: we need to put the screen point back into client space
|
|
3062
|
-
point: Vec.AddXY(currentScreenPoint, screenBounds.x, screenBounds.y),
|
|
3083
|
+
this.updatePointer({
|
|
3084
|
+
immediate: opts?.immediate,
|
|
3063
3085
|
pointerId: INTERNAL_POINTER_IDS.CAMERA_MOVE,
|
|
3064
|
-
|
|
3065
|
-
altKey: this.inputs.altKey,
|
|
3066
|
-
shiftKey: this.inputs.shiftKey,
|
|
3067
|
-
metaKey: this.inputs.metaKey,
|
|
3068
|
-
accelKey: isAccelKey(this.inputs),
|
|
3069
|
-
button: 0,
|
|
3070
|
-
isPen: this.getInstanceState().isPenMode ?? false,
|
|
3071
|
-
}
|
|
3072
|
-
|
|
3073
|
-
if (opts?.immediate) {
|
|
3074
|
-
this._flushEventForTick(event)
|
|
3075
|
-
} else {
|
|
3076
|
-
this.dispatch(event)
|
|
3077
|
-
}
|
|
3086
|
+
})
|
|
3078
3087
|
}
|
|
3079
3088
|
|
|
3080
3089
|
this._tickCameraState()
|
|
@@ -4395,21 +4404,28 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4395
4404
|
*/
|
|
4396
4405
|
deletePage(page: TLPageId | TLPage): this {
|
|
4397
4406
|
const id = typeof page === 'string' ? page : page.id
|
|
4398
|
-
this.run(
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4407
|
+
this.run(
|
|
4408
|
+
() => {
|
|
4409
|
+
if (this.getIsReadonly()) return
|
|
4410
|
+
const pages = this.getPages()
|
|
4411
|
+
if (pages.length === 1) return
|
|
4402
4412
|
|
|
4403
|
-
|
|
4404
|
-
|
|
4413
|
+
const deletedPage = this.getPage(id)
|
|
4414
|
+
if (!deletedPage) return
|
|
4405
4415
|
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4416
|
+
if (id === this.getCurrentPageId()) {
|
|
4417
|
+
const index = pages.findIndex((page) => page.id === id)
|
|
4418
|
+
const next = pages[index - 1] ?? pages[index + 1]
|
|
4419
|
+
this.setCurrentPage(next.id)
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
const shapes = this.getSortedChildIdsForParent(deletedPage.id)
|
|
4423
|
+
this.deleteShapes(shapes)
|
|
4424
|
+
|
|
4425
|
+
this.store.remove([deletedPage.id])
|
|
4426
|
+
},
|
|
4427
|
+
{ ignoreShapeLock: true }
|
|
4428
|
+
)
|
|
4413
4429
|
return this
|
|
4414
4430
|
}
|
|
4415
4431
|
|
|
@@ -5196,8 +5212,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5196
5212
|
// Check labels first
|
|
5197
5213
|
if (
|
|
5198
5214
|
this.isShapeOfType<TLFrameShape>(shape, 'frame') ||
|
|
5199
|
-
(this.isShapeOfType<TLArrowShape>(shape, 'arrow') && shape.props.text.trim()) ||
|
|
5200
5215
|
((this.isShapeOfType<TLNoteShape>(shape, 'note') ||
|
|
5216
|
+
this.isShapeOfType<TLArrowShape>(shape, 'arrow') ||
|
|
5201
5217
|
(this.isShapeOfType<TLGeoShape>(shape, 'geo') && shape.props.fill === 'none')) &&
|
|
5202
5218
|
this.getShapeUtil(shape).getText(shape)?.trim())
|
|
5203
5219
|
) {
|
|
@@ -9647,6 +9663,52 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9647
9663
|
return this
|
|
9648
9664
|
}
|
|
9649
9665
|
|
|
9666
|
+
/**
|
|
9667
|
+
* Dispatch a pointer move event in the current position of the pointer. This is useful when
|
|
9668
|
+
* external circumstances have changed (e.g. the camera moved or a shape was moved) and you want
|
|
9669
|
+
* the current interaction to respond to that change.
|
|
9670
|
+
*
|
|
9671
|
+
* @example
|
|
9672
|
+
* ```ts
|
|
9673
|
+
* editor.updatePointer()
|
|
9674
|
+
* ```
|
|
9675
|
+
*
|
|
9676
|
+
* @param options - The options for updating the pointer.
|
|
9677
|
+
* @returns The editor instance.
|
|
9678
|
+
* @public
|
|
9679
|
+
*/
|
|
9680
|
+
updatePointer(options?: TLUpdatePointerOptions): this {
|
|
9681
|
+
const event: TLPointerEventInfo = {
|
|
9682
|
+
type: 'pointer',
|
|
9683
|
+
target: 'canvas',
|
|
9684
|
+
name: 'pointer_move',
|
|
9685
|
+
point:
|
|
9686
|
+
options?.point ??
|
|
9687
|
+
// weird but true: what `inputs` calls screen-space is actually viewport space. so
|
|
9688
|
+
// we need to convert back into true screen space first. we should fix this...
|
|
9689
|
+
Vec.Add(
|
|
9690
|
+
this.inputs.currentScreenPoint,
|
|
9691
|
+
this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!.screenBounds
|
|
9692
|
+
),
|
|
9693
|
+
pointerId: options?.pointerId ?? 0,
|
|
9694
|
+
button: options?.button ?? 0,
|
|
9695
|
+
isPen: options?.isPen ?? this.inputs.isPen,
|
|
9696
|
+
shiftKey: options?.shiftKey ?? this.inputs.shiftKey,
|
|
9697
|
+
altKey: options?.altKey ?? this.inputs.altKey,
|
|
9698
|
+
ctrlKey: options?.ctrlKey ?? this.inputs.ctrlKey,
|
|
9699
|
+
metaKey: options?.metaKey ?? this.inputs.metaKey,
|
|
9700
|
+
accelKey: options?.accelKey ?? isAccelKey(this.inputs),
|
|
9701
|
+
}
|
|
9702
|
+
|
|
9703
|
+
if (options?.immediate) {
|
|
9704
|
+
this._flushEventForTick(event)
|
|
9705
|
+
} else {
|
|
9706
|
+
this.dispatch(event)
|
|
9707
|
+
}
|
|
9708
|
+
|
|
9709
|
+
return this
|
|
9710
|
+
}
|
|
9711
|
+
|
|
9650
9712
|
/**
|
|
9651
9713
|
* Puts the editor into focused mode.
|
|
9652
9714
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BoxModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
|
|
2
|
+
import { objectMapKeys } from '@tldraw/utils'
|
|
2
3
|
import { Editor } from '../../Editor'
|
|
3
4
|
|
|
4
5
|
const fixNewLines = /\r?\n|\r/g
|
|
@@ -60,10 +61,18 @@ export interface TLMeasureTextSpanOpts {
|
|
|
60
61
|
|
|
61
62
|
const spaceCharacterRegex = /\s/
|
|
62
63
|
|
|
64
|
+
const initialDefaultStyles = Object.freeze({
|
|
65
|
+
'overflow-wrap': 'break-word',
|
|
66
|
+
'word-break': 'auto',
|
|
67
|
+
width: null,
|
|
68
|
+
height: null,
|
|
69
|
+
'max-width': null,
|
|
70
|
+
'min-width': null,
|
|
71
|
+
})
|
|
72
|
+
|
|
63
73
|
/** @public */
|
|
64
74
|
export class TextManager {
|
|
65
75
|
private elm: HTMLDivElement
|
|
66
|
-
private defaultStyles: Record<string, string | null>
|
|
67
76
|
|
|
68
77
|
constructor(public editor: Editor) {
|
|
69
78
|
const elm = document.createElement('div')
|
|
@@ -73,31 +82,34 @@ export class TextManager {
|
|
|
73
82
|
elm.tabIndex = -1
|
|
74
83
|
this.editor.getContainer().appendChild(elm)
|
|
75
84
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
'word-break': 'auto',
|
|
81
|
-
width: null,
|
|
82
|
-
height: null,
|
|
83
|
-
'max-width': null,
|
|
84
|
-
'min-width': null,
|
|
85
|
+
this.elm = elm
|
|
86
|
+
|
|
87
|
+
for (const key of objectMapKeys(initialDefaultStyles)) {
|
|
88
|
+
elm.style.setProperty(key, initialDefaultStyles[key])
|
|
85
89
|
}
|
|
90
|
+
}
|
|
86
91
|
|
|
87
|
-
|
|
92
|
+
private setElementStyles(styles: Record<string, string | undefined>) {
|
|
93
|
+
const stylesToReinstate = {} as any
|
|
94
|
+
for (const key of objectMapKeys(styles)) {
|
|
95
|
+
if (typeof styles[key] === 'string') {
|
|
96
|
+
const oldValue = this.elm.style.getPropertyValue(key)
|
|
97
|
+
if (oldValue === styles[key]) continue
|
|
98
|
+
stylesToReinstate[key] = oldValue
|
|
99
|
+
this.elm.style.setProperty(key, styles[key])
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return () => {
|
|
103
|
+
for (const key of objectMapKeys(stylesToReinstate)) {
|
|
104
|
+
this.elm.style.setProperty(key, stylesToReinstate[key])
|
|
105
|
+
}
|
|
106
|
+
}
|
|
88
107
|
}
|
|
89
108
|
|
|
90
109
|
dispose() {
|
|
91
110
|
return this.elm.remove()
|
|
92
111
|
}
|
|
93
112
|
|
|
94
|
-
private resetElmStyles() {
|
|
95
|
-
const { elm, defaultStyles } = this
|
|
96
|
-
for (const key in defaultStyles) {
|
|
97
|
-
elm.style.setProperty(key, defaultStyles[key])
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
113
|
measureText(textToMeasure: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
102
114
|
const div = document.createElement('div')
|
|
103
115
|
div.textContent = normalizeTextForDom(textToMeasure)
|
|
@@ -107,54 +119,36 @@ export class TextManager {
|
|
|
107
119
|
measureHtml(html: string, opts: TLMeasureTextOpts): BoxModel & { scrollWidth: number } {
|
|
108
120
|
const { elm } = this
|
|
109
121
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
const newStyles = {
|
|
123
|
+
'font-family': opts.fontFamily,
|
|
124
|
+
'font-style': opts.fontStyle,
|
|
125
|
+
'font-weight': opts.fontWeight,
|
|
126
|
+
'font-size': opts.fontSize + 'px',
|
|
127
|
+
'line-height': opts.lineHeight.toString(),
|
|
128
|
+
padding: opts.padding,
|
|
129
|
+
'max-width': opts.maxWidth ? opts.maxWidth + 'px' : undefined,
|
|
130
|
+
'min-width': opts.minWidth ? opts.minWidth + 'px' : undefined,
|
|
131
|
+
'overflow-wrap': opts.disableOverflowWrapBreaking ? 'normal' : undefined,
|
|
132
|
+
...opts.otherStyles,
|
|
117
133
|
}
|
|
118
134
|
|
|
119
|
-
|
|
135
|
+
const restoreStyles = this.setElementStyles(newStyles)
|
|
120
136
|
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
try {
|
|
138
|
+
elm.innerHTML = html
|
|
123
139
|
|
|
124
|
-
|
|
125
|
-
|
|
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)
|
|
140
|
+
const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
|
|
141
|
+
const rect = elm.getBoundingClientRect()
|
|
130
142
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (opts.disableOverflowWrapBreaking) {
|
|
140
|
-
elm.style.setProperty('overflow-wrap', 'normal')
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (opts.otherStyles) {
|
|
144
|
-
for (const [key, value] of Object.entries(opts.otherStyles)) {
|
|
145
|
-
elm.style.setProperty(key, value)
|
|
143
|
+
return {
|
|
144
|
+
x: 0,
|
|
145
|
+
y: 0,
|
|
146
|
+
w: rect.width,
|
|
147
|
+
h: rect.height,
|
|
148
|
+
scrollWidth,
|
|
146
149
|
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const scrollWidth = opts.measureScrollWidth ? elm.scrollWidth : 0
|
|
150
|
-
const rect = elm.getBoundingClientRect()
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
x: 0,
|
|
154
|
-
y: 0,
|
|
155
|
-
w: rect.width,
|
|
156
|
-
h: rect.height,
|
|
157
|
-
scrollWidth,
|
|
150
|
+
} finally {
|
|
151
|
+
restoreStyles()
|
|
158
152
|
}
|
|
159
153
|
}
|
|
160
154
|
|
|
@@ -274,82 +268,68 @@ export class TextManager {
|
|
|
274
268
|
|
|
275
269
|
const { elm } = this
|
|
276
270
|
|
|
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())
|
|
293
|
-
|
|
294
|
-
const elementWidth = Math.ceil(opts.width - opts.padding * 2)
|
|
295
|
-
elm.style.setProperty('width', `${elementWidth}px`)
|
|
296
|
-
elm.style.setProperty('height', 'min-content')
|
|
297
|
-
elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
|
|
298
|
-
|
|
299
271
|
const shouldTruncateToFirstLine =
|
|
300
272
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
273
|
+
const elementWidth = Math.ceil(opts.width - opts.padding * 2)
|
|
274
|
+
const newStyles = {
|
|
275
|
+
'font-family': opts.fontFamily,
|
|
276
|
+
'font-style': opts.fontStyle,
|
|
277
|
+
'font-weight': opts.fontWeight,
|
|
278
|
+
'font-size': opts.fontSize + 'px',
|
|
279
|
+
'line-height': opts.lineHeight.toString(),
|
|
280
|
+
width: `${elementWidth}px`,
|
|
281
|
+
height: 'min-content',
|
|
282
|
+
'text-align': textAlignmentsForLtr[opts.textAlign],
|
|
283
|
+
'overflow-wrap': shouldTruncateToFirstLine ? 'anywhere' : undefined,
|
|
284
|
+
'word-break': shouldTruncateToFirstLine ? 'break-all' : undefined,
|
|
285
|
+
...opts.otherStyles,
|
|
311
286
|
}
|
|
287
|
+
const restoreStyles = this.setElementStyles(newStyles)
|
|
312
288
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// Render the text into the measurement element:
|
|
316
|
-
elm.textContent = normalizedText
|
|
317
|
-
|
|
318
|
-
// actually measure the text:
|
|
319
|
-
const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
|
|
320
|
-
shouldTruncateToFirstLine,
|
|
321
|
-
})
|
|
289
|
+
try {
|
|
290
|
+
const normalizedText = normalizeTextForDom(textToMeasure)
|
|
322
291
|
|
|
323
|
-
|
|
324
|
-
// we need to measure the ellipsis to know how much space it takes up
|
|
325
|
-
elm.textContent = '…'
|
|
326
|
-
const ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)
|
|
327
|
-
|
|
328
|
-
// then, we need to subtract that space from the width we have and measure again:
|
|
329
|
-
elm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)
|
|
292
|
+
// Render the text into the measurement element:
|
|
330
293
|
elm.textContent = normalizedText
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Finally, we add in our ellipsis at the end of the last span. We
|
|
336
|
-
// have to do this after measuring, not before, because adding the
|
|
337
|
-
// ellipsis changes how whitespace might be getting collapsed by the
|
|
338
|
-
// browser.
|
|
339
|
-
const lastSpan = truncatedSpans[truncatedSpans.length - 1]!
|
|
340
|
-
truncatedSpans.push({
|
|
341
|
-
text: '…',
|
|
342
|
-
box: {
|
|
343
|
-
x: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),
|
|
344
|
-
y: lastSpan.box.y,
|
|
345
|
-
w: ellipsisWidth,
|
|
346
|
-
h: lastSpan.box.h,
|
|
347
|
-
},
|
|
294
|
+
|
|
295
|
+
// actually measure the text:
|
|
296
|
+
const { spans, didTruncate } = this.measureElementTextNodeSpans(elm, {
|
|
297
|
+
shouldTruncateToFirstLine,
|
|
348
298
|
})
|
|
349
299
|
|
|
350
|
-
|
|
351
|
-
|
|
300
|
+
if (opts.overflow === 'truncate-ellipsis' && didTruncate) {
|
|
301
|
+
// we need to measure the ellipsis to know how much space it takes up
|
|
302
|
+
elm.textContent = '…'
|
|
303
|
+
const ellipsisWidth = Math.ceil(this.measureElementTextNodeSpans(elm).spans[0].box.w)
|
|
304
|
+
|
|
305
|
+
// then, we need to subtract that space from the width we have and measure again:
|
|
306
|
+
elm.style.setProperty('width', `${elementWidth - ellipsisWidth}px`)
|
|
307
|
+
elm.textContent = normalizedText
|
|
308
|
+
const truncatedSpans = this.measureElementTextNodeSpans(elm, {
|
|
309
|
+
shouldTruncateToFirstLine: true,
|
|
310
|
+
}).spans
|
|
311
|
+
|
|
312
|
+
// Finally, we add in our ellipsis at the end of the last span. We
|
|
313
|
+
// have to do this after measuring, not before, because adding the
|
|
314
|
+
// ellipsis changes how whitespace might be getting collapsed by the
|
|
315
|
+
// browser.
|
|
316
|
+
const lastSpan = truncatedSpans[truncatedSpans.length - 1]!
|
|
317
|
+
truncatedSpans.push({
|
|
318
|
+
text: '…',
|
|
319
|
+
box: {
|
|
320
|
+
x: Math.min(lastSpan.box.x + lastSpan.box.w, opts.width - opts.padding - ellipsisWidth),
|
|
321
|
+
y: lastSpan.box.y,
|
|
322
|
+
w: ellipsisWidth,
|
|
323
|
+
h: lastSpan.box.h,
|
|
324
|
+
},
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
return truncatedSpans
|
|
328
|
+
}
|
|
352
329
|
|
|
353
|
-
|
|
330
|
+
return spans
|
|
331
|
+
} finally {
|
|
332
|
+
restoreStyles()
|
|
333
|
+
}
|
|
354
334
|
}
|
|
355
335
|
}
|
|
@@ -24,6 +24,7 @@ describe('UserPreferencesManager', () => {
|
|
|
24
24
|
color: '#FF802B',
|
|
25
25
|
locale: 'en',
|
|
26
26
|
animationSpeed: 1,
|
|
27
|
+
areKeyboardShortcutsEnabled: true,
|
|
27
28
|
edgeScrollSpeed: 1,
|
|
28
29
|
colorScheme: 'light',
|
|
29
30
|
isSnapMode: false,
|
|
@@ -229,6 +230,7 @@ describe('UserPreferencesManager', () => {
|
|
|
229
230
|
locale: mockUserPreferences.locale,
|
|
230
231
|
color: mockUserPreferences.color,
|
|
231
232
|
animationSpeed: mockUserPreferences.animationSpeed,
|
|
233
|
+
areKeyboardShortcutsEnabled: mockUserPreferences.areKeyboardShortcutsEnabled,
|
|
232
234
|
isSnapMode: mockUserPreferences.isSnapMode,
|
|
233
235
|
colorScheme: mockUserPreferences.colorScheme,
|
|
234
236
|
isDarkMode: false, // light mode
|
|
@@ -362,6 +364,21 @@ describe('UserPreferencesManager', () => {
|
|
|
362
364
|
})
|
|
363
365
|
})
|
|
364
366
|
|
|
367
|
+
describe('getAreKeyboardShortcutsEnabled', () => {
|
|
368
|
+
it('should return user keyboard shortcuts', () => {
|
|
369
|
+
expect(userPreferencesManager.getAreKeyboardShortcutsEnabled()).toBe(
|
|
370
|
+
mockUserPreferences.areKeyboardShortcutsEnabled
|
|
371
|
+
)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('should return default keyboard shortcuts when null', () => {
|
|
375
|
+
userPreferencesAtom.set({ ...mockUserPreferences, areKeyboardShortcutsEnabled: null })
|
|
376
|
+
expect(userPreferencesManager.getAreKeyboardShortcutsEnabled()).toBe(
|
|
377
|
+
defaultUserPreferences.areKeyboardShortcutsEnabled
|
|
378
|
+
)
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
|
|
365
382
|
describe('getEdgeScrollSpeed', () => {
|
|
366
383
|
it('should return user edge scroll speed', () => {
|
|
367
384
|
expect(userPreferencesManager.getEdgeScrollSpeed()).toBe(
|
|
@@ -483,6 +500,7 @@ describe('UserPreferencesManager', () => {
|
|
|
483
500
|
color: null,
|
|
484
501
|
locale: null,
|
|
485
502
|
animationSpeed: null,
|
|
503
|
+
areKeyboardShortcutsEnabled: null,
|
|
486
504
|
edgeScrollSpeed: null,
|
|
487
505
|
isSnapMode: null,
|
|
488
506
|
isWrapMode: null,
|
|
@@ -496,6 +514,9 @@ describe('UserPreferencesManager', () => {
|
|
|
496
514
|
expect(userPreferencesManager.getColor()).toBe(defaultUserPreferences.color)
|
|
497
515
|
expect(userPreferencesManager.getLocale()).toBe(defaultUserPreferences.locale)
|
|
498
516
|
expect(userPreferencesManager.getAnimationSpeed()).toBe(defaultUserPreferences.animationSpeed)
|
|
517
|
+
expect(userPreferencesManager.getAreKeyboardShortcutsEnabled()).toBe(
|
|
518
|
+
defaultUserPreferences.areKeyboardShortcutsEnabled
|
|
519
|
+
)
|
|
499
520
|
expect(userPreferencesManager.getEdgeScrollSpeed()).toBe(
|
|
500
521
|
defaultUserPreferences.edgeScrollSpeed
|
|
501
522
|
)
|
|
@@ -43,6 +43,7 @@ export class UserPreferencesManager {
|
|
|
43
43
|
locale: this.getLocale(),
|
|
44
44
|
color: this.getColor(),
|
|
45
45
|
animationSpeed: this.getAnimationSpeed(),
|
|
46
|
+
areKeyboardShortcutsEnabled: this.getAreKeyboardShortcutsEnabled(),
|
|
46
47
|
isSnapMode: this.getIsSnapMode(),
|
|
47
48
|
colorScheme: this.user.userPreferences.get().colorScheme,
|
|
48
49
|
isDarkMode: this.getIsDarkMode(),
|
|
@@ -75,6 +76,13 @@ export class UserPreferencesManager {
|
|
|
75
76
|
return this.user.userPreferences.get().animationSpeed ?? defaultUserPreferences.animationSpeed
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
@computed getAreKeyboardShortcutsEnabled() {
|
|
80
|
+
return (
|
|
81
|
+
this.user.userPreferences.get().areKeyboardShortcutsEnabled ??
|
|
82
|
+
defaultUserPreferences.areKeyboardShortcutsEnabled
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
@computed getId() {
|
|
79
87
|
return this.user.userPreferences.get().id
|
|
80
88
|
}
|