@tldraw/editor 3.8.0-canary.e81e3057aaac → 3.8.0-canary.f1c6d1ad347c
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 +253 -62
- package/dist-cjs/index.js +15 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +2 -5
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +4 -2
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +98 -23
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager.js +1 -0
- package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js +66 -0
- package/dist-cjs/lib/editor/shapes/shared/resizeScaled.js.map +7 -0
- package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
- package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgAsImage.js +83 -0
- package/dist-cjs/lib/exports/getSvgAsImage.js.map +7 -0
- package/dist-cjs/lib/exports/getSvgJsx.js +16 -3
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +1 -3
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useLocalStore.js +1 -1
- package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -0
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +3 -3
- package/dist-cjs/lib/options.js +2 -2
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js +75 -0
- package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +7 -0
- package/dist-cjs/lib/utils/dom.js +6 -0
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +3 -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 +253 -62
- package/dist-esm/index.mjs +9 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +2 -5
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +4 -2
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +98 -23
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager.mjs +1 -0
- package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs +46 -0
- package/dist-esm/lib/editor/shapes/shared/resizeScaled.mjs.map +7 -0
- package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgAsImage.mjs +63 -0
- package/dist-esm/lib/exports/getSvgAsImage.mjs.map +7 -0
- package/dist-esm/lib/exports/getSvgJsx.mjs +16 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +2 -4
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useLocalStore.mjs +1 -1
- package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -0
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +3 -3
- package/dist-esm/lib/options.mjs +2 -2
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +45 -0
- package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +7 -0
- package/dist-esm/lib/utils/dom.mjs +6 -0
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +3 -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 +2 -1
- package/package.json +22 -20
- package/src/index.ts +20 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +2 -5
- package/src/lib/config/TLSessionStateSnapshot.ts +3 -1
- package/src/lib/config/createTLStore.ts +4 -2
- package/src/lib/editor/Editor.ts +147 -57
- package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +4 -4
- package/src/lib/editor/managers/TextManager.ts +1 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +30 -1
- package/src/lib/editor/shapes/shared/resizeScaled.ts +61 -0
- package/src/lib/editor/types/SvgExportContext.tsx +21 -0
- package/src/lib/editor/types/emit-types.ts +1 -0
- package/src/lib/editor/types/external-content.ts +90 -50
- package/src/lib/editor/types/misc-types.ts +55 -2
- package/src/lib/exports/StyleEmbedder.ts +1 -1
- package/src/lib/exports/exportToSvg.tsx +2 -2
- package/src/lib/exports/getSvgAsImage.ts +92 -0
- package/src/lib/exports/getSvgJsx.tsx +17 -2
- package/src/lib/hooks/useDocumentEvents.ts +2 -11
- package/src/lib/hooks/useLocalStore.ts +1 -1
- package/src/lib/hooks/usePassThroughWheelEvents.ts +7 -0
- package/src/lib/options.ts +5 -2
- package/src/lib/utils/browserCanvasMaxSize.ts +65 -0
- package/src/lib/utils/dom.ts +12 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +3 -1
- package/src/version.ts +3 -3
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -104,6 +104,7 @@ import {
|
|
|
104
104
|
ZOOM_TO_FIT_PADDING,
|
|
105
105
|
} from '../constants'
|
|
106
106
|
import { exportToSvg } from '../exports/exportToSvg'
|
|
107
|
+
import { getSvgAsImage } from '../exports/getSvgAsImage'
|
|
107
108
|
import { tlenv } from '../globals/environment'
|
|
108
109
|
import { tlmenus } from '../globals/menus'
|
|
109
110
|
import { tltime } from '../globals/time'
|
|
@@ -154,7 +155,7 @@ import {
|
|
|
154
155
|
TLPointerEventInfo,
|
|
155
156
|
TLWheelEventInfo,
|
|
156
157
|
} from './types/event-types'
|
|
157
|
-
import {
|
|
158
|
+
import { TLExternalAsset, TLExternalContent } from './types/external-content'
|
|
158
159
|
import { TLHistoryBatchOptions } from './types/history-types'
|
|
159
160
|
import {
|
|
160
161
|
OptionalKeys,
|
|
@@ -162,6 +163,7 @@ import {
|
|
|
162
163
|
TLCameraMoveOptions,
|
|
163
164
|
TLCameraOptions,
|
|
164
165
|
TLImageExportOptions,
|
|
166
|
+
TLSvgExportOptions,
|
|
165
167
|
} from './types/misc-types'
|
|
166
168
|
import { TLResizeHandle } from './types/selection-types'
|
|
167
169
|
|
|
@@ -927,6 +929,21 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
927
929
|
return shapeUtil
|
|
928
930
|
}
|
|
929
931
|
|
|
932
|
+
/**
|
|
933
|
+
* Returns true if the editor has a shape util for the given shape / shape type.
|
|
934
|
+
*
|
|
935
|
+
* @param shape - A shape, shape partial, or shape type.
|
|
936
|
+
*/
|
|
937
|
+
hasShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): boolean
|
|
938
|
+
hasShapeUtil<S extends TLUnknownShape>(type: S['type']): boolean
|
|
939
|
+
hasShapeUtil<T extends ShapeUtil>(
|
|
940
|
+
type: T extends ShapeUtil<infer R> ? R['type'] : string
|
|
941
|
+
): boolean
|
|
942
|
+
hasShapeUtil(arg: string | { type: string }): boolean {
|
|
943
|
+
const type = typeof arg === 'string' ? arg : arg.type
|
|
944
|
+
return hasOwnProperty(this.shapeUtils, type)
|
|
945
|
+
}
|
|
946
|
+
|
|
930
947
|
/* ------------------- Binding Utils ------------------ */
|
|
931
948
|
/**
|
|
932
949
|
* A map of shape utility classes (TLShapeUtils) by shape type.
|
|
@@ -1218,18 +1235,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1218
1235
|
}
|
|
1219
1236
|
|
|
1220
1237
|
/** @internal */
|
|
1221
|
-
createErrorAnnotations(
|
|
1222
|
-
origin: string,
|
|
1223
|
-
willCrashApp: boolean | 'unknown'
|
|
1224
|
-
): {
|
|
1225
|
-
tags: { origin: string; willCrashApp: boolean | 'unknown' }
|
|
1226
|
-
extras: {
|
|
1227
|
-
activeStateNode?: string
|
|
1228
|
-
selectedShapes?: TLUnknownShape[]
|
|
1229
|
-
editingShape?: TLUnknownShape
|
|
1230
|
-
inputs?: Record<string, unknown>
|
|
1231
|
-
}
|
|
1232
|
-
} {
|
|
1238
|
+
createErrorAnnotations(origin: string, willCrashApp: boolean | 'unknown') {
|
|
1233
1239
|
try {
|
|
1234
1240
|
const editingShapeId = this.getEditingShapeId()
|
|
1235
1241
|
return {
|
|
@@ -1239,9 +1245,20 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1239
1245
|
},
|
|
1240
1246
|
extras: {
|
|
1241
1247
|
activeStateNode: this.root.getPath(),
|
|
1242
|
-
selectedShapes: this.getSelectedShapes()
|
|
1248
|
+
selectedShapes: this.getSelectedShapes().map((s) => {
|
|
1249
|
+
const { props, ...rest } = s
|
|
1250
|
+
const { text: _text, richText: _richText, ...restProps } = props as any
|
|
1251
|
+
return {
|
|
1252
|
+
...rest,
|
|
1253
|
+
props: restProps,
|
|
1254
|
+
}
|
|
1255
|
+
}),
|
|
1256
|
+
selectionCount: this.getSelectedShapes().length,
|
|
1243
1257
|
editingShape: editingShapeId ? this.getShape(editingShapeId) : undefined,
|
|
1244
1258
|
inputs: this.inputs,
|
|
1259
|
+
pageState: this.getCurrentPageState(),
|
|
1260
|
+
instanceState: this.getInstanceState(),
|
|
1261
|
+
collaboratorCount: this.getCollaboratorsOnCurrentPage().length,
|
|
1245
1262
|
},
|
|
1246
1263
|
}
|
|
1247
1264
|
} catch {
|
|
@@ -1383,8 +1400,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1383
1400
|
*
|
|
1384
1401
|
* @example
|
|
1385
1402
|
* ```ts
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1403
|
+
* editor.getStateDescendant('select')
|
|
1404
|
+
* editor.getStateDescendant('select.brushing')
|
|
1388
1405
|
* ```
|
|
1389
1406
|
*
|
|
1390
1407
|
* @param path - The descendant's path of state ids, separated by periods.
|
|
@@ -1458,10 +1475,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1458
1475
|
if (partial.isChangingStyle !== undefined) {
|
|
1459
1476
|
clearTimeout(this._isChangingStyleTimeout)
|
|
1460
1477
|
if (partial.isChangingStyle === true) {
|
|
1461
|
-
// If we've set to true, set a new reset timeout to change the value back to false after
|
|
1478
|
+
// If we've set to true, set a new reset timeout to change the value back to false after 1 seconds
|
|
1462
1479
|
this._isChangingStyleTimeout = this.timers.setTimeout(() => {
|
|
1463
1480
|
this._updateInstanceState({ isChangingStyle: false }, { history: 'ignore' })
|
|
1464
|
-
},
|
|
1481
|
+
}, 1000)
|
|
1465
1482
|
}
|
|
1466
1483
|
}
|
|
1467
1484
|
|
|
@@ -1664,7 +1681,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1664
1681
|
* @public
|
|
1665
1682
|
*/
|
|
1666
1683
|
isAncestorSelected(shape: TLShape | TLShapeId): boolean {
|
|
1667
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
1684
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
1668
1685
|
const _shape = this.getShape(id)
|
|
1669
1686
|
if (!_shape) return false
|
|
1670
1687
|
const selectedShapeIds = this.getSelectedShapeIds()
|
|
@@ -1921,7 +1938,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1921
1938
|
* @public
|
|
1922
1939
|
*/
|
|
1923
1940
|
setFocusedGroup(shape: TLShapeId | TLGroupShape | null): this {
|
|
1924
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
1941
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
1925
1942
|
|
|
1926
1943
|
if (id !== null) {
|
|
1927
1944
|
const shape = this.getShape(id)
|
|
@@ -2004,7 +2021,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2004
2021
|
* @public
|
|
2005
2022
|
*/
|
|
2006
2023
|
setEditingShape(shape: TLShapeId | TLShape | null): this {
|
|
2007
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
2024
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2008
2025
|
if (id !== this.getEditingShapeId()) {
|
|
2009
2026
|
if (id) {
|
|
2010
2027
|
const shape = this.getShape(id)
|
|
@@ -2065,7 +2082,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2065
2082
|
* @public
|
|
2066
2083
|
*/
|
|
2067
2084
|
setHoveredShape(shape: TLShapeId | TLShape | null): this {
|
|
2068
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
2085
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2069
2086
|
if (id === this.getHoveredShapeId()) return this
|
|
2070
2087
|
this.run(
|
|
2071
2088
|
() => {
|
|
@@ -2214,7 +2231,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2214
2231
|
* @public
|
|
2215
2232
|
*/
|
|
2216
2233
|
setCroppingShape(shape: TLShapeId | TLShape | null): this {
|
|
2217
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
2234
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2218
2235
|
if (id !== this.getCroppingShapeId()) {
|
|
2219
2236
|
this.run(
|
|
2220
2237
|
() => {
|
|
@@ -4145,20 +4162,24 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4145
4162
|
context: {
|
|
4146
4163
|
screenScale?: number
|
|
4147
4164
|
shouldResolveToOriginal?: boolean
|
|
4165
|
+
dpr?: number
|
|
4148
4166
|
}
|
|
4149
4167
|
): Promise<string | null> {
|
|
4150
4168
|
if (!assetId) return null
|
|
4151
4169
|
const asset = this.getAsset(assetId)
|
|
4152
4170
|
if (!asset) return null
|
|
4153
4171
|
|
|
4154
|
-
const {
|
|
4172
|
+
const {
|
|
4173
|
+
screenScale = 1,
|
|
4174
|
+
shouldResolveToOriginal = false,
|
|
4175
|
+
dpr = this.getInstanceState().devicePixelRatio,
|
|
4176
|
+
} = context
|
|
4155
4177
|
|
|
4156
4178
|
// We only look at the zoom level at powers of 2.
|
|
4157
4179
|
const zoomStepFunction = (zoom: number) => Math.pow(2, Math.ceil(Math.log2(zoom)))
|
|
4158
|
-
const steppedScreenScale =
|
|
4180
|
+
const steppedScreenScale = zoomStepFunction(screenScale)
|
|
4159
4181
|
const networkEffectiveType: string | null =
|
|
4160
4182
|
'connection' in navigator ? (navigator as any).connection.effectiveType : null
|
|
4161
|
-
const dpr = this.getInstanceState().devicePixelRatio
|
|
4162
4183
|
|
|
4163
4184
|
return await this.store.props.assets.resolve(asset, {
|
|
4164
4185
|
screenScale: screenScale || 1,
|
|
@@ -4172,7 +4193,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4172
4193
|
* Upload an asset to the store's asset service, returning a URL that can be used to resolve the
|
|
4173
4194
|
* asset.
|
|
4174
4195
|
*/
|
|
4175
|
-
async uploadAsset(
|
|
4196
|
+
async uploadAsset(
|
|
4197
|
+
asset: TLAsset,
|
|
4198
|
+
file: File,
|
|
4199
|
+
abortSignal?: AbortSignal
|
|
4200
|
+
): Promise<{ src: string; meta?: JsonObject }> {
|
|
4176
4201
|
return await this.store.props.assets.upload(asset, file, abortSignal)
|
|
4177
4202
|
}
|
|
4178
4203
|
|
|
@@ -6750,6 +6775,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6750
6775
|
}
|
|
6751
6776
|
}
|
|
6752
6777
|
|
|
6778
|
+
let didResize = false
|
|
6779
|
+
|
|
6753
6780
|
if (util.onResize && util.canResize(initialShape)) {
|
|
6754
6781
|
// get the model changes from the shape util
|
|
6755
6782
|
const newPagePoint = this._scalePagePoint(
|
|
@@ -6788,24 +6815,30 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6788
6815
|
)
|
|
6789
6816
|
}
|
|
6790
6817
|
|
|
6818
|
+
const resizedShape = util.onResize(
|
|
6819
|
+
{ ...initialShape, x, y },
|
|
6820
|
+
{
|
|
6821
|
+
newPoint: newLocalPoint,
|
|
6822
|
+
handle: opts.dragHandle ?? 'bottom_right',
|
|
6823
|
+
// don't set isSingle to true for children
|
|
6824
|
+
mode: opts.mode ?? 'scale_shape',
|
|
6825
|
+
scaleX: myScale.x,
|
|
6826
|
+
scaleY: myScale.y,
|
|
6827
|
+
initialBounds,
|
|
6828
|
+
initialShape,
|
|
6829
|
+
}
|
|
6830
|
+
)
|
|
6831
|
+
|
|
6832
|
+
if (resizedShape) {
|
|
6833
|
+
didResize = true
|
|
6834
|
+
}
|
|
6835
|
+
|
|
6791
6836
|
workingShape = applyPartialToRecordWithProps(workingShape, {
|
|
6792
6837
|
id,
|
|
6793
6838
|
type: initialShape.type as any,
|
|
6794
6839
|
x: newLocalPoint.x,
|
|
6795
6840
|
y: newLocalPoint.y,
|
|
6796
|
-
...
|
|
6797
|
-
{ ...initialShape, x, y },
|
|
6798
|
-
{
|
|
6799
|
-
newPoint: newLocalPoint,
|
|
6800
|
-
handle: opts.dragHandle ?? 'bottom_right',
|
|
6801
|
-
// don't set isSingle to true for children
|
|
6802
|
-
mode: opts.mode ?? 'scale_shape',
|
|
6803
|
-
scaleX: myScale.x,
|
|
6804
|
-
scaleY: myScale.y,
|
|
6805
|
-
initialBounds,
|
|
6806
|
-
initialShape,
|
|
6807
|
-
}
|
|
6808
|
-
),
|
|
6841
|
+
...resizedShape,
|
|
6809
6842
|
})
|
|
6810
6843
|
|
|
6811
6844
|
if (!opts.skipStartAndEndCallbacks) {
|
|
@@ -6816,7 +6849,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6816
6849
|
}
|
|
6817
6850
|
|
|
6818
6851
|
this.updateShapes([workingShape])
|
|
6819
|
-
}
|
|
6852
|
+
}
|
|
6853
|
+
|
|
6854
|
+
if (!didResize) {
|
|
6855
|
+
// reposition shape (rather than resizing it) based on where its resized center would be
|
|
6856
|
+
|
|
6820
6857
|
const initialPageCenter = Mat.applyToPoint(pageTransform, initialBounds.center)
|
|
6821
6858
|
// get the model changes from the shape util
|
|
6822
6859
|
const newPageCenter = this._scalePagePoint(
|
|
@@ -7938,10 +7975,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7938
7975
|
|
|
7939
7976
|
/** @internal */
|
|
7940
7977
|
externalAssetContentHandlers: {
|
|
7941
|
-
[K in
|
|
7942
|
-
[Key in K]:
|
|
7943
|
-
| null
|
|
7944
|
-
| ((info: TLExternalAssetContent & { type: Key }) => Promise<TLAsset | undefined>)
|
|
7978
|
+
[K in TLExternalAsset['type']]: {
|
|
7979
|
+
[Key in K]: null | ((info: TLExternalAsset & { type: Key }) => Promise<TLAsset | undefined>)
|
|
7945
7980
|
}[K]
|
|
7946
7981
|
} = {
|
|
7947
7982
|
file: null,
|
|
@@ -7970,9 +8005,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7970
8005
|
*
|
|
7971
8006
|
* @public
|
|
7972
8007
|
*/
|
|
7973
|
-
registerExternalAssetHandler<T extends
|
|
8008
|
+
registerExternalAssetHandler<T extends TLExternalAsset['type']>(
|
|
7974
8009
|
type: T,
|
|
7975
|
-
handler: null | ((info:
|
|
8010
|
+
handler: null | ((info: TLExternalAsset & { type: T }) => Promise<TLAsset>)
|
|
7976
8011
|
): this {
|
|
7977
8012
|
this.externalAssetContentHandlers[type] = handler as any
|
|
7978
8013
|
return this
|
|
@@ -8040,11 +8075,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8040
8075
|
* @param info - Info about the external content.
|
|
8041
8076
|
* @returns The asset.
|
|
8042
8077
|
*/
|
|
8043
|
-
async getAssetForExternalContent(info:
|
|
8078
|
+
async getAssetForExternalContent(info: TLExternalAsset): Promise<TLAsset | undefined> {
|
|
8044
8079
|
return await this.externalAssetContentHandlers[info.type]?.(info as any)
|
|
8045
8080
|
}
|
|
8046
8081
|
|
|
8047
|
-
hasExternalAssetHandler(type:
|
|
8082
|
+
hasExternalAssetHandler(type: TLExternalAsset['type']): boolean {
|
|
8048
8083
|
return !!this.externalAssetContentHandlers[type]
|
|
8049
8084
|
}
|
|
8050
8085
|
|
|
@@ -8564,11 +8599,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8564
8599
|
*
|
|
8565
8600
|
* @public
|
|
8566
8601
|
*/
|
|
8567
|
-
async getSvgElement(shapes: TLShapeId[] | TLShape[], opts:
|
|
8602
|
+
async getSvgElement(shapes: TLShapeId[] | TLShape[], opts: TLSvgExportOptions = {}) {
|
|
8568
8603
|
const ids =
|
|
8569
|
-
|
|
8570
|
-
? (
|
|
8571
|
-
:
|
|
8604
|
+
shapes.length === 0
|
|
8605
|
+
? this.getCurrentPageShapeIdsSorted()
|
|
8606
|
+
: typeof shapes[0] === 'string'
|
|
8607
|
+
? (shapes as TLShapeId[])
|
|
8608
|
+
: (shapes as TLShape[]).map((s) => s.id)
|
|
8572
8609
|
|
|
8573
8610
|
if (ids.length === 0) return undefined
|
|
8574
8611
|
|
|
@@ -8585,7 +8622,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8585
8622
|
*
|
|
8586
8623
|
* @public
|
|
8587
8624
|
*/
|
|
8588
|
-
async getSvgString(shapes: TLShapeId[] | TLShape[], opts:
|
|
8625
|
+
async getSvgString(shapes: TLShapeId[] | TLShape[], opts: TLSvgExportOptions = {}) {
|
|
8589
8626
|
const result = await this.getSvgElement(shapes, opts)
|
|
8590
8627
|
if (!result) return undefined
|
|
8591
8628
|
|
|
@@ -8598,12 +8635,63 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8598
8635
|
}
|
|
8599
8636
|
|
|
8600
8637
|
/** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
|
|
8601
|
-
async getSvg(shapes: TLShapeId[] | TLShape[], opts:
|
|
8638
|
+
async getSvg(shapes: TLShapeId[] | TLShape[], opts: TLSvgExportOptions = {}) {
|
|
8602
8639
|
const result = await this.getSvgElement(shapes, opts)
|
|
8603
8640
|
if (!result) return undefined
|
|
8604
8641
|
return result.svg
|
|
8605
8642
|
}
|
|
8606
8643
|
|
|
8644
|
+
/**
|
|
8645
|
+
* Get an exported image of the given shapes.
|
|
8646
|
+
*
|
|
8647
|
+
* @param shapes - The shapes (or shape ids) to export.
|
|
8648
|
+
* @param opts - Options for the export.
|
|
8649
|
+
*
|
|
8650
|
+
* @returns A blob of the image.
|
|
8651
|
+
* @public
|
|
8652
|
+
*/
|
|
8653
|
+
async toImage(shapes: TLShapeId[] | TLShape[], opts: TLImageExportOptions = {}) {
|
|
8654
|
+
const withDefaults = {
|
|
8655
|
+
format: 'png',
|
|
8656
|
+
scale: 1,
|
|
8657
|
+
pixelRatio: opts.format === 'svg' ? undefined : 2,
|
|
8658
|
+
...opts,
|
|
8659
|
+
} satisfies TLImageExportOptions
|
|
8660
|
+
const result = await this.getSvgString(shapes, withDefaults)
|
|
8661
|
+
if (!result) throw new Error('Could not create SVG')
|
|
8662
|
+
|
|
8663
|
+
switch (withDefaults.format) {
|
|
8664
|
+
case 'svg':
|
|
8665
|
+
return {
|
|
8666
|
+
blob: new Blob([result.svg], { type: 'text/plain' }),
|
|
8667
|
+
width: result.width,
|
|
8668
|
+
height: result.height,
|
|
8669
|
+
}
|
|
8670
|
+
case 'jpeg':
|
|
8671
|
+
case 'png':
|
|
8672
|
+
case 'webp': {
|
|
8673
|
+
const blob = await getSvgAsImage(result.svg, {
|
|
8674
|
+
type: withDefaults.format,
|
|
8675
|
+
quality: withDefaults.quality,
|
|
8676
|
+
pixelRatio: withDefaults.pixelRatio,
|
|
8677
|
+
width: result.width,
|
|
8678
|
+
height: result.height,
|
|
8679
|
+
})
|
|
8680
|
+
if (!blob) {
|
|
8681
|
+
throw new Error('Could not construct image.')
|
|
8682
|
+
}
|
|
8683
|
+
return {
|
|
8684
|
+
blob,
|
|
8685
|
+
width: result.width,
|
|
8686
|
+
height: result.height,
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8689
|
+
default: {
|
|
8690
|
+
exhaustiveSwitchError(withDefaults.format)
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
}
|
|
8694
|
+
|
|
8607
8695
|
/* --------------------- Events --------------------- */
|
|
8608
8696
|
|
|
8609
8697
|
/**
|
|
@@ -8713,8 +8801,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8713
8801
|
// If our pointer moved only because we're following some other user, then don't
|
|
8714
8802
|
// update our last activity timestamp; otherwise, update it to the current timestamp.
|
|
8715
8803
|
info.type === 'pointer' && info.pointerId === INTERNAL_POINTER_IDS.CAMERA_MOVE
|
|
8716
|
-
? this.store.unsafeGetWithoutCapture(TLPOINTER_ID)?.lastActivityTimestamp ??
|
|
8717
|
-
this._tickManager.now
|
|
8804
|
+
? (this.store.unsafeGetWithoutCapture(TLPOINTER_ID)?.lastActivityTimestamp ??
|
|
8805
|
+
this._tickManager.now)
|
|
8718
8806
|
: this._tickManager.now,
|
|
8719
8807
|
meta: {},
|
|
8720
8808
|
},
|
|
@@ -9279,6 +9367,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9279
9367
|
// todo: replace with new readonly mode?
|
|
9280
9368
|
if (this.getCrashingError()) return this
|
|
9281
9369
|
|
|
9370
|
+
this.emit('before-event', info)
|
|
9371
|
+
|
|
9282
9372
|
const { inputs } = this
|
|
9283
9373
|
const { type } = info
|
|
9284
9374
|
|
|
@@ -390,8 +390,8 @@ export class BoundsSnaps {
|
|
|
390
390
|
|
|
391
391
|
// at the same time, calculate how far we need to nudge the shape to 'snap' to the target point(s)
|
|
392
392
|
const nudge = new Vec(
|
|
393
|
-
lockedAxis === 'x' ? 0 : nearestSnapsX[0]?.nudge ?? 0,
|
|
394
|
-
lockedAxis === 'y' ? 0 : nearestSnapsY[0]?.nudge ?? 0
|
|
393
|
+
lockedAxis === 'x' ? 0 : (nearestSnapsX[0]?.nudge ?? 0),
|
|
394
|
+
lockedAxis === 'y' ? 0 : (nearestSnapsY[0]?.nudge ?? 0)
|
|
395
395
|
)
|
|
396
396
|
|
|
397
397
|
// ok we've figured out how much the box should be nudged, now let's find all the snap points
|
|
@@ -504,8 +504,8 @@ export class BoundsSnaps {
|
|
|
504
504
|
|
|
505
505
|
// at the same time, calculate how far we need to nudge the shape to 'snap' to the target point(s)
|
|
506
506
|
const nudge = new Vec(
|
|
507
|
-
isXLocked ? 0 : nearestSnapsX[0]?.nudge ?? 0,
|
|
508
|
-
isYLocked ? 0 : nearestSnapsY[0]?.nudge ?? 0
|
|
507
|
+
isXLocked ? 0 : (nearestSnapsX[0]?.nudge ?? 0),
|
|
508
|
+
isYLocked ? 0 : (nearestSnapsY[0]?.nudge ?? 0)
|
|
509
509
|
)
|
|
510
510
|
|
|
511
511
|
if (isAspectRatioLocked && isSelectionCorner(handle) && nudge.len() !== 0) {
|
|
@@ -230,6 +230,7 @@ export class TextManager {
|
|
|
230
230
|
elm.style.setProperty('font-weight', opts.fontWeight)
|
|
231
231
|
elm.style.setProperty('line-height', `${opts.lineHeight * opts.fontSize}px`)
|
|
232
232
|
elm.style.setProperty('text-align', textAlignmentsForLtr[opts.textAlign])
|
|
233
|
+
elm.style.setProperty('font-style', opts.fontStyle)
|
|
233
234
|
|
|
234
235
|
const shouldTruncateToFirstLine =
|
|
235
236
|
opts.overflow === 'truncate-ellipsis' || opts.overflow === 'truncate-clip'
|
|
@@ -5,11 +5,12 @@ import {
|
|
|
5
5
|
TLHandle,
|
|
6
6
|
TLPropsMigrations,
|
|
7
7
|
TLShape,
|
|
8
|
+
TLShapeCrop,
|
|
8
9
|
TLShapePartial,
|
|
9
10
|
TLUnknownShape,
|
|
10
11
|
} from '@tldraw/tlschema'
|
|
11
12
|
import { ReactElement } from 'react'
|
|
12
|
-
import { Box } from '../../primitives/Box'
|
|
13
|
+
import { Box, SelectionHandle } from '../../primitives/Box'
|
|
13
14
|
import { Vec } from '../../primitives/Vec'
|
|
14
15
|
import { Geometry2d } from '../../primitives/geometry/Geometry2d'
|
|
15
16
|
import type { Editor } from '../Editor'
|
|
@@ -419,6 +420,19 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
419
420
|
*/
|
|
420
421
|
onBeforeUpdate?(prev: Shape, next: Shape): Shape | void
|
|
421
422
|
|
|
423
|
+
/**
|
|
424
|
+
* A callback called when a shape changes from a crop.
|
|
425
|
+
*
|
|
426
|
+
* @param shape - The shape at the start of the crop.
|
|
427
|
+
* @param info - Info about the crop.
|
|
428
|
+
* @returns A change to apply to the shape, or void.
|
|
429
|
+
* @public
|
|
430
|
+
*/
|
|
431
|
+
onCrop?(
|
|
432
|
+
shape: Shape,
|
|
433
|
+
info: TLCropInfo<Shape>
|
|
434
|
+
): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void
|
|
435
|
+
|
|
422
436
|
/**
|
|
423
437
|
* A callback called when some other shapes are dragged over this one.
|
|
424
438
|
*
|
|
@@ -616,6 +630,21 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
616
630
|
onEditEnd?(shape: Shape): void
|
|
617
631
|
}
|
|
618
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Info about a crop.
|
|
635
|
+
* @param handle - The handle being dragged.
|
|
636
|
+
* @param change - The distance the handle is moved.
|
|
637
|
+
* @param initialShape - The shape at the start of the resize.
|
|
638
|
+
* @public
|
|
639
|
+
*/
|
|
640
|
+
export interface TLCropInfo<T extends TLShape> {
|
|
641
|
+
handle: SelectionHandle
|
|
642
|
+
change: Vec
|
|
643
|
+
crop: TLShapeCrop
|
|
644
|
+
uncroppedSize: { w: number; h: number }
|
|
645
|
+
initialShape: T
|
|
646
|
+
}
|
|
647
|
+
|
|
619
648
|
/**
|
|
620
649
|
* The type of resize.
|
|
621
650
|
*
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { TLBaseShape } from '@tldraw/tlschema'
|
|
2
|
+
import { exhaustiveSwitchError } from '@tldraw/utils'
|
|
3
|
+
import { Vec } from '../../../primitives/Vec'
|
|
4
|
+
import { TLResizeInfo } from '../ShapeUtil'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resize a shape that has a scale prop.
|
|
8
|
+
*
|
|
9
|
+
* @param shape - The shape to resize
|
|
10
|
+
* @param info - The resize info
|
|
11
|
+
*
|
|
12
|
+
* @public */
|
|
13
|
+
export function resizeScaled(
|
|
14
|
+
shape: TLBaseShape<any, { scale: number }>,
|
|
15
|
+
{ initialBounds, scaleX, scaleY, newPoint, handle }: TLResizeInfo<any>
|
|
16
|
+
) {
|
|
17
|
+
let scaleDelta: number
|
|
18
|
+
switch (handle) {
|
|
19
|
+
case 'bottom_left':
|
|
20
|
+
case 'bottom_right':
|
|
21
|
+
case 'top_left':
|
|
22
|
+
case 'top_right': {
|
|
23
|
+
scaleDelta = Math.max(0.01, Math.max(Math.abs(scaleX), Math.abs(scaleY)))
|
|
24
|
+
break
|
|
25
|
+
}
|
|
26
|
+
case 'left':
|
|
27
|
+
case 'right': {
|
|
28
|
+
scaleDelta = Math.max(0.01, Math.abs(scaleX))
|
|
29
|
+
break
|
|
30
|
+
}
|
|
31
|
+
case 'bottom':
|
|
32
|
+
case 'top': {
|
|
33
|
+
scaleDelta = Math.max(0.01, Math.abs(scaleY))
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
default: {
|
|
37
|
+
throw exhaustiveSwitchError(handle)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Compute the offset (if flipped X or flipped Y)
|
|
42
|
+
const offset = new Vec(0, 0)
|
|
43
|
+
|
|
44
|
+
if (scaleX < 0) {
|
|
45
|
+
offset.x = -(initialBounds.width * scaleDelta)
|
|
46
|
+
}
|
|
47
|
+
if (scaleY < 0) {
|
|
48
|
+
offset.y = -(initialBounds.height * scaleDelta)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Apply the offset to the new point
|
|
52
|
+
const { x, y } = Vec.Add(newPoint, offset.rot(shape.rotation))
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
x,
|
|
56
|
+
y,
|
|
57
|
+
props: {
|
|
58
|
+
scale: scaleDelta * shape.props.scale,
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TLAssetId } from '@tldraw/tlschema'
|
|
1
2
|
import { promiseWithResolve } from '@tldraw/utils'
|
|
2
3
|
import { ReactElement, ReactNode, createContext, useContext, useEffect, useState } from 'react'
|
|
3
4
|
import { ContainerProvider } from '../../hooks/useContainer'
|
|
@@ -29,10 +30,30 @@ export interface SvgExportContext {
|
|
|
29
30
|
*/
|
|
30
31
|
waitUntil(promise: Promise<void>): void
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Resolve an asset URL in the context of this export. Supply the asset ID and the width in
|
|
35
|
+
* shape-pixels it'll be displayed at, and this will resolve the asset according to the export
|
|
36
|
+
* options.
|
|
37
|
+
*/
|
|
38
|
+
resolveAssetUrl(assetId: TLAssetId, width: number): Promise<string | null>
|
|
39
|
+
|
|
32
40
|
/**
|
|
33
41
|
* Whether the export should be in dark mode.
|
|
34
42
|
*/
|
|
35
43
|
readonly isDarkMode: boolean
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The scale of the export - how much CSS pixels will be scaled up/down by.
|
|
47
|
+
*/
|
|
48
|
+
readonly scale: number
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Use this value to optionally downscale images in the export. If we're exporting directly to
|
|
52
|
+
* an SVG, this will usually be null, and you shouldn't downscale images. If the export is to a
|
|
53
|
+
* raster format like PNG, this will be the number of raster pixels in the resulting bitmap per
|
|
54
|
+
* CSS pixel in the resulting SVG.
|
|
55
|
+
*/
|
|
56
|
+
readonly pixelRatio: number | null
|
|
36
57
|
}
|
|
37
58
|
|
|
38
59
|
const Context = createContext<SvgExportContext | null>(null)
|