@tldraw/editor 3.8.0-canary.a70cbfe4fb04 → 3.8.0-canary.aa9a8742d222
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 +155 -50
- package/dist-cjs/index.js +11 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +28 -17
- 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 +13 -0
- 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/emit-types.js.map +1 -1
- package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
- package/dist-cjs/lib/exports/StyleEmbedder.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/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/dom.js +6 -0
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +155 -50
- package/dist-esm/index.mjs +5 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +28 -17
- 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 +13 -0
- 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/exports/StyleEmbedder.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/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/dom.mjs +6 -0
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +2 -1
- package/package.json +20 -20
- package/src/index.ts +16 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
- package/src/lib/config/TLSessionStateSnapshot.ts +3 -1
- package/src/lib/editor/Editor.ts +61 -34
- 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 +49 -1
- package/src/lib/editor/shapes/shared/resizeScaled.ts +61 -0
- package/src/lib/editor/types/emit-types.ts +1 -0
- package/src/lib/editor/types/external-content.ts +90 -50
- package/src/lib/exports/StyleEmbedder.ts +1 -1
- package/src/lib/hooks/useDocumentEvents.ts +2 -11
- package/src/lib/hooks/usePassThroughWheelEvents.ts +7 -0
- package/src/lib/options.ts +5 -2
- package/src/lib/utils/dom.ts +12 -0
- package/src/version.ts +3 -3
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -155,7 +155,7 @@ import {
|
|
|
155
155
|
TLPointerEventInfo,
|
|
156
156
|
TLWheelEventInfo,
|
|
157
157
|
} from './types/event-types'
|
|
158
|
-
import {
|
|
158
|
+
import { TLExternalAsset, TLExternalContent } from './types/external-content'
|
|
159
159
|
import { TLHistoryBatchOptions } from './types/history-types'
|
|
160
160
|
import {
|
|
161
161
|
OptionalKeys,
|
|
@@ -929,6 +929,21 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
929
929
|
return shapeUtil
|
|
930
930
|
}
|
|
931
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
|
+
|
|
932
947
|
/* ------------------- Binding Utils ------------------ */
|
|
933
948
|
/**
|
|
934
949
|
* A map of shape utility classes (TLShapeUtils) by shape type.
|
|
@@ -1385,8 +1400,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1385
1400
|
*
|
|
1386
1401
|
* @example
|
|
1387
1402
|
* ```ts
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1403
|
+
* editor.getStateDescendant('select')
|
|
1404
|
+
* editor.getStateDescendant('select.brushing')
|
|
1390
1405
|
* ```
|
|
1391
1406
|
*
|
|
1392
1407
|
* @param path - The descendant's path of state ids, separated by periods.
|
|
@@ -1460,10 +1475,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1460
1475
|
if (partial.isChangingStyle !== undefined) {
|
|
1461
1476
|
clearTimeout(this._isChangingStyleTimeout)
|
|
1462
1477
|
if (partial.isChangingStyle === true) {
|
|
1463
|
-
// 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
|
|
1464
1479
|
this._isChangingStyleTimeout = this.timers.setTimeout(() => {
|
|
1465
1480
|
this._updateInstanceState({ isChangingStyle: false }, { history: 'ignore' })
|
|
1466
|
-
},
|
|
1481
|
+
}, 1000)
|
|
1467
1482
|
}
|
|
1468
1483
|
}
|
|
1469
1484
|
|
|
@@ -1666,7 +1681,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1666
1681
|
* @public
|
|
1667
1682
|
*/
|
|
1668
1683
|
isAncestorSelected(shape: TLShape | TLShapeId): boolean {
|
|
1669
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
1684
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
1670
1685
|
const _shape = this.getShape(id)
|
|
1671
1686
|
if (!_shape) return false
|
|
1672
1687
|
const selectedShapeIds = this.getSelectedShapeIds()
|
|
@@ -1923,7 +1938,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1923
1938
|
* @public
|
|
1924
1939
|
*/
|
|
1925
1940
|
setFocusedGroup(shape: TLShapeId | TLGroupShape | null): this {
|
|
1926
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
1941
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
1927
1942
|
|
|
1928
1943
|
if (id !== null) {
|
|
1929
1944
|
const shape = this.getShape(id)
|
|
@@ -2006,7 +2021,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2006
2021
|
* @public
|
|
2007
2022
|
*/
|
|
2008
2023
|
setEditingShape(shape: TLShapeId | TLShape | null): this {
|
|
2009
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
2024
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2010
2025
|
if (id !== this.getEditingShapeId()) {
|
|
2011
2026
|
if (id) {
|
|
2012
2027
|
const shape = this.getShape(id)
|
|
@@ -2067,7 +2082,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2067
2082
|
* @public
|
|
2068
2083
|
*/
|
|
2069
2084
|
setHoveredShape(shape: TLShapeId | TLShape | null): this {
|
|
2070
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
2085
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2071
2086
|
if (id === this.getHoveredShapeId()) return this
|
|
2072
2087
|
this.run(
|
|
2073
2088
|
() => {
|
|
@@ -2216,7 +2231,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
2216
2231
|
* @public
|
|
2217
2232
|
*/
|
|
2218
2233
|
setCroppingShape(shape: TLShapeId | TLShape | null): this {
|
|
2219
|
-
const id = typeof shape === 'string' ? shape : shape?.id ?? null
|
|
2234
|
+
const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
|
|
2220
2235
|
if (id !== this.getCroppingShapeId()) {
|
|
2221
2236
|
this.run(
|
|
2222
2237
|
() => {
|
|
@@ -6760,6 +6775,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6760
6775
|
}
|
|
6761
6776
|
}
|
|
6762
6777
|
|
|
6778
|
+
let didResize = false
|
|
6779
|
+
|
|
6763
6780
|
if (util.onResize && util.canResize(initialShape)) {
|
|
6764
6781
|
// get the model changes from the shape util
|
|
6765
6782
|
const newPagePoint = this._scalePagePoint(
|
|
@@ -6798,24 +6815,30 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6798
6815
|
)
|
|
6799
6816
|
}
|
|
6800
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
|
+
|
|
6801
6836
|
workingShape = applyPartialToRecordWithProps(workingShape, {
|
|
6802
6837
|
id,
|
|
6803
6838
|
type: initialShape.type as any,
|
|
6804
6839
|
x: newLocalPoint.x,
|
|
6805
6840
|
y: newLocalPoint.y,
|
|
6806
|
-
...
|
|
6807
|
-
{ ...initialShape, x, y },
|
|
6808
|
-
{
|
|
6809
|
-
newPoint: newLocalPoint,
|
|
6810
|
-
handle: opts.dragHandle ?? 'bottom_right',
|
|
6811
|
-
// don't set isSingle to true for children
|
|
6812
|
-
mode: opts.mode ?? 'scale_shape',
|
|
6813
|
-
scaleX: myScale.x,
|
|
6814
|
-
scaleY: myScale.y,
|
|
6815
|
-
initialBounds,
|
|
6816
|
-
initialShape,
|
|
6817
|
-
}
|
|
6818
|
-
),
|
|
6841
|
+
...resizedShape,
|
|
6819
6842
|
})
|
|
6820
6843
|
|
|
6821
6844
|
if (!opts.skipStartAndEndCallbacks) {
|
|
@@ -6826,7 +6849,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6826
6849
|
}
|
|
6827
6850
|
|
|
6828
6851
|
this.updateShapes([workingShape])
|
|
6829
|
-
}
|
|
6852
|
+
}
|
|
6853
|
+
|
|
6854
|
+
if (!didResize) {
|
|
6855
|
+
// reposition shape (rather than resizing it) based on where its resized center would be
|
|
6856
|
+
|
|
6830
6857
|
const initialPageCenter = Mat.applyToPoint(pageTransform, initialBounds.center)
|
|
6831
6858
|
// get the model changes from the shape util
|
|
6832
6859
|
const newPageCenter = this._scalePagePoint(
|
|
@@ -7948,10 +7975,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7948
7975
|
|
|
7949
7976
|
/** @internal */
|
|
7950
7977
|
externalAssetContentHandlers: {
|
|
7951
|
-
[K in
|
|
7952
|
-
[Key in K]:
|
|
7953
|
-
| null
|
|
7954
|
-
| ((info: TLExternalAssetContent & { type: Key }) => Promise<TLAsset | undefined>)
|
|
7978
|
+
[K in TLExternalAsset['type']]: {
|
|
7979
|
+
[Key in K]: null | ((info: TLExternalAsset & { type: Key }) => Promise<TLAsset | undefined>)
|
|
7955
7980
|
}[K]
|
|
7956
7981
|
} = {
|
|
7957
7982
|
file: null,
|
|
@@ -7980,9 +8005,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7980
8005
|
*
|
|
7981
8006
|
* @public
|
|
7982
8007
|
*/
|
|
7983
|
-
registerExternalAssetHandler<T extends
|
|
8008
|
+
registerExternalAssetHandler<T extends TLExternalAsset['type']>(
|
|
7984
8009
|
type: T,
|
|
7985
|
-
handler: null | ((info:
|
|
8010
|
+
handler: null | ((info: TLExternalAsset & { type: T }) => Promise<TLAsset>)
|
|
7986
8011
|
): this {
|
|
7987
8012
|
this.externalAssetContentHandlers[type] = handler as any
|
|
7988
8013
|
return this
|
|
@@ -8050,11 +8075,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8050
8075
|
* @param info - Info about the external content.
|
|
8051
8076
|
* @returns The asset.
|
|
8052
8077
|
*/
|
|
8053
|
-
async getAssetForExternalContent(info:
|
|
8078
|
+
async getAssetForExternalContent(info: TLExternalAsset): Promise<TLAsset | undefined> {
|
|
8054
8079
|
return await this.externalAssetContentHandlers[info.type]?.(info as any)
|
|
8055
8080
|
}
|
|
8056
8081
|
|
|
8057
|
-
hasExternalAssetHandler(type:
|
|
8082
|
+
hasExternalAssetHandler(type: TLExternalAsset['type']): boolean {
|
|
8058
8083
|
return !!this.externalAssetContentHandlers[type]
|
|
8059
8084
|
}
|
|
8060
8085
|
|
|
@@ -8776,8 +8801,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8776
8801
|
// If our pointer moved only because we're following some other user, then don't
|
|
8777
8802
|
// update our last activity timestamp; otherwise, update it to the current timestamp.
|
|
8778
8803
|
info.type === 'pointer' && info.pointerId === INTERNAL_POINTER_IDS.CAMERA_MOVE
|
|
8779
|
-
? this.store.unsafeGetWithoutCapture(TLPOINTER_ID)?.lastActivityTimestamp ??
|
|
8780
|
-
this._tickManager.now
|
|
8804
|
+
? (this.store.unsafeGetWithoutCapture(TLPOINTER_ID)?.lastActivityTimestamp ??
|
|
8805
|
+
this._tickManager.now)
|
|
8781
8806
|
: this._tickManager.now,
|
|
8782
8807
|
meta: {},
|
|
8783
8808
|
},
|
|
@@ -9342,6 +9367,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9342
9367
|
// todo: replace with new readonly mode?
|
|
9343
9368
|
if (this.getCrashingError()) return this
|
|
9344
9369
|
|
|
9370
|
+
this.emit('before-event', info)
|
|
9371
|
+
|
|
9345
9372
|
const { inputs } = this
|
|
9346
9373
|
const { type } = info
|
|
9347
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'
|
|
@@ -52,8 +53,27 @@ export interface TLShapeUtilCanvasSvgDef {
|
|
|
52
53
|
|
|
53
54
|
/** @public */
|
|
54
55
|
export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
56
|
+
/** Configure this shape utils {@link ShapeUtil.options | `options`}. */
|
|
57
|
+
static configure<T extends TLShapeUtilConstructor<any, any>>(
|
|
58
|
+
this: T,
|
|
59
|
+
options: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never
|
|
60
|
+
): T {
|
|
61
|
+
// @ts-expect-error -- typescript has no idea what's going on here but it's fine
|
|
62
|
+
return class extends this {
|
|
63
|
+
// @ts-expect-error
|
|
64
|
+
options = { ...this.options, ...options }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
constructor(public editor: Editor) {}
|
|
56
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Options for this shape util. If you're implementing a custom shape util, you can override
|
|
72
|
+
* this to provide customization options for your shape. If using an existing shape util, you
|
|
73
|
+
* can customizing this by calling {@link ShapeUtil.configure}.
|
|
74
|
+
*/
|
|
75
|
+
options = {}
|
|
76
|
+
|
|
57
77
|
/**
|
|
58
78
|
* Props allow you to define the shape's properties in a way that the editor can understand.
|
|
59
79
|
* This has two main uses:
|
|
@@ -419,6 +439,19 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
419
439
|
*/
|
|
420
440
|
onBeforeUpdate?(prev: Shape, next: Shape): Shape | void
|
|
421
441
|
|
|
442
|
+
/**
|
|
443
|
+
* A callback called when a shape changes from a crop.
|
|
444
|
+
*
|
|
445
|
+
* @param shape - The shape at the start of the crop.
|
|
446
|
+
* @param info - Info about the crop.
|
|
447
|
+
* @returns A change to apply to the shape, or void.
|
|
448
|
+
* @public
|
|
449
|
+
*/
|
|
450
|
+
onCrop?(
|
|
451
|
+
shape: Shape,
|
|
452
|
+
info: TLCropInfo<Shape>
|
|
453
|
+
): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void
|
|
454
|
+
|
|
422
455
|
/**
|
|
423
456
|
* A callback called when some other shapes are dragged over this one.
|
|
424
457
|
*
|
|
@@ -616,6 +649,21 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
616
649
|
onEditEnd?(shape: Shape): void
|
|
617
650
|
}
|
|
618
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Info about a crop.
|
|
654
|
+
* @param handle - The handle being dragged.
|
|
655
|
+
* @param change - The distance the handle is moved.
|
|
656
|
+
* @param initialShape - The shape at the start of the resize.
|
|
657
|
+
* @public
|
|
658
|
+
*/
|
|
659
|
+
export interface TLCropInfo<T extends TLShape> {
|
|
660
|
+
handle: SelectionHandle
|
|
661
|
+
change: Vec
|
|
662
|
+
crop: TLShapeCrop
|
|
663
|
+
uncroppedSize: { w: number; h: number }
|
|
664
|
+
initialShape: T
|
|
665
|
+
}
|
|
666
|
+
|
|
619
667
|
/**
|
|
620
668
|
* The type of resize.
|
|
621
669
|
*
|
|
@@ -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
|
+
}
|
|
@@ -2,57 +2,97 @@ import { TLAssetId } from '@tldraw/tlschema'
|
|
|
2
2
|
import { VecLike } from '../../primitives/Vec'
|
|
3
3
|
import { TLContent } from './clipboard-types'
|
|
4
4
|
|
|
5
|
+
/** @public */
|
|
6
|
+
export interface TLTldrawExternalContentSource {
|
|
7
|
+
type: 'tldraw'
|
|
8
|
+
data: TLContent
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @public */
|
|
12
|
+
export interface TLExcalidrawExternalContentSource {
|
|
13
|
+
type: 'excalidraw'
|
|
14
|
+
data: any
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @public */
|
|
18
|
+
export interface TLTextExternalContentSource {
|
|
19
|
+
type: 'text'
|
|
20
|
+
data: string
|
|
21
|
+
subtype: 'json' | 'html' | 'text' | 'url'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @public */
|
|
25
|
+
export interface TLErrorExternalContentSource {
|
|
26
|
+
type: 'error'
|
|
27
|
+
data: string | null
|
|
28
|
+
reason: string
|
|
29
|
+
}
|
|
30
|
+
|
|
5
31
|
/** @public */
|
|
6
32
|
export type TLExternalContentSource =
|
|
7
|
-
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
| {
|
|
16
|
-
type: 'text'
|
|
17
|
-
data: string
|
|
18
|
-
subtype: 'json' | 'html' | 'text' | 'url'
|
|
19
|
-
}
|
|
20
|
-
| {
|
|
21
|
-
type: 'error'
|
|
22
|
-
data: string | null
|
|
23
|
-
reason: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** @public */
|
|
27
|
-
export type TLExternalContent<EmbedDefinition> = {
|
|
33
|
+
| TLTldrawExternalContentSource
|
|
34
|
+
| TLExcalidrawExternalContentSource
|
|
35
|
+
| TLTextExternalContentSource
|
|
36
|
+
| TLErrorExternalContentSource
|
|
37
|
+
|
|
38
|
+
/** @public */
|
|
39
|
+
export interface TLBaseExternalContent {
|
|
28
40
|
sources?: TLExternalContentSource[]
|
|
29
41
|
point?: VecLike
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @public */
|
|
45
|
+
export interface TLTextExternalContent extends TLBaseExternalContent {
|
|
46
|
+
type: 'text'
|
|
47
|
+
text: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @public */
|
|
51
|
+
export interface TLFilesExternalContent extends TLBaseExternalContent {
|
|
52
|
+
type: 'files'
|
|
53
|
+
files: File[]
|
|
54
|
+
ignoreParent: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @public */
|
|
58
|
+
export interface TLUrlExternalContent extends TLBaseExternalContent {
|
|
59
|
+
type: 'url'
|
|
60
|
+
url: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @public */
|
|
64
|
+
export interface TLSvgTextExternalContent extends TLBaseExternalContent {
|
|
65
|
+
type: 'svg-text'
|
|
66
|
+
text: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @public */
|
|
70
|
+
export interface TLEmbedExternalContent<EmbedDefinition> extends TLBaseExternalContent {
|
|
71
|
+
type: 'embed'
|
|
72
|
+
url: string
|
|
73
|
+
embed: EmbedDefinition
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @public */
|
|
77
|
+
export type TLExternalContent<EmbedDefinition> =
|
|
78
|
+
| TLTextExternalContent
|
|
79
|
+
| TLFilesExternalContent
|
|
80
|
+
| TLUrlExternalContent
|
|
81
|
+
| TLSvgTextExternalContent
|
|
82
|
+
| TLEmbedExternalContent<EmbedDefinition>
|
|
83
|
+
|
|
84
|
+
/** @public */
|
|
85
|
+
export interface TLFileExternalAsset {
|
|
86
|
+
type: 'file'
|
|
87
|
+
file: File
|
|
88
|
+
assetId?: TLAssetId
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @public */
|
|
92
|
+
export interface TLUrlExternalAsset {
|
|
93
|
+
type: 'url'
|
|
94
|
+
url: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @public */
|
|
98
|
+
export type TLExternalAsset = TLFileExternalAsset | TLUrlExternalAsset
|
|
@@ -54,7 +54,7 @@ export class StyleEmbedder {
|
|
|
54
54
|
: NO_STYLES
|
|
55
55
|
|
|
56
56
|
const parentStyles = shouldSkipInheritedParentStyles
|
|
57
|
-
? this.styles.get(element.parentElement as Element)?.self ?? NO_STYLES
|
|
57
|
+
? (this.styles.get(element.parentElement as Element)?.self ?? NO_STYLES)
|
|
58
58
|
: NO_STYLES
|
|
59
59
|
|
|
60
60
|
const info: ElementStyleInfo = {
|
|
@@ -2,7 +2,7 @@ import { useValue } from '@tldraw/state-react'
|
|
|
2
2
|
import { useEffect } from 'react'
|
|
3
3
|
import { Editor } from '../editor/Editor'
|
|
4
4
|
import { TLKeyboardEventInfo } from '../editor/types/event-types'
|
|
5
|
-
import { preventDefault, stopEventPropagation } from '../utils/dom'
|
|
5
|
+
import { activeElementShouldCaptureKeys, preventDefault, stopEventPropagation } from '../utils/dom'
|
|
6
6
|
import { isAccelKey } from '../utils/keyboard'
|
|
7
7
|
import { useContainer } from './useContainer'
|
|
8
8
|
import { useEditor } from './useEditor'
|
|
@@ -274,15 +274,6 @@ export function useDocumentEvents() {
|
|
|
274
274
|
}, [editor, container, isAppFocused])
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
const INPUTS = ['input', 'select', 'button', 'textarea']
|
|
278
|
-
|
|
279
277
|
function areShortcutsDisabled(editor: Editor) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return (
|
|
283
|
-
editor.menus.hasOpenMenus() ||
|
|
284
|
-
(activeElement &&
|
|
285
|
-
(activeElement.getAttribute('contenteditable') ||
|
|
286
|
-
INPUTS.indexOf(activeElement.tagName.toLowerCase()) > -1))
|
|
287
|
-
)
|
|
278
|
+
return editor.menus.hasOpenMenus() || activeElementShouldCaptureKeys()
|
|
288
279
|
}
|
|
@@ -11,6 +11,13 @@ export function usePassThroughWheelEvents(ref: RefObject<HTMLElement>) {
|
|
|
11
11
|
useEffect(() => {
|
|
12
12
|
function onWheel(e: WheelEvent) {
|
|
13
13
|
if ((e as any).isSpecialRedispatchedEvent) return
|
|
14
|
+
|
|
15
|
+
// if the element is scrollable, don't redispatch the event
|
|
16
|
+
const elm = ref.current
|
|
17
|
+
if (elm && elm.scrollHeight > elm.clientHeight) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
preventDefault(e)
|
|
15
22
|
const cvs = container.querySelector('.tl-canvas')
|
|
16
23
|
if (!cvs) return
|
package/src/lib/options.ts
CHANGED
|
@@ -29,7 +29,6 @@ export interface TldrawOptions {
|
|
|
29
29
|
readonly dragDistanceSquared: number
|
|
30
30
|
readonly defaultSvgPadding: number
|
|
31
31
|
readonly cameraSlideFriction: number
|
|
32
|
-
readonly maxPointsPerDrawShape: number
|
|
33
32
|
readonly gridSteps: readonly {
|
|
34
33
|
readonly min: number
|
|
35
34
|
readonly mid: number
|
|
@@ -66,6 +65,10 @@ export interface TldrawOptions {
|
|
|
66
65
|
* external context providers. By default, this is `React.Fragment`.
|
|
67
66
|
*/
|
|
68
67
|
readonly exportProvider: ComponentType<{ children: React.ReactNode }>
|
|
68
|
+
/**
|
|
69
|
+
* By default, the toolbar items are accessible via number shortcuts according to their order. To disable this, set this option to false.
|
|
70
|
+
*/
|
|
71
|
+
readonly enableToolbarKeyboardShortcuts: boolean
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
/** @public */
|
|
@@ -81,7 +84,6 @@ export const defaultTldrawOptions = {
|
|
|
81
84
|
dragDistanceSquared: 16, // 4 squared
|
|
82
85
|
defaultSvgPadding: 32,
|
|
83
86
|
cameraSlideFriction: 0.09,
|
|
84
|
-
maxPointsPerDrawShape: 500,
|
|
85
87
|
gridSteps: [
|
|
86
88
|
{ min: -1, mid: 0.15, step: 64 },
|
|
87
89
|
{ min: 0.05, mid: 0.375, step: 16 },
|
|
@@ -111,4 +113,5 @@ export const defaultTldrawOptions = {
|
|
|
111
113
|
actionShortcutsLocation: 'swap',
|
|
112
114
|
createTextOnCanvasDoubleClick: true,
|
|
113
115
|
exportProvider: Fragment,
|
|
116
|
+
enableToolbarKeyboardShortcuts: true,
|
|
114
117
|
} as const satisfies TldrawOptions
|
package/src/lib/utils/dom.ts
CHANGED
|
@@ -90,3 +90,15 @@ export const setStyleProperty = (
|
|
|
90
90
|
if (!elm) return
|
|
91
91
|
elm.style.setProperty(property, value as string)
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
const INPUTS = ['input', 'select', 'button', 'textarea']
|
|
95
|
+
|
|
96
|
+
/** @internal */
|
|
97
|
+
export function activeElementShouldCaptureKeys() {
|
|
98
|
+
const { activeElement } = document
|
|
99
|
+
return !!(
|
|
100
|
+
activeElement &&
|
|
101
|
+
(activeElement.getAttribute('contenteditable') ||
|
|
102
|
+
INPUTS.indexOf(activeElement.tagName.toLowerCase()) > -1)
|
|
103
|
+
)
|
|
104
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '3.8.0-canary.
|
|
4
|
+
export const version = '3.8.0-canary.aa9a8742d222'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-02-11T08:44:24.464Z',
|
|
8
|
+
patch: '2025-02-11T08:44:24.464Z',
|
|
9
9
|
}
|