@tldraw/editor 5.4.0-next.09eca5dee30d → 5.4.0-next.0a83c4ec44f1
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 +114 -47
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +7 -2
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +6 -8
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasOverlays.js +1 -3
- package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +1 -1
- package/dist-cjs/lib/config/TLUserPreferences.js +6 -2
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +1 -1
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +181 -294
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +7 -3
- package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -3
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +7 -0
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +6 -3
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +12 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
- package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
- package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/globals/editors.js +57 -0
- package/dist-cjs/lib/globals/editors.js.map +7 -0
- package/dist-cjs/lib/globals/menus.js +9 -4
- package/dist-cjs/lib/globals/menus.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +39 -9
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
- package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +9 -2
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +16 -3
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/license/useLicenseManagerState.js +5 -4
- package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +2 -1
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +4 -3
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +63 -21
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +2 -1
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/deepLinks.js +5 -1
- package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
- package/dist-cjs/lib/utils/normalizeWheel.js +2 -0
- package/dist-cjs/lib/utils/normalizeWheel.js.map +2 -2
- package/dist-cjs/lib/utils/rotation.js +1 -9
- package/dist-cjs/lib/utils/rotation.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +17 -0
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/lib/utils/sync/alerts.js +1 -1
- package/dist-cjs/lib/utils/sync/alerts.js.map +1 -1
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +114 -47
- package/dist-esm/index.mjs +9 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +9 -4
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
- package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
- package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +1 -1
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +189 -295
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +7 -3
- package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -3
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +7 -0
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -3
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +6 -3
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +12 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
- package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
- package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/globals/editors.mjs +37 -0
- package/dist-esm/lib/globals/editors.mjs.map +7 -0
- package/dist-esm/lib/globals/menus.mjs +9 -4
- package/dist-esm/lib/globals/menus.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +40 -10
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
- package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +9 -2
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +16 -3
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/license/useLicenseManagerState.mjs +5 -4
- package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +2 -1
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +4 -3
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +63 -21
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +2 -1
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/deepLinks.mjs +5 -1
- package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
- package/dist-esm/lib/utils/normalizeWheel.mjs +2 -0
- package/dist-esm/lib/utils/normalizeWheel.mjs.map +2 -2
- package/dist-esm/lib/utils/rotation.mjs +1 -9
- package/dist-esm/lib/utils/rotation.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +17 -0
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/alerts.mjs +1 -1
- package/dist-esm/lib/utils/sync/alerts.mjs.map +1 -1
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +13 -7
- package/src/index.ts +6 -1
- package/src/lib/TldrawEditor.tsx +16 -4
- package/src/lib/components/MenuClickCapture.tsx +19 -18
- package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
- package/src/lib/config/TLUserPreferences.test.ts +18 -1
- package/src/lib/config/TLUserPreferences.ts +8 -4
- package/src/lib/config/createTLStore.ts +1 -1
- package/src/lib/editor/Editor.ts +266 -383
- package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +7 -3
- package/src/lib/editor/managers/ClickManager/ClickManager.ts +5 -3
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
- package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
- package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +12 -1
- package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
- package/src/lib/editor/managers/TextManager/TextManager.ts +2 -6
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +13 -0
- package/src/lib/editor/managers/TickManager/TickManager.ts +9 -3
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +64 -3
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +18 -1
- package/src/lib/editor/overlays/OverlayManager.ts +3 -7
- package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
- package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
- package/src/lib/editor/tools/StateNode.test.ts +64 -0
- package/src/lib/editor/tools/StateNode.ts +12 -2
- package/src/lib/globals/editors.test.ts +76 -0
- package/src/lib/globals/editors.ts +53 -0
- package/src/lib/globals/menus.test.ts +33 -0
- package/src/lib/globals/menus.ts +11 -4
- package/src/lib/hooks/useCanvasEvents.ts +2 -0
- package/src/lib/hooks/useDocumentEvents.ts +60 -16
- package/src/lib/hooks/useLocalStore.ts +16 -8
- package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
- package/src/lib/hooks/usePeerIds.ts +2 -1
- package/src/lib/license/LicenseManager.test.ts +160 -0
- package/src/lib/license/LicenseManager.ts +13 -4
- package/src/lib/license/LicenseProvider.tsx +24 -3
- package/src/lib/license/useLicenseManagerState.ts +8 -6
- package/src/lib/primitives/Box.test.ts +9 -1
- package/src/lib/primitives/Box.ts +3 -1
- package/src/lib/primitives/Vec.test.ts +10 -0
- package/src/lib/primitives/Vec.ts +4 -4
- package/src/lib/primitives/geometry/Arc2d.test.ts +226 -3
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +173 -3
- package/src/lib/primitives/geometry/CubicSpline2d.test.ts +160 -3
- package/src/lib/primitives/geometry/Ellipse2d.test.ts +243 -3
- package/src/lib/primitives/geometry/Geometry2d.test.ts +553 -1
- package/src/lib/primitives/geometry/Group2d.test.ts +398 -3
- package/src/lib/primitives/geometry/Group2d.ts +63 -27
- package/src/lib/primitives/geometry/Polyline2d.test.ts +207 -0
- package/src/lib/primitives/geometry/Stadium2d.test.ts +191 -3
- package/src/lib/primitives/utils.test.ts +10 -1
- package/src/lib/primitives/utils.ts +4 -1
- package/src/lib/test/useCanvasEvents.test.tsx +74 -0
- package/src/lib/test/useDocumentEvents.test.tsx +107 -0
- package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
- package/src/lib/utils/deepLinks.test.ts +15 -0
- package/src/lib/utils/deepLinks.ts +7 -1
- package/src/lib/utils/normalizeWheel.test.ts +39 -0
- package/src/lib/utils/normalizeWheel.ts +6 -0
- package/src/lib/utils/rotation.ts +4 -12
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +56 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +24 -0
- package/src/lib/utils/sync/alerts.ts +1 -1
- package/src/version.ts +3 -3
- package/src/lib/primitives/geometry/Polyline.test.ts +0 -5
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
Result,
|
|
73
73
|
ZERO_INDEX_KEY,
|
|
74
74
|
annotateError,
|
|
75
|
+
areArraysShallowEqual,
|
|
75
76
|
assert,
|
|
76
77
|
assertExists,
|
|
77
78
|
bind,
|
|
@@ -118,8 +119,10 @@ import {
|
|
|
118
119
|
import { getOwnerWindow } from '../exports/domUtils'
|
|
119
120
|
import { exportToSvg } from '../exports/exportToSvg'
|
|
120
121
|
import { getSvgAsImageWithOptions, trimSvgToContent } from '../exports/getSvgAsImage'
|
|
122
|
+
import { registerMountedEditor, unregisterMountedEditor } from '../globals/editors'
|
|
121
123
|
import { tlmenus } from '../globals/menus'
|
|
122
124
|
import { tltime } from '../globals/time'
|
|
125
|
+
import { LicenseManager } from '../license/LicenseManager'
|
|
123
126
|
import { TldrawOptions, defaultTldrawOptions } from '../options'
|
|
124
127
|
import { Box, BoxLike } from '../primitives/Box'
|
|
125
128
|
import { EASINGS } from '../primitives/easings'
|
|
@@ -127,7 +130,13 @@ import { Geometry2d } from '../primitives/geometry/Geometry2d'
|
|
|
127
130
|
import { Group2d } from '../primitives/geometry/Group2d'
|
|
128
131
|
import { intersectPolygonPolygon } from '../primitives/intersect'
|
|
129
132
|
import { Mat, MatLike } from '../primitives/Mat'
|
|
130
|
-
import {
|
|
133
|
+
import {
|
|
134
|
+
HALF_PI,
|
|
135
|
+
approximately,
|
|
136
|
+
areAnglesCompatible,
|
|
137
|
+
clamp,
|
|
138
|
+
pointInPolygon,
|
|
139
|
+
} from '../primitives/utils'
|
|
131
140
|
import { Vec, VecLike } from '../primitives/Vec'
|
|
132
141
|
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
133
142
|
import { dataUrlToFile } from '../utils/assets'
|
|
@@ -185,6 +194,7 @@ import { TLHistoryBatchOptions } from './types/history-types'
|
|
|
185
194
|
import {
|
|
186
195
|
OptionalKeys,
|
|
187
196
|
RequiredKeys,
|
|
197
|
+
TLCameraConstraints,
|
|
188
198
|
TLCameraMoveOptions,
|
|
189
199
|
TLCameraOptions,
|
|
190
200
|
TLGetShapeAtPointOptions,
|
|
@@ -543,6 +553,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
543
553
|
nextPageState.editingShapeId = null
|
|
544
554
|
}
|
|
545
555
|
|
|
556
|
+
if (
|
|
557
|
+
prevPageState.croppingShapeId &&
|
|
558
|
+
shapesNoLongerInPage.has(prevPageState.croppingShapeId)
|
|
559
|
+
) {
|
|
560
|
+
if (!nextPageState) nextPageState = { ...prevPageState }
|
|
561
|
+
nextPageState.croppingShapeId = null
|
|
562
|
+
}
|
|
563
|
+
|
|
546
564
|
const hintingShapeIds = prevPageState.hintingShapeIds.filter(
|
|
547
565
|
(id) => !shapesNoLongerInPage.has(id)
|
|
548
566
|
)
|
|
@@ -828,10 +846,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
828
846
|
|
|
829
847
|
if (prev?.selectedShapeIds !== next?.selectedShapeIds) {
|
|
830
848
|
// ensure that descendants and ancestors are not selected at the same time
|
|
849
|
+
const selectedShapeIds = new Set(next.selectedShapeIds)
|
|
831
850
|
const filtered = next.selectedShapeIds.filter((id) => {
|
|
832
851
|
let parentId = this.getShape(id)?.parentId
|
|
833
852
|
while (isShapeId(parentId)) {
|
|
834
|
-
if (
|
|
853
|
+
if (selectedShapeIds.has(parentId)) {
|
|
835
854
|
return false
|
|
836
855
|
}
|
|
837
856
|
parentId = this.getShape(parentId)?.parentId
|
|
@@ -918,10 +937,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
918
937
|
|
|
919
938
|
this.on('mount', () => {
|
|
920
939
|
this._isMounted.set(true)
|
|
940
|
+
registerMountedEditor(this)
|
|
921
941
|
})
|
|
922
942
|
|
|
923
943
|
this.on('unmount', () => {
|
|
924
944
|
this._isMounted.set(false)
|
|
945
|
+
unregisterMountedEditor(this)
|
|
925
946
|
})
|
|
926
947
|
|
|
927
948
|
this.timers.requestAnimationFrame(() => {
|
|
@@ -934,7 +955,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
934
955
|
const mode = this.store.props.collaboration.mode
|
|
935
956
|
this.disposables.add(
|
|
936
957
|
react('update collaboration mode', () => {
|
|
937
|
-
|
|
958
|
+
const isReadonly = mode.get() === 'readonly'
|
|
959
|
+
// only track `mode`, and keep the sync out of the user's undo history
|
|
960
|
+
unsafe__withoutCapture(() =>
|
|
961
|
+
this._updateInstanceState({ isReadonly }, { history: 'ignore' })
|
|
962
|
+
)
|
|
938
963
|
})
|
|
939
964
|
)
|
|
940
965
|
}
|
|
@@ -973,6 +998,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
973
998
|
|
|
974
999
|
readonly options: TldrawOptions
|
|
975
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* The license manager whose feature flags apply to this editor. Assigned by `<TldrawEditor />`
|
|
1003
|
+
* when it creates the editor, so that UI rendered outside the editor tree can resolve the
|
|
1004
|
+
* editor's license through the editor instance. Undefined for editors created directly.
|
|
1005
|
+
*
|
|
1006
|
+
* @internal
|
|
1007
|
+
*/
|
|
1008
|
+
licenseManager?: LicenseManager
|
|
1009
|
+
|
|
976
1010
|
readonly contextId = uniqueId()
|
|
977
1011
|
|
|
978
1012
|
/**
|
|
@@ -1209,6 +1243,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1209
1243
|
if (this._isMounted.get()) {
|
|
1210
1244
|
this.emit('unmount')
|
|
1211
1245
|
}
|
|
1246
|
+
// The unmount listener above normally handles this; unregister again in case a listener
|
|
1247
|
+
// threw or was removed, so a disposed editor never lingers in `tleditors`.
|
|
1248
|
+
unregisterMountedEditor(this)
|
|
1212
1249
|
|
|
1213
1250
|
// Stop any in-progress camera animations and following before
|
|
1214
1251
|
// running disposables, so their cleanup listeners fire first
|
|
@@ -1374,7 +1411,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1374
1411
|
getShapeUtil(arg: string | { type: string }) {
|
|
1375
1412
|
const type = typeof arg === 'string' ? arg : arg.type
|
|
1376
1413
|
const shapeUtil = getOwnProperty(this.shapeUtils, type)
|
|
1377
|
-
|
|
1414
|
+
// hot path: avoid building the message (and the assert wrapper) on every successful call
|
|
1415
|
+
if (!shapeUtil) throw new Error(`No shape util found for type "${type}"`)
|
|
1378
1416
|
return shapeUtil
|
|
1379
1417
|
}
|
|
1380
1418
|
|
|
@@ -2451,9 +2489,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2451
2489
|
const selectedShapes = this.getSelectedShapes()
|
|
2452
2490
|
if (!selectedShapes.length) return
|
|
2453
2491
|
const selectedShape = selectedShapes[0]
|
|
2454
|
-
const children =
|
|
2455
|
-
.map((id) => this.getShape(id))
|
|
2456
|
-
|
|
2492
|
+
const children = compact(
|
|
2493
|
+
this.getSortedChildIdsForParent(selectedShape.id).map((id) => this.getShape(id))
|
|
2494
|
+
)
|
|
2457
2495
|
const sortedChildren = this._getShapesInReadingOrder(children)
|
|
2458
2496
|
if (sortedChildren.length === 0) return
|
|
2459
2497
|
this._selectShapesAndZoom([sortedChildren[0].id])
|
|
@@ -2554,20 +2592,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2554
2592
|
* @internal
|
|
2555
2593
|
*/
|
|
2556
2594
|
getShapesSharedRotation(shapeIds: TLShapeId[]) {
|
|
2557
|
-
let foundFirst = false // annoying but we can't use an i===0 check because we need to skip over undefineds
|
|
2558
2595
|
let rotation = 0
|
|
2559
2596
|
for (let i = 0, n = shapeIds.length; i < n; i++) {
|
|
2560
|
-
const
|
|
2561
|
-
if (
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
}
|
|
2567
|
-
} else {
|
|
2568
|
-
// First rotation found
|
|
2569
|
-
foundFirst = true
|
|
2570
|
-
rotation = pageTransform.rotation()
|
|
2597
|
+
const pageRotation = this.getShapePageTransform(shapeIds[i]).rotation()
|
|
2598
|
+
if (i === 0) {
|
|
2599
|
+
rotation = pageRotation
|
|
2600
|
+
} else if (pageRotation !== rotation) {
|
|
2601
|
+
// There are at least 2 different rotations, so the common rotation is zero
|
|
2602
|
+
return 0
|
|
2571
2603
|
}
|
|
2572
2604
|
}
|
|
2573
2605
|
|
|
@@ -2599,7 +2631,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2599
2631
|
|
|
2600
2632
|
if (shapeIds.length === 1) {
|
|
2601
2633
|
const bounds = this.getShapeGeometry(shapeIds[0]).bounds.clone()
|
|
2602
|
-
const pageTransform = this.getShapePageTransform(shapeIds[0])
|
|
2634
|
+
const pageTransform = this.getShapePageTransform(shapeIds[0])
|
|
2603
2635
|
bounds.point = pageTransform.applyToPoint(bounds.point)
|
|
2604
2636
|
return bounds
|
|
2605
2637
|
}
|
|
@@ -2607,11 +2639,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2607
2639
|
// need to 'un-rotate' all the outlines of the existing nodes so we can fit them inside a box
|
|
2608
2640
|
const boxFromRotatedVertices = Box.FromPoints(
|
|
2609
2641
|
shapeIds
|
|
2610
|
-
.flatMap((id) =>
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
return pageTransform.applyToPoints(this.getShapeGeometry(id).bounds.corners)
|
|
2614
|
-
})
|
|
2642
|
+
.flatMap((id) =>
|
|
2643
|
+
this.getShapePageTransform(id).applyToPoints(this.getShapeGeometry(id).bounds.corners)
|
|
2644
|
+
)
|
|
2615
2645
|
.map((p) => p.rot(-selectionRotation))
|
|
2616
2646
|
)
|
|
2617
2647
|
// now position box so that it's top-left corner is in the right place
|
|
@@ -2638,9 +2668,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2638
2668
|
@computed getSelectionRotatedScreenBounds(): Box | undefined {
|
|
2639
2669
|
const bounds = this.getSelectionRotatedPageBounds()
|
|
2640
2670
|
if (!bounds) return undefined
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2671
|
+
// Don't use pageToScreen here: it reads the screen bounds without capturing them, so this
|
|
2672
|
+
// computed would never invalidate when the container moves
|
|
2673
|
+
const screenBounds = this.getViewportScreenBounds()
|
|
2674
|
+
const { x: cx, y: cy, z: zoom } = this.getCamera()
|
|
2675
|
+
return new Box(
|
|
2676
|
+
(bounds.x + cx) * zoom + screenBounds.x,
|
|
2677
|
+
(bounds.y + cy) * zoom + screenBounds.y,
|
|
2678
|
+
bounds.width * zoom,
|
|
2679
|
+
bounds.height * zoom
|
|
2680
|
+
)
|
|
2644
2681
|
}
|
|
2645
2682
|
|
|
2646
2683
|
// Focus Group
|
|
@@ -2780,57 +2817,38 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2780
2817
|
setEditingShape(shape: TLShapeId | TLShape | null): this {
|
|
2781
2818
|
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2782
2819
|
|
|
2783
|
-
|
|
2784
|
-
|
|
2820
|
+
// id was provided but the next editing shape was not editable or didn't exist, so do nothing
|
|
2821
|
+
if (id && !this.canEditShape(id)) return this
|
|
2822
|
+
|
|
2823
|
+
this.run(() => {
|
|
2824
|
+
// Clean up the previous editing shape. This runs outside the history-ignored batch below,
|
|
2825
|
+
// otherwise document changes made by onEditEnd (e.g. deleting an empty text shape) are
|
|
2826
|
+
// never recorded and leave a phantom undo entry whose redo resurrects the shape.
|
|
2827
|
+
const prevEditingShapeId = this.getEditingShapeId()
|
|
2828
|
+
if (prevEditingShapeId) {
|
|
2829
|
+
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2830
|
+
if (prevEditingShape) {
|
|
2831
|
+
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2785
2835
|
this.run(
|
|
2786
2836
|
() => {
|
|
2787
|
-
// Clean up the previous editing shape
|
|
2788
|
-
const prevEditingShapeId = this.getEditingShapeId()
|
|
2789
|
-
if (prevEditingShapeId) {
|
|
2790
|
-
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2791
|
-
if (prevEditingShape) {
|
|
2792
|
-
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
|
|
2796
2837
|
// Clean up the editing shape state and rich text editor
|
|
2797
2838
|
this._updateCurrentPageState({ editingShapeId: null })
|
|
2798
2839
|
this._currentRichTextEditor.set(null)
|
|
2799
|
-
},
|
|
2800
|
-
{ history: 'ignore' }
|
|
2801
|
-
)
|
|
2802
2840
|
|
|
2803
|
-
|
|
2804
|
-
}
|
|
2841
|
+
if (!id) return
|
|
2805
2842
|
|
|
2806
|
-
|
|
2807
|
-
|
|
2843
|
+
this.select(id)
|
|
2844
|
+
this._updateCurrentPageState({ editingShapeId: id })
|
|
2808
2845
|
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
const prevEditingShape = this.getShape(prevEditingShapeId)
|
|
2816
|
-
if (prevEditingShape) {
|
|
2817
|
-
this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape)
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
|
|
2821
|
-
// Clean up the editing shape state and rich text editor
|
|
2822
|
-
this._updateCurrentPageState({ editingShapeId: null })
|
|
2823
|
-
this._currentRichTextEditor.set(null)
|
|
2824
|
-
|
|
2825
|
-
// Set the new editing shape
|
|
2826
|
-
this.select(id)
|
|
2827
|
-
this._updateCurrentPageState({ editingShapeId: id })
|
|
2828
|
-
|
|
2829
|
-
const nextEditingShape = this.getShape(id)! // shape should be there because canEditShape checked it. Possible small chance that onEditEnd deleted it?
|
|
2830
|
-
this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape)
|
|
2831
|
-
},
|
|
2832
|
-
{ history: 'ignore' }
|
|
2833
|
-
)
|
|
2846
|
+
const nextEditingShape = this.getShape(id)! // shape should be there because canEditShape checked it. Possible small chance that onEditEnd deleted it?
|
|
2847
|
+
this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape)
|
|
2848
|
+
},
|
|
2849
|
+
{ history: 'ignore' }
|
|
2850
|
+
)
|
|
2851
|
+
})
|
|
2834
2852
|
|
|
2835
2853
|
return this
|
|
2836
2854
|
}
|
|
@@ -2995,26 +3013,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2995
3013
|
* @public
|
|
2996
3014
|
*/
|
|
2997
3015
|
setErasingShapes(shapes: TLShapeId[] | TLShape[]): this {
|
|
3016
|
+
// copy before sorting: the caller may pass a store-owned (frozen) array
|
|
2998
3017
|
const ids =
|
|
2999
3018
|
typeof shapes[0] === 'string'
|
|
3000
|
-
? (shapes as TLShapeId[])
|
|
3019
|
+
? (shapes as TLShapeId[]).slice()
|
|
3001
3020
|
: (shapes as TLShape[]).map((shape) => shape.id)
|
|
3002
3021
|
ids.sort() // sort the incoming ids
|
|
3003
3022
|
const erasingShapeIds = this.getErasingShapeIds()
|
|
3004
3023
|
this.run(
|
|
3005
3024
|
() => {
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
// presuming the current ids are also sorted, check each item to see if it's the same;
|
|
3009
|
-
// if we find any unequal, then we know the new ids are different.
|
|
3010
|
-
for (let i = 0; i < ids.length; i++) {
|
|
3011
|
-
if (ids[i] !== erasingShapeIds[i]) {
|
|
3012
|
-
this._updateCurrentPageState({ erasingShapeIds: ids })
|
|
3013
|
-
break
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
} else {
|
|
3017
|
-
// if the ids are a different length, then we know they're different.
|
|
3025
|
+
// the current ids are also sorted, so a shallow comparison tells us whether they changed
|
|
3026
|
+
if (!areArraysShallowEqual(ids, erasingShapeIds)) {
|
|
3018
3027
|
this._updateCurrentPageState({ erasingShapeIds: ids })
|
|
3019
3028
|
}
|
|
3020
3029
|
},
|
|
@@ -3051,6 +3060,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3051
3060
|
if (!_shape) return false
|
|
3052
3061
|
const util = this.getShapeUtil(_shape)
|
|
3053
3062
|
if (!util.canCrop(_shape)) return false
|
|
3063
|
+
if (this.getIsReadonly()) return false
|
|
3054
3064
|
if (this.isShapeOrAncestorLocked(_shape)) return false
|
|
3055
3065
|
return true
|
|
3056
3066
|
}
|
|
@@ -3130,11 +3140,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3130
3140
|
const collaborators = this.getCollaborators()
|
|
3131
3141
|
let leaderPresence = null as null | TLInstancePresence
|
|
3132
3142
|
while (targetUserId && !visited.includes(targetUserId)) {
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3143
|
+
const nextPresence = collaborators.find((c) => c.userId === targetUserId)
|
|
3144
|
+
// Stop at the last resolvable presence, otherwise a leader whose own leader has left
|
|
3145
|
+
// (or whose presence hasn't arrived yet) can't be followed at all
|
|
3146
|
+
if (!nextPresence) break
|
|
3147
|
+
leaderPresence = nextPresence
|
|
3148
|
+
targetUserId = nextPresence.followingUserId ?? null
|
|
3149
|
+
visited.push(nextPresence.userId)
|
|
3138
3150
|
}
|
|
3139
3151
|
return leaderPresence
|
|
3140
3152
|
}
|
|
@@ -3252,44 +3264,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3252
3264
|
*
|
|
3253
3265
|
* @public */
|
|
3254
3266
|
getInitialZoom() {
|
|
3255
|
-
|
|
3256
|
-
// If no camera constraints are provided, the default zoom is 100%
|
|
3257
|
-
if (!cameraOptions.constraints) return 1
|
|
3258
|
-
|
|
3259
|
-
// When defaultZoom is default, the default zoom is 100%
|
|
3260
|
-
if (cameraOptions.constraints.initialZoom === 'default') return 1
|
|
3261
|
-
|
|
3262
|
-
const { zx, zy } = getCameraFitXFitY(this, cameraOptions)
|
|
3263
|
-
|
|
3264
|
-
switch (cameraOptions.constraints.initialZoom) {
|
|
3265
|
-
case 'fit-min': {
|
|
3266
|
-
return Math.max(zx, zy)
|
|
3267
|
-
}
|
|
3268
|
-
case 'fit-max': {
|
|
3269
|
-
return Math.min(zx, zy)
|
|
3270
|
-
}
|
|
3271
|
-
case 'fit-x': {
|
|
3272
|
-
return zx
|
|
3273
|
-
}
|
|
3274
|
-
case 'fit-y': {
|
|
3275
|
-
return zy
|
|
3276
|
-
}
|
|
3277
|
-
case 'fit-min-100': {
|
|
3278
|
-
return Math.min(1, Math.max(zx, zy))
|
|
3279
|
-
}
|
|
3280
|
-
case 'fit-max-100': {
|
|
3281
|
-
return Math.min(1, Math.min(zx, zy))
|
|
3282
|
-
}
|
|
3283
|
-
case 'fit-x-100': {
|
|
3284
|
-
return Math.min(1, zx)
|
|
3285
|
-
}
|
|
3286
|
-
case 'fit-y-100': {
|
|
3287
|
-
return Math.min(1, zy)
|
|
3288
|
-
}
|
|
3289
|
-
default: {
|
|
3290
|
-
throw exhaustiveSwitchError(cameraOptions.constraints.initialZoom)
|
|
3291
|
-
}
|
|
3292
|
-
}
|
|
3267
|
+
return this._getFitZoom(this.getCameraOptions().constraints?.initialZoom ?? 'default')
|
|
3293
3268
|
}
|
|
3294
3269
|
|
|
3295
3270
|
/**
|
|
@@ -3302,16 +3277,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3302
3277
|
*
|
|
3303
3278
|
* @public */
|
|
3304
3279
|
getBaseZoom() {
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
if (!cameraOptions.constraints) return 1
|
|
3280
|
+
return this._getFitZoom(this.getCameraOptions().constraints?.baseZoom ?? 'default')
|
|
3281
|
+
}
|
|
3308
3282
|
|
|
3309
|
-
|
|
3310
|
-
|
|
3283
|
+
private _getFitZoom(fit: TLCameraConstraints['initialZoom']): number {
|
|
3284
|
+
const cameraOptions = this.getCameraOptions()
|
|
3285
|
+
if (!cameraOptions.constraints || fit === 'default') return 1
|
|
3311
3286
|
|
|
3312
3287
|
const { zx, zy } = getCameraFitXFitY(this, cameraOptions)
|
|
3313
3288
|
|
|
3314
|
-
switch (
|
|
3289
|
+
switch (fit) {
|
|
3315
3290
|
case 'fit-min': {
|
|
3316
3291
|
return Math.max(zx, zy)
|
|
3317
3292
|
}
|
|
@@ -3337,7 +3312,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3337
3312
|
return Math.min(1, zy)
|
|
3338
3313
|
}
|
|
3339
3314
|
default: {
|
|
3340
|
-
throw exhaustiveSwitchError(
|
|
3315
|
+
throw exhaustiveSwitchError(fit)
|
|
3341
3316
|
}
|
|
3342
3317
|
}
|
|
3343
3318
|
}
|
|
@@ -3374,7 +3349,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3374
3349
|
...this._cameraOptions.__unsafe__getWithoutCapture(),
|
|
3375
3350
|
...opts,
|
|
3376
3351
|
})
|
|
3377
|
-
|
|
3352
|
+
// `undefined < 1` is false, so an explicit `zoomSteps: undefined` would otherwise get through
|
|
3353
|
+
// and make every later camera move throw
|
|
3354
|
+
if (!next.zoomSteps || next.zoomSteps.length < 1) next.zoomSteps = [1]
|
|
3378
3355
|
this._cameraOptions.set(next)
|
|
3379
3356
|
this.setCamera(this.getCamera())
|
|
3380
3357
|
return this
|
|
@@ -3420,8 +3397,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3420
3397
|
const { constraints } = cameraOptions
|
|
3421
3398
|
|
|
3422
3399
|
// Clamp padding to half the viewport size on either dimension
|
|
3423
|
-
const
|
|
3424
|
-
const
|
|
3400
|
+
const px = Math.min(constraints.padding.x, vsb.w / 2)
|
|
3401
|
+
const py = Math.min(constraints.padding.y, vsb.h / 2)
|
|
3425
3402
|
|
|
3426
3403
|
// Expand the bounds by the padding
|
|
3427
3404
|
const bounds = Box.From(cameraOptions.constraints.bounds)
|
|
@@ -3490,14 +3467,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3490
3467
|
}
|
|
3491
3468
|
case 'inside': {
|
|
3492
3469
|
// When below fit zoom, constrain the camera so that the bounds stay completely within the viewport
|
|
3493
|
-
if (z < zx) x = clamp(x, minX, (vsb.w - px) / z - bounds.w)
|
|
3470
|
+
if (z < zx) x = clamp(x, minX, (vsb.w - px) / z - bounds.w - bounds.x)
|
|
3494
3471
|
// When above fit zoom, keep the bounds within padding distance of the viewport edge
|
|
3495
3472
|
else x = clamp(x, minX + freeW, minX)
|
|
3496
3473
|
break
|
|
3497
3474
|
}
|
|
3498
3475
|
case 'outside': {
|
|
3499
3476
|
// Constrain the camera so that the bounds never leaves the viewport
|
|
3500
|
-
x = clamp(x, px / z - bounds.w, (vsb.w - px) / z)
|
|
3477
|
+
x = clamp(x, px / z - bounds.w - bounds.x, (vsb.w - px) / z - bounds.x)
|
|
3501
3478
|
break
|
|
3502
3479
|
}
|
|
3503
3480
|
case 'free': {
|
|
@@ -3522,12 +3499,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3522
3499
|
break
|
|
3523
3500
|
}
|
|
3524
3501
|
case 'inside': {
|
|
3525
|
-
if (z < zy) y = clamp(y, minY, (vsb.h - py) / z - bounds.h)
|
|
3502
|
+
if (z < zy) y = clamp(y, minY, (vsb.h - py) / z - bounds.h - bounds.y)
|
|
3526
3503
|
else y = clamp(y, minY + freeH, minY)
|
|
3527
3504
|
break
|
|
3528
3505
|
}
|
|
3529
3506
|
case 'outside': {
|
|
3530
|
-
y = clamp(y, py / z - bounds.h, (vsb.h - py) / z)
|
|
3507
|
+
y = clamp(y, py / z - bounds.h - bounds.y, (vsb.h - py) / z - bounds.y)
|
|
3531
3508
|
break
|
|
3532
3509
|
}
|
|
3533
3510
|
case 'free': {
|
|
@@ -3982,9 +3959,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3982
3959
|
const remaining = duration - elapsed
|
|
3983
3960
|
const t = easing(1 - remaining / duration)
|
|
3984
3961
|
|
|
3985
|
-
const left = start.minX
|
|
3986
|
-
const top = start.minY
|
|
3987
|
-
const right = start.maxX
|
|
3962
|
+
const left = lerp(start.minX, end.minX, t)
|
|
3963
|
+
const top = lerp(start.minY, end.minY, t)
|
|
3964
|
+
const right = lerp(start.maxX, end.maxX, t)
|
|
3988
3965
|
|
|
3989
3966
|
this._setCamera(new Vec(-left, -top, this.getViewportScreenBounds().width / (right - left)), {
|
|
3990
3967
|
force: true,
|
|
@@ -4099,9 +4076,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4099
4076
|
let newCy = cy + dy
|
|
4100
4077
|
let newCz = cz
|
|
4101
4078
|
|
|
4102
|
-
// animate zoom if z direction is passed in
|
|
4079
|
+
// animate zoom if z direction is passed in. Use an exponential factor so that a single
|
|
4080
|
+
// long frame can't drive the zoom to zero or negative (which would give NaN coordinates)
|
|
4103
4081
|
if (dirZ !== 0) {
|
|
4104
|
-
newCz = cz * (
|
|
4082
|
+
newCz = cz * Math.exp(dirZ * currentSpeed * elapsed)
|
|
4105
4083
|
// Adjust x/y to keep the viewport center fixed while zooming
|
|
4106
4084
|
const center = this.getViewportScreenCenter()
|
|
4107
4085
|
newCx += center.x / newCz - center.x / cz
|
|
@@ -4686,7 +4664,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4686
4664
|
let nextIndex = this.options.maxShapesPerPage * 2
|
|
4687
4665
|
let nextBackgroundIndex = this.options.maxShapesPerPage
|
|
4688
4666
|
|
|
4689
|
-
const erasingShapeIds = this.getErasingShapeIds()
|
|
4667
|
+
const erasingShapeIds = new Set(this.getErasingShapeIds())
|
|
4690
4668
|
|
|
4691
4669
|
const addShapeById = (id: TLShapeId, opacity: number, isAncestorErasing: boolean) => {
|
|
4692
4670
|
const shape = this.getShape(id)
|
|
@@ -4694,7 +4672,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4694
4672
|
|
|
4695
4673
|
if (this.isShapeHidden(shape)) {
|
|
4696
4674
|
// process children just in case they are overriding the hidden state
|
|
4697
|
-
const isErasing = isAncestorErasing || erasingShapeIds.
|
|
4675
|
+
const isErasing = isAncestorErasing || erasingShapeIds.has(id)
|
|
4698
4676
|
for (const childId of this.getSortedChildIdsForParent(id)) {
|
|
4699
4677
|
addShapeById(childId, opacity, isErasing)
|
|
4700
4678
|
}
|
|
@@ -4706,7 +4684,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4706
4684
|
const util = this.getShapeUtil(shape)
|
|
4707
4685
|
|
|
4708
4686
|
if (useEditorState) {
|
|
4709
|
-
isShapeErasing = !isAncestorErasing && erasingShapeIds.
|
|
4687
|
+
isShapeErasing = !isAncestorErasing && erasingShapeIds.has(id)
|
|
4710
4688
|
if (isShapeErasing) {
|
|
4711
4689
|
opacity *= 0.32
|
|
4712
4690
|
}
|
|
@@ -5465,11 +5443,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5465
5443
|
/** @internal */
|
|
5466
5444
|
@computed private _getShapePageBoundsCache(): ComputedCache<Box, TLShape> {
|
|
5467
5445
|
return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
|
|
5468
|
-
const pageTransform = this.getShapePageTransform(shape)
|
|
5469
|
-
if (!pageTransform) return undefined
|
|
5470
|
-
|
|
5471
5446
|
return Box.FromPoints(
|
|
5472
|
-
|
|
5447
|
+
this.getShapePageTransform(shape).applyToPoints(this.getShapeGeometry(shape).boundsVertices)
|
|
5473
5448
|
)
|
|
5474
5449
|
})
|
|
5475
5450
|
}
|
|
@@ -5608,7 +5583,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5608
5583
|
if (pageMask) {
|
|
5609
5584
|
if (pageMask.length === 0) return undefined
|
|
5610
5585
|
const { corners } = pageBounds
|
|
5611
|
-
|
|
5586
|
+
// the mask may have fewer than four points (e.g. a triangular clip path)
|
|
5587
|
+
if (
|
|
5588
|
+
pageMask.length === corners.length &&
|
|
5589
|
+
corners.every((p, i) => Vec.Equals(p, pageMask[i]))
|
|
5590
|
+
) {
|
|
5591
|
+
return pageBounds.clone()
|
|
5592
|
+
}
|
|
5612
5593
|
const intersection = intersectPolygonPolygon(pageMask, corners)
|
|
5613
5594
|
if (!intersection) return
|
|
5614
5595
|
return Box.FromPoints(intersection)
|
|
@@ -5862,6 +5843,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5862
5843
|
const margin = this.options.hitTestMargin / this.getZoomLevel()
|
|
5863
5844
|
const sortedShapes = this.getCurrentPageShapesSorted()
|
|
5864
5845
|
|
|
5846
|
+
// iterate from the top (highest z-index) to find the top-most matching shape
|
|
5865
5847
|
for (let i = sortedShapes.length - 1; i >= 0; i--) {
|
|
5866
5848
|
const shape = sortedShapes[i]
|
|
5867
5849
|
if (shape.type === 'group') continue
|
|
@@ -5911,44 +5893,43 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5911
5893
|
const searchMargin = Math.max(innerMargin, outerMargin, this.getHitTestMargin())
|
|
5912
5894
|
const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, searchMargin)
|
|
5913
5895
|
|
|
5914
|
-
const shapesToCheck =
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
: this.getCurrentPageShapesSorted()
|
|
5918
|
-
).filter((shape) => {
|
|
5919
|
-
// Frame-like shapes have labels positioned above the shape (outside bounds), so always include them
|
|
5920
|
-
if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) return false
|
|
5896
|
+
const shapesToCheck = opts.renderingOnly
|
|
5897
|
+
? this.getCurrentPageRenderingShapesSorted()
|
|
5898
|
+
: this.getCurrentPageShapesSorted()
|
|
5921
5899
|
|
|
5900
|
+
for (let i = shapesToCheck.length - 1; i >= 0; i--) {
|
|
5901
|
+
const shape = shapesToCheck[i]
|
|
5902
|
+
// Frame-like shapes have labels positioned above the shape (outside bounds), so always include them
|
|
5903
|
+
if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) continue
|
|
5922
5904
|
if (
|
|
5923
5905
|
(shape.isLocked && !hitLocked) ||
|
|
5924
5906
|
this.isShapeHidden(shape) ||
|
|
5925
5907
|
this.isShapeOfType(shape, 'group')
|
|
5926
|
-
)
|
|
5927
|
-
|
|
5908
|
+
) {
|
|
5909
|
+
continue
|
|
5910
|
+
}
|
|
5928
5911
|
const pageMask = this.getShapeMask(shape)
|
|
5929
|
-
if (pageMask && !pointInPolygon(point, pageMask))
|
|
5930
|
-
if (filter && !filter(shape))
|
|
5931
|
-
return true
|
|
5932
|
-
})
|
|
5912
|
+
if (pageMask && !pointInPolygon(point, pageMask)) continue
|
|
5913
|
+
if (filter && !filter(shape)) continue
|
|
5933
5914
|
|
|
5934
|
-
for (let i = shapesToCheck.length - 1; i >= 0; i--) {
|
|
5935
|
-
const shape = shapesToCheck[i]
|
|
5936
5915
|
const geometry = this.getShapeGeometry(shape)
|
|
5937
5916
|
const isGroup = geometry instanceof Group2d
|
|
5938
5917
|
|
|
5939
5918
|
const pointInShapeSpace = this.getPointInShapeSpace(shape, point)
|
|
5940
5919
|
|
|
5941
|
-
// Check labels first
|
|
5920
|
+
// Check labels first. Only group geometries can carry a label child; a frame-like
|
|
5921
|
+
// shape util may still return a plain geometry.
|
|
5942
5922
|
const shapeUtil = this.getShapeUtil(shape)
|
|
5943
5923
|
const isShapeFrameLike = this.isShapeFrameLike(shape)
|
|
5944
5924
|
if (
|
|
5945
|
-
|
|
5946
|
-
(
|
|
5947
|
-
this.isShapeOfType(shape, '
|
|
5948
|
-
|
|
5949
|
-
|
|
5925
|
+
isGroup &&
|
|
5926
|
+
(isShapeFrameLike ||
|
|
5927
|
+
((this.isShapeOfType(shape, 'note') ||
|
|
5928
|
+
this.isShapeOfType(shape, 'arrow') ||
|
|
5929
|
+
(this.isShapeOfType(shape, 'geo') && shape.props.fill === 'none')) &&
|
|
5930
|
+
shapeUtil.getText(shape)?.trim()))
|
|
5950
5931
|
) {
|
|
5951
|
-
for (const childGeometry of
|
|
5932
|
+
for (const childGeometry of geometry.children) {
|
|
5952
5933
|
if (childGeometry.isLabel && childGeometry.isPointInBounds(pointInShapeSpace)) {
|
|
5953
5934
|
return shape
|
|
5954
5935
|
}
|
|
@@ -6114,15 +6095,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6114
6095
|
const margin = opts.margin ?? 0
|
|
6115
6096
|
const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, margin)
|
|
6116
6097
|
|
|
6117
|
-
// Get all page shapes in z-index order and filter to candidates that pass isPointInShape
|
|
6118
|
-
// Frame-like shapes are always checked because their labels can be outside their bounds
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
.
|
|
6098
|
+
// Get all page shapes in z-index order and filter to candidates that pass isPointInShape.
|
|
6099
|
+
// Frame-like shapes are always checked because their labels can be outside their bounds.
|
|
6100
|
+
// Iterate backwards so the result is pre-sorted in reverse z-index order (top-most first).
|
|
6101
|
+
const sorted = this.getCurrentPageShapesSorted()
|
|
6102
|
+
const result: TLShape[] = []
|
|
6103
|
+
for (let i = sorted.length - 1; i >= 0; i--) {
|
|
6104
|
+
const shape = sorted[i]
|
|
6105
|
+
if (this.isShapeHidden(shape)) continue
|
|
6106
|
+
if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) continue
|
|
6107
|
+
if (this.isPointInShape(shape, point, opts)) result.push(shape)
|
|
6108
|
+
}
|
|
6109
|
+
return result
|
|
6126
6110
|
}
|
|
6127
6111
|
|
|
6128
6112
|
/**
|
|
@@ -6222,9 +6206,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6222
6206
|
if (!freshShape) return new Vec(0, 0)
|
|
6223
6207
|
if (isPageId(freshShape.parentId)) return Vec.From(point)
|
|
6224
6208
|
|
|
6225
|
-
|
|
6226
|
-
if (!parentTransform) return Vec.From(point)
|
|
6227
|
-
return parentTransform.clone().invert().applyToPoint(point)
|
|
6209
|
+
return this.getShapePageTransform(freshShape.parentId).clone().invert().applyToPoint(point)
|
|
6228
6210
|
}
|
|
6229
6211
|
|
|
6230
6212
|
/**
|
|
@@ -6391,26 +6373,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6391
6373
|
* @public
|
|
6392
6374
|
*/
|
|
6393
6375
|
isShapeInPage(shape: TLShape | TLShapeId, pageId = this.getCurrentPageId()): boolean {
|
|
6394
|
-
|
|
6395
|
-
const shapeToCheck = this.getShape(id)
|
|
6396
|
-
if (!shapeToCheck) return false
|
|
6397
|
-
|
|
6398
|
-
let shapeIsInPage = false
|
|
6399
|
-
|
|
6400
|
-
if (shapeToCheck.parentId === pageId) {
|
|
6401
|
-
shapeIsInPage = true
|
|
6402
|
-
} else {
|
|
6403
|
-
let parent = this.getShape(shapeToCheck.parentId)
|
|
6404
|
-
isInPageSearch: while (parent) {
|
|
6405
|
-
if (parent.parentId === pageId) {
|
|
6406
|
-
shapeIsInPage = true
|
|
6407
|
-
break isInPageSearch
|
|
6408
|
-
}
|
|
6409
|
-
parent = this.getShape(parent.parentId)
|
|
6410
|
-
}
|
|
6411
|
-
}
|
|
6412
|
-
|
|
6413
|
-
return shapeIsInPage
|
|
6376
|
+
return this.getAncestorPageId(shape) === pageId
|
|
6414
6377
|
}
|
|
6415
6378
|
|
|
6416
6379
|
/**
|
|
@@ -6521,13 +6484,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6521
6484
|
for (let i = 0; i < shapesToReparent.length; i++) {
|
|
6522
6485
|
const shape = shapesToReparent[i]
|
|
6523
6486
|
|
|
6524
|
-
const pageTransform = this.getShapePageTransform(shape)
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
const pagePoint = pageTransform.point()
|
|
6528
|
-
if (!pagePoint) continue
|
|
6529
|
-
|
|
6530
|
-
const newPoint = invertedParentTransform.applyToPoint(pagePoint)
|
|
6487
|
+
const pageTransform = this.getShapePageTransform(shape)
|
|
6488
|
+
const newPoint = invertedParentTransform.applyToPoint(pageTransform.point())
|
|
6531
6489
|
const newRotation = pageTransform.rotation() - parentPageRotation
|
|
6532
6490
|
|
|
6533
6491
|
if (shape.id === parentId) {
|
|
@@ -6628,7 +6586,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6628
6586
|
*/
|
|
6629
6587
|
getShapeAndDescendantIds(ids: TLShapeId[]): Set<TLShapeId> {
|
|
6630
6588
|
const shapeIds = new Set<TLShapeId>()
|
|
6631
|
-
for (const shape of ids.map((id) => this.getShape(id)
|
|
6589
|
+
for (const shape of compact(ids.map((id) => this.getShape(id))).sort(sortByIndex)) {
|
|
6632
6590
|
shapeIds.add(shape.id)
|
|
6633
6591
|
this.visitDescendants(shape, (descendantId) => {
|
|
6634
6592
|
shapeIds.add(descendantId)
|
|
@@ -6652,6 +6610,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6652
6610
|
const draggingShapes = compact(droppingShapes.map((s) => this.getShape(s))).filter(
|
|
6653
6611
|
(s) => !s.isLocked && !this.isShapeHidden(s)
|
|
6654
6612
|
)
|
|
6613
|
+
// Descendants of the dragged shapes can't be the target, otherwise dragging a frame out of
|
|
6614
|
+
// its parent reports a nested child as the target and the parent never sees the drag leave
|
|
6615
|
+
const excludedIds = this.getShapeAndDescendantIds(draggingShapes.map((s) => s.id))
|
|
6655
6616
|
|
|
6656
6617
|
const maybeDraggingOverShapes = this.getShapesAtPoint(point, {
|
|
6657
6618
|
hitInside: true,
|
|
@@ -6661,7 +6622,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6661
6622
|
!droppingShapes.includes(s) &&
|
|
6662
6623
|
!s.isLocked &&
|
|
6663
6624
|
!this.isShapeHidden(s) &&
|
|
6664
|
-
!
|
|
6625
|
+
!excludedIds.has(s.id)
|
|
6665
6626
|
)
|
|
6666
6627
|
|
|
6667
6628
|
for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
|
|
@@ -7035,8 +6996,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7035
6996
|
for (const id of ids) {
|
|
7036
6997
|
const shape = this.getShape(id)!
|
|
7037
6998
|
const localDelta = Vec.From(offset)
|
|
7038
|
-
|
|
7039
|
-
if (parentTransform) localDelta.rot(-parentTransform.rotation())
|
|
6999
|
+
localDelta.rot(-this.getShapeParentTransform(shape).rotation())
|
|
7040
7000
|
|
|
7041
7001
|
changes.push(this.getChangesToTranslateShape(shape, localDelta.add(shape)))
|
|
7042
7002
|
}
|
|
@@ -7106,7 +7066,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7106
7066
|
let ox = 0
|
|
7107
7067
|
let oy = 0
|
|
7108
7068
|
|
|
7109
|
-
|
|
7069
|
+
// Only offset the roots of the duplicated tree: a descendant that was also passed in
|
|
7070
|
+
// follows its duplicated parent, so offsetting it too would move it twice
|
|
7071
|
+
if (
|
|
7072
|
+
offset &&
|
|
7073
|
+
initialIds.has(originalId) &&
|
|
7074
|
+
!shapeIdSet.has(originalShape.parentId as TLShapeId)
|
|
7075
|
+
) {
|
|
7110
7076
|
const parentTransform = this.getShapeParentTransform(originalShape)
|
|
7111
7077
|
const vec = new Vec(offset.x, offset.y).rot(-parentTransform!.rotation())
|
|
7112
7078
|
ox = vec.x
|
|
@@ -7272,27 +7238,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7272
7238
|
|
|
7273
7239
|
if (this.getIsReadonly() || ids.length === 0) return this
|
|
7274
7240
|
|
|
7275
|
-
let allLocked = true
|
|
7276
|
-
allUnlocked = true
|
|
7241
|
+
let allLocked = true
|
|
7277
7242
|
const shapesToToggle: TLShape[] = []
|
|
7278
7243
|
for (const id of ids) {
|
|
7279
7244
|
const shape = this.getShape(id)
|
|
7280
7245
|
if (shape) {
|
|
7281
7246
|
shapesToToggle.push(shape)
|
|
7282
|
-
if (shape.isLocked)
|
|
7283
|
-
allUnlocked = false
|
|
7284
|
-
} else {
|
|
7285
|
-
allLocked = false
|
|
7286
|
-
}
|
|
7247
|
+
if (!shape.isLocked) allLocked = false
|
|
7287
7248
|
}
|
|
7288
7249
|
}
|
|
7289
7250
|
this.run(() => {
|
|
7290
|
-
if (
|
|
7291
|
-
this.updateShapes(
|
|
7292
|
-
shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
|
|
7293
|
-
)
|
|
7294
|
-
this.setSelectedShapes([])
|
|
7295
|
-
} else if (allLocked) {
|
|
7251
|
+
if (allLocked) {
|
|
7296
7252
|
this.updateShapes(
|
|
7297
7253
|
shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: false }))
|
|
7298
7254
|
)
|
|
@@ -7300,6 +7256,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7300
7256
|
this.updateShapes(
|
|
7301
7257
|
shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
|
|
7302
7258
|
)
|
|
7259
|
+
this.setSelectedShapes([])
|
|
7303
7260
|
}
|
|
7304
7261
|
})
|
|
7305
7262
|
|
|
@@ -7439,9 +7396,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7439
7396
|
|
|
7440
7397
|
// optionally filter to axis-aligned shapes (rotation is a multiple of 90 degrees)
|
|
7441
7398
|
if (opts?.filterAxisAligned) {
|
|
7442
|
-
freshShapes = freshShapes.filter(
|
|
7443
|
-
|
|
7444
|
-
|
|
7399
|
+
freshShapes = freshShapes.filter((s) => {
|
|
7400
|
+
// Page rotations come back through atan2 on a composed matrix, so a 270° shape can land
|
|
7401
|
+
// a hair either side of the multiple; an exact === 0 check drops it
|
|
7402
|
+
const remainder = Math.abs(this.getShapePageTransform(s).rotation() % HALF_PI)
|
|
7403
|
+
return Math.min(remainder, HALF_PI - remainder) < 1e-9
|
|
7404
|
+
})
|
|
7445
7405
|
}
|
|
7446
7406
|
|
|
7447
7407
|
const clusters: { shapes: TLShape[]; pageBounds: Box }[] = []
|
|
@@ -7662,13 +7622,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7662
7622
|
|
|
7663
7623
|
let shapeGap: number = 0
|
|
7664
7624
|
|
|
7625
|
+
// Stack in spatial order rather than input (z) order, otherwise shapes swap places
|
|
7626
|
+
shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])
|
|
7627
|
+
|
|
7665
7628
|
if (_gap === 0) {
|
|
7666
7629
|
// note: this is not used in the current tldraw.com; there we use a specified stack
|
|
7667
7630
|
|
|
7668
7631
|
const gaps: Record<number, number> = {}
|
|
7669
7632
|
|
|
7670
|
-
shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])
|
|
7671
|
-
|
|
7672
7633
|
// Collect all of the gaps between shapes. We want to find
|
|
7673
7634
|
// patterns (equal gaps between shapes) and use the most common
|
|
7674
7635
|
// one as the gap for all of the shapes.
|
|
@@ -7721,8 +7682,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7721
7682
|
// todo: ensure that the parent isn't being aligned together with its children
|
|
7722
7683
|
const parent = this.getShapeParent(shape)
|
|
7723
7684
|
if (parent) {
|
|
7724
|
-
|
|
7725
|
-
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
7685
|
+
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
7726
7686
|
}
|
|
7727
7687
|
|
|
7728
7688
|
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
@@ -7772,10 +7732,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7772
7732
|
|
|
7773
7733
|
const maxWidth = commonBounds.width
|
|
7774
7734
|
|
|
7775
|
-
// sort the shape clusters by width and then height, descending
|
|
7735
|
+
// sort the shape clusters by width and then height, descending: potpack fills each row's
|
|
7736
|
+
// right-hand space with the shapes that follow, so they must be no taller than the row
|
|
7776
7737
|
shapeClustersToPack
|
|
7777
|
-
.sort((a, b) =>
|
|
7778
|
-
.sort((a, b) =>
|
|
7738
|
+
.sort((a, b) => b.pageBounds.width - a.pageBounds.width)
|
|
7739
|
+
.sort((a, b) => b.pageBounds.height - a.pageBounds.height)
|
|
7779
7740
|
|
|
7780
7741
|
// Start with is (sort of) the square of the area
|
|
7781
7742
|
const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth)
|
|
@@ -7845,8 +7806,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7845
7806
|
|
|
7846
7807
|
const parent = this.getShapeParent(shape)
|
|
7847
7808
|
if (parent) {
|
|
7848
|
-
|
|
7849
|
-
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
7809
|
+
shapeDelta.rot(-this.getShapeParentTransform(shape).rotation())
|
|
7850
7810
|
}
|
|
7851
7811
|
|
|
7852
7812
|
shapeDelta.add(shape)
|
|
@@ -7936,8 +7896,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7936
7896
|
// todo: ensure that the parent isn't being aligned together with its children
|
|
7937
7897
|
const parent = this.getShapeParent(shape)
|
|
7938
7898
|
if (parent) {
|
|
7939
|
-
|
|
7940
|
-
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
7899
|
+
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
7941
7900
|
}
|
|
7942
7901
|
|
|
7943
7902
|
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
@@ -8037,8 +7996,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8037
7996
|
// todo: ensure that the parent isn't being aligned together with its children
|
|
8038
7997
|
const parent = this.getShapeParent(shape)
|
|
8039
7998
|
if (parent) {
|
|
8040
|
-
|
|
8041
|
-
if (parentTransform) shapeDelta.rot(-parentTransform.rotation())
|
|
7999
|
+
shapeDelta.rot(-this.getShapePageTransform(parent).rotation())
|
|
8042
8000
|
}
|
|
8043
8001
|
|
|
8044
8002
|
shapeDelta.add(shape) // add the shape's x and y to the delta
|
|
@@ -8106,8 +8064,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8106
8064
|
for (const shape of shapes) {
|
|
8107
8065
|
// First translate
|
|
8108
8066
|
const shapeLocalOffset = localOffset.clone()
|
|
8109
|
-
|
|
8110
|
-
if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation())
|
|
8067
|
+
shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation())
|
|
8111
8068
|
shapeLocalOffset.add(shape)
|
|
8112
8069
|
const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset)
|
|
8113
8070
|
this.updateShape(changes)
|
|
@@ -8178,8 +8135,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8178
8135
|
for (const shape of shapes) {
|
|
8179
8136
|
// First translate
|
|
8180
8137
|
const shapeLocalOffset = localOffset.clone()
|
|
8181
|
-
|
|
8182
|
-
if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation())
|
|
8138
|
+
shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation())
|
|
8183
8139
|
shapeLocalOffset.add(shape)
|
|
8184
8140
|
const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset)
|
|
8185
8141
|
this.updateShape(changes)
|
|
@@ -8243,12 +8199,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8243
8199
|
const pageTransform = opts.initialPageTransform
|
|
8244
8200
|
? Mat.Cast(opts.initialPageTransform)
|
|
8245
8201
|
: this.getShapePageTransform(id)
|
|
8246
|
-
if (!pageTransform) return null
|
|
8247
8202
|
|
|
8248
8203
|
const pageRotation = pageTransform.rotation()
|
|
8249
8204
|
|
|
8250
|
-
if (pageRotation == null) return null
|
|
8251
|
-
|
|
8252
8205
|
const scaleAxisRotation = opts.scaleAxisRotation ?? pageRotation
|
|
8253
8206
|
|
|
8254
8207
|
const initialBounds = opts.initialBounds ?? this.getShapeGeometry(id).bounds
|
|
@@ -8485,7 +8438,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8485
8438
|
)
|
|
8486
8439
|
|
|
8487
8440
|
// now calculate how far away the shape is from where it needs to be
|
|
8488
|
-
const pageTransform = this.getShapePageTransform(id)
|
|
8441
|
+
const pageTransform = this.getShapePageTransform(id)
|
|
8489
8442
|
// We need to use the local bounds center transformed to page space, not the axis-aligned
|
|
8490
8443
|
// page bounds center. This is because the page bounds are axis-aligned and their center
|
|
8491
8444
|
// changes when the rotation changes, but we want to use the same reference point as
|
|
@@ -8493,7 +8446,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8493
8446
|
const currentLocalBounds = this.getShapeGeometry(id).bounds
|
|
8494
8447
|
const currentPageCenter = Mat.applyToPoint(pageTransform, currentLocalBounds.center)
|
|
8495
8448
|
const shapePageTransformOrigin = pageTransform.point()
|
|
8496
|
-
if (!currentPageCenter || !shapePageTransformOrigin) return this
|
|
8497
8449
|
const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter)
|
|
8498
8450
|
|
|
8499
8451
|
// and finally figure out what the shape's new position should be
|
|
@@ -8881,10 +8833,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8881
8833
|
|
|
8882
8834
|
updates.push({
|
|
8883
8835
|
...end,
|
|
8884
|
-
x: start.x
|
|
8885
|
-
y: start.y
|
|
8886
|
-
opacity: start.opacity
|
|
8887
|
-
rotation: start.rotation
|
|
8836
|
+
x: lerp(start.x, end.x, t),
|
|
8837
|
+
y: lerp(start.y, end.y, t),
|
|
8838
|
+
opacity: lerp(start.opacity, end.opacity, t),
|
|
8839
|
+
rotation: lerp(start.rotation, end.rotation, t),
|
|
8888
8840
|
props: this.getShapeUtil(end).getInterpolatedProps?.(start, end, t) ?? end.props,
|
|
8889
8841
|
})
|
|
8890
8842
|
}
|
|
@@ -9100,7 +9052,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9100
9052
|
* @public
|
|
9101
9053
|
*/
|
|
9102
9054
|
updateShapes<T extends TLShape>(partials: (TLShapePartial<T> | null | undefined)[]) {
|
|
9103
|
-
const compactedPartials: TLShapePartial<T>[] =
|
|
9055
|
+
const compactedPartials: TLShapePartial<T>[] = []
|
|
9104
9056
|
|
|
9105
9057
|
for (let i = 0, n = partials.length; i < n; i++) {
|
|
9106
9058
|
const partial = partials[i]
|
|
@@ -9246,26 +9198,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9246
9198
|
/* --------------------- Styles --------------------- */
|
|
9247
9199
|
|
|
9248
9200
|
/**
|
|
9249
|
-
*
|
|
9201
|
+
* Groups have no styles of their own: a style read or write on a selection applies to the
|
|
9202
|
+
* non-group shapes beneath each group, however deeply nested. Returns those shapes.
|
|
9250
9203
|
*
|
|
9251
9204
|
* @internal
|
|
9252
9205
|
*/
|
|
9253
|
-
private
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
}
|
|
9264
|
-
} else {
|
|
9265
|
-
for (const [style, propKey] of this.styleProps[shape.type]) {
|
|
9266
|
-
sharedStyleMap.applyValue(style, getOwnProperty(shape.props, propKey))
|
|
9206
|
+
private _getStyleableShapes(shapes: TLShape[]): TLShape[] {
|
|
9207
|
+
const result: TLShape[] = []
|
|
9208
|
+
const visit = (shape: TLShape) => {
|
|
9209
|
+
if (this.isShapeOfType(shape, 'group')) {
|
|
9210
|
+
for (const childId of this.getSortedChildIdsForParent(shape.id)) {
|
|
9211
|
+
const child = this.getShape(childId)
|
|
9212
|
+
if (child) visit(child)
|
|
9213
|
+
}
|
|
9214
|
+
} else {
|
|
9215
|
+
result.push(shape)
|
|
9267
9216
|
}
|
|
9268
9217
|
}
|
|
9218
|
+
for (const shape of shapes) visit(shape)
|
|
9219
|
+
return result
|
|
9269
9220
|
}
|
|
9270
9221
|
|
|
9271
9222
|
/**
|
|
@@ -9275,11 +9226,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9275
9226
|
*/
|
|
9276
9227
|
@computed
|
|
9277
9228
|
private _getSelectionSharedStyles(): ReadonlySharedStyleMap {
|
|
9278
|
-
const selectedShapes = this.getSelectedShapes()
|
|
9279
|
-
|
|
9280
9229
|
const sharedStyles = new SharedStyleMap()
|
|
9281
|
-
for (const
|
|
9282
|
-
|
|
9230
|
+
for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
|
|
9231
|
+
for (const [style, propKey] of this.styleProps[shape.type]) {
|
|
9232
|
+
sharedStyles.applyValue(style, getOwnProperty(shape.props, propKey))
|
|
9233
|
+
}
|
|
9283
9234
|
}
|
|
9284
9235
|
|
|
9285
9236
|
return sharedStyles
|
|
@@ -9337,18 +9288,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9337
9288
|
if (!currentTool) return styles
|
|
9338
9289
|
|
|
9339
9290
|
if (currentTool.shapeType) {
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
!(this.getShapeUtil('frame')!.options as any).showColors
|
|
9343
|
-
) {
|
|
9344
|
-
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
|
9345
|
-
if (style.id === 'tldraw:color') continue
|
|
9346
|
-
styles.applyValue(style, this.getStyleForNextShape(style))
|
|
9347
|
-
}
|
|
9348
|
-
} else {
|
|
9349
|
-
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
|
9350
|
-
styles.applyValue(style, this.getStyleForNextShape(style))
|
|
9351
|
-
}
|
|
9291
|
+
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
|
9292
|
+
styles.applyValue(style, this.getStyleForNextShape(style))
|
|
9352
9293
|
}
|
|
9353
9294
|
}
|
|
9354
9295
|
|
|
@@ -9364,27 +9305,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9364
9305
|
*/
|
|
9365
9306
|
@computed getSharedOpacity(): SharedStyle<number> {
|
|
9366
9307
|
if (this.isIn('select') && this.getSelectedShapeIds().length > 0) {
|
|
9367
|
-
const shapesToCheck: TLShape[] = []
|
|
9368
|
-
const addShape = (shapeId: TLShapeId) => {
|
|
9369
|
-
const shape = this.getShape(shapeId)
|
|
9370
|
-
if (!shape) return
|
|
9371
|
-
// For groups, ignore the opacity of the group shape and instead include
|
|
9372
|
-
// the opacity of the group's children. These are the shapes that would have
|
|
9373
|
-
// their opacity changed if the user called `setOpacity` on the current selection.
|
|
9374
|
-
if (this.isShapeOfType(shape, 'group')) {
|
|
9375
|
-
for (const childId of this.getSortedChildIdsForParent(shape.id)) {
|
|
9376
|
-
addShape(childId)
|
|
9377
|
-
}
|
|
9378
|
-
} else {
|
|
9379
|
-
shapesToCheck.push(shape)
|
|
9380
|
-
}
|
|
9381
|
-
}
|
|
9382
|
-
for (const shapeId of this.getSelectedShapeIds()) {
|
|
9383
|
-
addShape(shapeId)
|
|
9384
|
-
}
|
|
9385
|
-
|
|
9386
9308
|
let opacity: number | null = null
|
|
9387
|
-
for (const shape of
|
|
9309
|
+
for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
|
|
9388
9310
|
if (opacity === null) {
|
|
9389
9311
|
opacity = shape.opacity
|
|
9390
9312
|
} else if (opacity !== shape.opacity) {
|
|
@@ -9427,33 +9349,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9427
9349
|
const selectedShapes = this.getSelectedShapes()
|
|
9428
9350
|
|
|
9429
9351
|
if (selectedShapes.length > 0) {
|
|
9430
|
-
const shapesToUpdate: TLShape[] = []
|
|
9431
|
-
|
|
9432
|
-
// We can have many deep levels of grouped shape
|
|
9433
|
-
// Making a recursive function to look through all the levels
|
|
9434
|
-
const addShapeById = (shape: TLShape) => {
|
|
9435
|
-
if (this.isShapeOfType(shape, 'group')) {
|
|
9436
|
-
const childIds = this.getSortedChildIdsForParent(shape)
|
|
9437
|
-
for (const childId of childIds) {
|
|
9438
|
-
addShapeById(this.getShape(childId)!)
|
|
9439
|
-
}
|
|
9440
|
-
} else {
|
|
9441
|
-
shapesToUpdate.push(shape)
|
|
9442
|
-
}
|
|
9443
|
-
}
|
|
9444
|
-
|
|
9445
|
-
for (const id of selectedShapes) {
|
|
9446
|
-
addShapeById(id)
|
|
9447
|
-
}
|
|
9448
|
-
|
|
9449
9352
|
this.updateShapes(
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
}
|
|
9456
|
-
})
|
|
9353
|
+
this._getStyleableShapes(selectedShapes).map((shape) => ({
|
|
9354
|
+
id: shape.id,
|
|
9355
|
+
type: shape.type,
|
|
9356
|
+
opacity,
|
|
9357
|
+
}))
|
|
9457
9358
|
)
|
|
9458
9359
|
}
|
|
9459
9360
|
|
|
@@ -9507,43 +9408,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9507
9408
|
const selectedShapes = this.getSelectedShapes()
|
|
9508
9409
|
|
|
9509
9410
|
if (selectedShapes.length > 0) {
|
|
9510
|
-
const updates:
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
if (this.isShapeOfType(shape, 'group')) {
|
|
9520
|
-
const childIds = this.getSortedChildIdsForParent(shape.id)
|
|
9521
|
-
for (const childId of childIds) {
|
|
9522
|
-
addShapeById(this.getShape(childId)!)
|
|
9523
|
-
}
|
|
9524
|
-
} else {
|
|
9525
|
-
const util = this.getShapeUtil(shape)
|
|
9526
|
-
const stylePropKey = this.styleProps[shape.type].get(style)
|
|
9527
|
-
if (stylePropKey) {
|
|
9528
|
-
const shapePartial: TLShapePartial = {
|
|
9529
|
-
id: shape.id,
|
|
9530
|
-
type: shape.type,
|
|
9531
|
-
props: { [stylePropKey]: value },
|
|
9532
|
-
}
|
|
9533
|
-
updates.push({
|
|
9534
|
-
util,
|
|
9535
|
-
originalShape: shape,
|
|
9536
|
-
updatePartial: shapePartial,
|
|
9537
|
-
})
|
|
9538
|
-
}
|
|
9411
|
+
const updates: TLShapePartial[] = []
|
|
9412
|
+
for (const shape of this._getStyleableShapes(selectedShapes)) {
|
|
9413
|
+
const stylePropKey = this.styleProps[shape.type].get(style)
|
|
9414
|
+
if (stylePropKey) {
|
|
9415
|
+
updates.push({
|
|
9416
|
+
id: shape.id,
|
|
9417
|
+
type: shape.type,
|
|
9418
|
+
props: { [stylePropKey]: value },
|
|
9419
|
+
})
|
|
9539
9420
|
}
|
|
9540
9421
|
}
|
|
9541
9422
|
|
|
9542
|
-
|
|
9543
|
-
addShapeById(shape)
|
|
9544
|
-
}
|
|
9545
|
-
|
|
9546
|
-
this.updateShapes(updates.map(({ updatePartial }) => updatePartial))
|
|
9423
|
+
this.updateShapes(updates)
|
|
9547
9424
|
}
|
|
9548
9425
|
|
|
9549
9426
|
return this
|
|
@@ -10075,7 +9952,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10075
9952
|
if (shapeIdMap.has(newShape.parentId)) {
|
|
10076
9953
|
newShape.parentId = shapeIdMap.get(oldShape.parentId)!
|
|
10077
9954
|
} else {
|
|
10078
|
-
rootShapeIds.push(newShape.id)
|
|
10079
9955
|
// newShape.parentId = pasteParentId
|
|
10080
9956
|
newShape.index = index
|
|
10081
9957
|
index = getIndexAbove(index)
|
|
@@ -10118,10 +9994,14 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10118
9994
|
(asset.type === 'video' && asset.props.src?.startsWith('data:video'))
|
|
10119
9995
|
) {
|
|
10120
9996
|
// it's src is a base64 image or video; we need to create a new asset without the src,
|
|
10121
|
-
// then create a new asset from the original src. So we
|
|
10122
|
-
//
|
|
10123
|
-
|
|
10124
|
-
asset
|
|
9997
|
+
// then create a new asset from the original src. So we keep the original asset for the
|
|
9998
|
+
// upload and create a copy with its src removed. Copy rather than mutate: when no
|
|
9999
|
+
// migration applies, migrateStoreSnapshot hands back the caller's own record objects.
|
|
10000
|
+
assetsToUpdate.push(asset as TLImageAsset | TLVideoAsset)
|
|
10001
|
+
const assetWithoutSrc = structuredClone(asset as TLImageAsset | TLVideoAsset)
|
|
10002
|
+
assetWithoutSrc.props.src = null
|
|
10003
|
+
assetsToCreate.push(assetWithoutSrc)
|
|
10004
|
+
continue
|
|
10125
10005
|
}
|
|
10126
10006
|
|
|
10127
10007
|
// Add the asset to the list of assets to create
|
|
@@ -11116,7 +10996,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
11116
10996
|
if (info.name === 'cancel' || info.name === 'complete') {
|
|
11117
10997
|
this.inputs.setIsDragging(false)
|
|
11118
10998
|
|
|
11119
|
-
|
|
10999
|
+
// A pan owned by a held spacebar outlives the cancelled interaction;
|
|
11000
|
+
// key_up ends it. Otherwise Escape mid-pan stops the camera until
|
|
11001
|
+
// the user releases and re-presses Space (#10446).
|
|
11002
|
+
if (this.inputs.getIsPanning() && !this.inputs.keys.has('Space')) {
|
|
11120
11003
|
this.inputs.setIsPanning(false)
|
|
11121
11004
|
this.inputs.setIsSpacebarPanning(false)
|
|
11122
11005
|
this.setCursor({ type: this._prevCursor, rotation: 0 })
|
|
@@ -11870,10 +11753,10 @@ function withIsolatedShapes<T>(
|
|
|
11870
11753
|
|
|
11871
11754
|
function getCameraFitXFitY(editor: Editor, cameraOptions: TLCameraOptions) {
|
|
11872
11755
|
if (!cameraOptions.constraints) throw Error('Should have constraints here')
|
|
11873
|
-
const {
|
|
11874
|
-
padding: { x: px, y: py },
|
|
11875
|
-
} = cameraOptions.constraints
|
|
11876
11756
|
const vsb = editor.getViewportScreenBounds()
|
|
11757
|
+
// Clamp padding to half the viewport size on either dimension, as getConstrainedCamera does
|
|
11758
|
+
const px = Math.min(cameraOptions.constraints.padding.x, vsb.w / 2)
|
|
11759
|
+
const py = Math.min(cameraOptions.constraints.padding.y, vsb.h / 2)
|
|
11877
11760
|
const bounds = Box.From(cameraOptions.constraints.bounds)
|
|
11878
11761
|
const zx = (vsb.w - px * 2) / bounds.w
|
|
11879
11762
|
const zy = (vsb.h - py * 2) / bounds.h
|