@tldraw/editor 3.15.4 → 3.16.0-canary.016d4c2889b7
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 +180 -9
- package/dist-cjs/index.js +5 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +8 -2
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +0 -5
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +11 -36
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +4 -23
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultScribble.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +9 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js +53 -0
- package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js.map +7 -0
- package/dist-cjs/lib/config/TLUserPreferences.js +9 -3
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +138 -69
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +9 -4
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.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/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/exports/getSvgJsx.js +35 -16
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +31 -25
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useEditorComponents.js +2 -0
- package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -1
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
- package/dist-cjs/lib/{utils/nearestMultiple.js → hooks/useStateAttribute.js} +15 -14
- package/dist-cjs/lib/hooks/useStateAttribute.js.map +7 -0
- package/dist-cjs/lib/license/Watermark.js +6 -6
- package/dist-cjs/lib/license/Watermark.js.map +1 -1
- package/dist-cjs/lib/options.js +7 -0
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +3 -0
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/utils/EditorAtom.js +45 -0
- package/dist-cjs/lib/utils/EditorAtom.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +180 -9
- package/dist-esm/index.mjs +7 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +8 -2
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +0 -5
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +11 -36
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +4 -23
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +1 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultScribble.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +9 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs +23 -0
- package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs.map +7 -0
- package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +138 -69
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +9 -4
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.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/exports/getSvgJsx.mjs +36 -16
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +32 -26
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEditorComponents.mjs +4 -0
- package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -1
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useStateAttribute.mjs +15 -0
- package/dist-esm/lib/hooks/useStateAttribute.mjs.map +7 -0
- package/dist-esm/lib/license/Watermark.mjs +6 -6
- package/dist-esm/lib/license/Watermark.mjs.map +1 -1
- package/dist-esm/lib/options.mjs +7 -0
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +4 -1
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/utils/EditorAtom.mjs +25 -0
- package/dist-esm/lib/utils/EditorAtom.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +305 -311
- package/package.json +14 -37
- package/src/index.ts +7 -0
- package/src/lib/TldrawEditor.tsx +13 -6
- package/src/lib/components/MenuClickCapture.tsx +0 -8
- package/src/lib/components/Shape.tsx +12 -33
- package/src/lib/components/default-components/DefaultCanvas.tsx +5 -22
- package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
- package/src/lib/components/default-components/DefaultScribble.tsx +1 -1
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +5 -1
- package/src/lib/components/default-components/DefaultShapeWrapper.tsx +35 -0
- package/src/lib/config/TLUserPreferences.ts +8 -1
- package/src/lib/editor/Editor.test.ts +12 -11
- package/src/lib/editor/Editor.ts +178 -103
- package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
- package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -23
- package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
- package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +34 -26
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +6 -1
- package/src/lib/editor/shapes/ShapeUtil.ts +36 -0
- package/src/lib/editor/types/misc-types.ts +73 -1
- package/src/lib/exports/getSvgJsx.test.ts +868 -0
- package/src/lib/exports/getSvgJsx.tsx +78 -21
- package/src/lib/hooks/useCanvasEvents.ts +45 -38
- package/src/lib/hooks/useEditorComponents.tsx +7 -1
- package/src/lib/hooks/usePassThroughWheelEvents.ts +6 -1
- package/src/lib/hooks/useStateAttribute.ts +15 -0
- package/src/lib/license/LicenseManager.test.ts +3 -1
- package/src/lib/license/Watermark.test.tsx +2 -1
- package/src/lib/license/Watermark.tsx +6 -6
- package/src/lib/options.ts +8 -0
- package/src/lib/primitives/Box.test.ts +126 -0
- package/src/lib/primitives/Box.ts +10 -1
- package/src/lib/utils/EditorAtom.ts +37 -0
- package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
- package/src/version.ts +3 -3
- package/dist-cjs/lib/utils/nearestMultiple.js.map +0 -7
- package/dist-esm/lib/utils/nearestMultiple.mjs +0 -14
- package/dist-esm/lib/utils/nearestMultiple.mjs.map +0 -7
- package/src/lib/utils/nearestMultiple.ts +0 -13
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -176,8 +176,10 @@ import {
|
|
|
176
176
|
RequiredKeys,
|
|
177
177
|
TLCameraMoveOptions,
|
|
178
178
|
TLCameraOptions,
|
|
179
|
+
TLGetShapeAtPointOptions,
|
|
179
180
|
TLImageExportOptions,
|
|
180
181
|
TLSvgExportOptions,
|
|
182
|
+
TLUpdatePointerOptions,
|
|
181
183
|
} from './types/misc-types'
|
|
182
184
|
import { TLAdjacentDirection, TLResizeHandle } from './types/selection-types'
|
|
183
185
|
|
|
@@ -3072,7 +3074,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3072
3074
|
// Dispatch a new pointer move because the pointer's page will have changed
|
|
3073
3075
|
// (its screen position will compute to a new page position given the new camera position)
|
|
3074
3076
|
const { currentScreenPoint, currentPagePoint } = this.inputs
|
|
3075
|
-
const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
3076
3077
|
|
|
3077
3078
|
// compare the next page point (derived from the current camera) to the current page point
|
|
3078
3079
|
if (
|
|
@@ -3080,27 +3081,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3080
3081
|
currentScreenPoint.y / z - y !== currentPagePoint.y
|
|
3081
3082
|
) {
|
|
3082
3083
|
// If it's changed, dispatch a pointer event
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
target: 'canvas',
|
|
3086
|
-
name: 'pointer_move',
|
|
3087
|
-
// weird but true: we need to put the screen point back into client space
|
|
3088
|
-
point: Vec.AddXY(currentScreenPoint, screenBounds.x, screenBounds.y),
|
|
3084
|
+
this.updatePointer({
|
|
3085
|
+
immediate: opts?.immediate,
|
|
3089
3086
|
pointerId: INTERNAL_POINTER_IDS.CAMERA_MOVE,
|
|
3090
|
-
|
|
3091
|
-
altKey: this.inputs.altKey,
|
|
3092
|
-
shiftKey: this.inputs.shiftKey,
|
|
3093
|
-
metaKey: this.inputs.metaKey,
|
|
3094
|
-
accelKey: isAccelKey(this.inputs),
|
|
3095
|
-
button: 0,
|
|
3096
|
-
isPen: this.getInstanceState().isPenMode ?? false,
|
|
3097
|
-
}
|
|
3098
|
-
|
|
3099
|
-
if (opts?.immediate) {
|
|
3100
|
-
this._flushEventForTick(event)
|
|
3101
|
-
} else {
|
|
3102
|
-
this.dispatch(event)
|
|
3103
|
-
}
|
|
3087
|
+
})
|
|
3104
3088
|
}
|
|
3105
3089
|
|
|
3106
3090
|
this._tickCameraState()
|
|
@@ -4421,21 +4405,28 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4421
4405
|
*/
|
|
4422
4406
|
deletePage(page: TLPageId | TLPage): this {
|
|
4423
4407
|
const id = typeof page === 'string' ? page : page.id
|
|
4424
|
-
this.run(
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4408
|
+
this.run(
|
|
4409
|
+
() => {
|
|
4410
|
+
if (this.getIsReadonly()) return
|
|
4411
|
+
const pages = this.getPages()
|
|
4412
|
+
if (pages.length === 1) return
|
|
4428
4413
|
|
|
4429
|
-
|
|
4430
|
-
|
|
4414
|
+
const deletedPage = this.getPage(id)
|
|
4415
|
+
if (!deletedPage) return
|
|
4431
4416
|
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4417
|
+
if (id === this.getCurrentPageId()) {
|
|
4418
|
+
const index = pages.findIndex((page) => page.id === id)
|
|
4419
|
+
const next = pages[index - 1] ?? pages[index + 1]
|
|
4420
|
+
this.setCurrentPage(next.id)
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
const shapes = this.getSortedChildIdsForParent(deletedPage.id)
|
|
4424
|
+
this.deleteShapes(shapes)
|
|
4425
|
+
|
|
4426
|
+
this.store.remove([deletedPage.id])
|
|
4427
|
+
},
|
|
4428
|
+
{ ignoreShapeLock: true }
|
|
4429
|
+
)
|
|
4439
4430
|
return this
|
|
4440
4431
|
}
|
|
4441
4432
|
|
|
@@ -4869,27 +4860,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4869
4860
|
return this.store.createComputedCache('pageMaskCache', (shape) => {
|
|
4870
4861
|
if (isPageId(shape.parentId)) return undefined
|
|
4871
4862
|
|
|
4872
|
-
const
|
|
4873
|
-
|
|
4874
|
-
)
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
return []
|
|
4892
|
-
})
|
|
4863
|
+
const clipPaths: Vec[][] = []
|
|
4864
|
+
// Get all ancestors that can potentially clip this shape
|
|
4865
|
+
for (const ancestor of this.getShapeAncestors(shape.id)) {
|
|
4866
|
+
const util = this.getShapeUtil(ancestor)
|
|
4867
|
+
const clipPath = util.getClipPath?.(ancestor)
|
|
4868
|
+
if (!clipPath) continue
|
|
4869
|
+
if (util.shouldClipChild?.(shape) === false) continue
|
|
4870
|
+
const pageTransform = this.getShapePageTransform(ancestor.id)
|
|
4871
|
+
clipPaths.push(pageTransform.applyToPoints(clipPath))
|
|
4872
|
+
}
|
|
4873
|
+
if (clipPaths.length === 0) return undefined
|
|
4874
|
+
|
|
4875
|
+
const pageMask = clipPaths.reduce((acc, b) => {
|
|
4876
|
+
const intersection = intersectPolygonPolygon(acc, b)
|
|
4877
|
+
if (intersection) {
|
|
4878
|
+
return intersection.map(Vec.Cast)
|
|
4879
|
+
}
|
|
4880
|
+
return []
|
|
4881
|
+
})
|
|
4893
4882
|
|
|
4894
4883
|
return pageMask
|
|
4895
4884
|
})
|
|
@@ -5164,20 +5153,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5164
5153
|
*
|
|
5165
5154
|
* @returns The shape at the given point, or undefined if there is no shape at the point.
|
|
5166
5155
|
*/
|
|
5167
|
-
getShapeAtPoint(
|
|
5168
|
-
point: VecLike,
|
|
5169
|
-
opts = {} as {
|
|
5170
|
-
renderingOnly?: boolean
|
|
5171
|
-
margin?: number
|
|
5172
|
-
hitInside?: boolean
|
|
5173
|
-
hitLocked?: boolean
|
|
5174
|
-
// TODO: we probably need to rename this, we don't quite _always_
|
|
5175
|
-
// respect this esp. in the part below that does "Check labels first"
|
|
5176
|
-
hitLabels?: boolean
|
|
5177
|
-
hitFrameInside?: boolean
|
|
5178
|
-
filter?(shape: TLShape): boolean
|
|
5179
|
-
}
|
|
5180
|
-
): TLShape | undefined {
|
|
5156
|
+
getShapeAtPoint(point: VecLike, opts: TLGetShapeAtPointOptions = {}): TLShape | undefined {
|
|
5181
5157
|
const zoomLevel = this.getZoomLevel()
|
|
5182
5158
|
const viewportPageBounds = this.getViewportPageBounds()
|
|
5183
5159
|
const {
|
|
@@ -5189,6 +5165,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5189
5165
|
hitFrameInside = false,
|
|
5190
5166
|
} = opts
|
|
5191
5167
|
|
|
5168
|
+
const [innerMargin, outerMargin] = Array.isArray(margin) ? margin : [margin, margin]
|
|
5169
|
+
|
|
5192
5170
|
let inHollowSmallestArea = Infinity
|
|
5193
5171
|
let inHollowSmallestAreaHit: TLShape | null = null
|
|
5194
5172
|
|
|
@@ -5208,7 +5186,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5208
5186
|
return false
|
|
5209
5187
|
const pageMask = this.getShapeMask(shape)
|
|
5210
5188
|
if (pageMask && !pointInPolygon(point, pageMask)) return false
|
|
5211
|
-
if (filter
|
|
5189
|
+
if (filter && !filter(shape)) return false
|
|
5212
5190
|
return true
|
|
5213
5191
|
})
|
|
5214
5192
|
|
|
@@ -5222,8 +5200,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5222
5200
|
// Check labels first
|
|
5223
5201
|
if (
|
|
5224
5202
|
this.isShapeOfType<TLFrameShape>(shape, 'frame') ||
|
|
5225
|
-
(this.isShapeOfType<TLArrowShape>(shape, 'arrow') && shape.props.text.trim()) ||
|
|
5226
5203
|
((this.isShapeOfType<TLNoteShape>(shape, 'note') ||
|
|
5204
|
+
this.isShapeOfType<TLArrowShape>(shape, 'arrow') ||
|
|
5227
5205
|
(this.isShapeOfType<TLGeoShape>(shape, 'geo') && shape.props.fill === 'none')) &&
|
|
5228
5206
|
this.getShapeUtil(shape).getText(shape)?.trim())
|
|
5229
5207
|
) {
|
|
@@ -5234,13 +5212,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5234
5212
|
}
|
|
5235
5213
|
}
|
|
5236
5214
|
|
|
5237
|
-
if (this.isShapeOfType(shape, 'frame')) {
|
|
5215
|
+
if (this.isShapeOfType<TLFrameShape>(shape, 'frame')) {
|
|
5238
5216
|
// On the rare case that we've hit a frame (not its label), test again hitInside to be forced true;
|
|
5239
5217
|
// this prevents clicks from passing through the body of a frame to shapes behind it.
|
|
5240
5218
|
|
|
5241
5219
|
// If the hit is within the frame's outer margin, then select the frame
|
|
5242
|
-
const distance = geometry.distanceToPoint(pointInShapeSpace,
|
|
5243
|
-
if (
|
|
5220
|
+
const distance = geometry.distanceToPoint(pointInShapeSpace, hitFrameInside)
|
|
5221
|
+
if (
|
|
5222
|
+
hitFrameInside
|
|
5223
|
+
? (distance > 0 && distance <= outerMargin) ||
|
|
5224
|
+
(distance <= 0 && distance > -innerMargin)
|
|
5225
|
+
: distance > 0 && distance <= outerMargin
|
|
5226
|
+
) {
|
|
5244
5227
|
return inMarginClosestToEdgeHit || shape
|
|
5245
5228
|
}
|
|
5246
5229
|
|
|
@@ -5279,11 +5262,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5279
5262
|
// If the margin is zero and the geometry has a very small width or height,
|
|
5280
5263
|
// then check the actual distance. This is to prevent a bug where straight
|
|
5281
5264
|
// lines would never pass the broad phase (point-in-bounds) check.
|
|
5282
|
-
if (
|
|
5265
|
+
if (outerMargin === 0 && (geometry.bounds.w < 1 || geometry.bounds.h < 1)) {
|
|
5283
5266
|
distance = geometry.distanceToPoint(pointInShapeSpace, hitInside)
|
|
5284
5267
|
} else {
|
|
5285
5268
|
// Broad phase
|
|
5286
|
-
if (geometry.bounds.containsPoint(pointInShapeSpace,
|
|
5269
|
+
if (geometry.bounds.containsPoint(pointInShapeSpace, outerMargin)) {
|
|
5287
5270
|
// Narrow phase (actual distance)
|
|
5288
5271
|
distance = geometry.distanceToPoint(pointInShapeSpace, hitInside)
|
|
5289
5272
|
} else {
|
|
@@ -5298,7 +5281,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5298
5281
|
// the shape or negative if inside of the shape. If the distance
|
|
5299
5282
|
// is greater than the margin, then it's a miss. Otherwise...
|
|
5300
5283
|
|
|
5301
|
-
|
|
5284
|
+
// Are we close to the shape's edge?
|
|
5285
|
+
if (distance <= outerMargin || (hitInside && distance <= 0 && distance > -innerMargin)) {
|
|
5302
5286
|
if (geometry.isFilled || (isGroup && geometry.children[0].isFilled)) {
|
|
5303
5287
|
// If the shape is filled, then it's a hit. Remember, we're
|
|
5304
5288
|
// starting from the TOP-MOST shape in z-index order, so any
|
|
@@ -5308,11 +5292,21 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5308
5292
|
// If the shape is bigger than the viewport, then skip it.
|
|
5309
5293
|
if (this.getShapePageBounds(shape)!.contains(viewportPageBounds)) continue
|
|
5310
5294
|
|
|
5311
|
-
//
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5295
|
+
// If we're close to the edge of the shape, and if it's the closest edge among
|
|
5296
|
+
// all the edges that we've gotten close to so far, then we will want to hit the
|
|
5297
|
+
// shape unless we hit something else or closer in later iterations.
|
|
5298
|
+
if (
|
|
5299
|
+
hitInside
|
|
5300
|
+
? // On hitInside, the distance will be negative for hits inside
|
|
5301
|
+
// If the distance is positive, check against the outer margin
|
|
5302
|
+
(distance > 0 && distance <= outerMargin) ||
|
|
5303
|
+
// If the distance is negative, check against the inner margin
|
|
5304
|
+
(distance <= 0 && distance > -innerMargin)
|
|
5305
|
+
: // If hitInside is false, then sadly _we do not know_ whether the
|
|
5306
|
+
// point is inside or outside of the shape, so we check against
|
|
5307
|
+
// the max of the two margins
|
|
5308
|
+
Math.abs(distance) <= Math.max(innerMargin, outerMargin)
|
|
5309
|
+
) {
|
|
5316
5310
|
if (Math.abs(distance) < inMarginClosestToEdgeDistance) {
|
|
5317
5311
|
inMarginClosestToEdgeDistance = Math.abs(distance)
|
|
5318
5312
|
inMarginClosestToEdgeHit = shape
|
|
@@ -5334,6 +5328,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5334
5328
|
} else {
|
|
5335
5329
|
// For open shapes (e.g. lines or draw shapes) always use the margin.
|
|
5336
5330
|
// If the distance is less than the margin, return the shape as the hit.
|
|
5331
|
+
// Use the editor's configurable hit test margin.
|
|
5337
5332
|
if (distance < this.options.hitTestMargin / zoomLevel) {
|
|
5338
5333
|
return shape
|
|
5339
5334
|
}
|
|
@@ -6336,7 +6331,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6336
6331
|
|
|
6337
6332
|
this.createShapes(shapesToCreate)
|
|
6338
6333
|
this.createBindings(bindingsToCreate)
|
|
6339
|
-
|
|
6334
|
+
|
|
6335
|
+
this.setSelectedShapes(
|
|
6336
|
+
compact(
|
|
6337
|
+
ids.map((oldId) => {
|
|
6338
|
+
const newId = shapeIds.get(oldId)
|
|
6339
|
+
if (!newId) return null
|
|
6340
|
+
if (!this.getShape(newId)) return null
|
|
6341
|
+
return newId
|
|
6342
|
+
})
|
|
6343
|
+
)
|
|
6344
|
+
)
|
|
6340
6345
|
|
|
6341
6346
|
if (offset !== undefined) {
|
|
6342
6347
|
// If we've offset the duplicated shapes, check to see whether their new bounds is entirely
|
|
@@ -7390,7 +7395,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7390
7395
|
if (
|
|
7391
7396
|
!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
|
|
7392
7397
|
type: 'stretch',
|
|
7393
|
-
shapes: shapesToStretchFirstPass,
|
|
7394
7398
|
})
|
|
7395
7399
|
) {
|
|
7396
7400
|
continue
|
|
@@ -7861,25 +7865,32 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7861
7865
|
) {
|
|
7862
7866
|
let parentId: TLParentId = this.getFocusedGroupId()
|
|
7863
7867
|
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7868
|
+
const isPositioned = partial.x !== undefined && partial.y !== undefined
|
|
7869
|
+
|
|
7870
|
+
// If the shape has been explicitly positioned, we'll try to find a parent at
|
|
7871
|
+
// that position. If not, we'll assume the user isn't deliberately placing the
|
|
7872
|
+
// shape and the positioning will be handled later by another system.
|
|
7873
|
+
if (isPositioned) {
|
|
7874
|
+
for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
|
|
7875
|
+
const parent = currentPageShapesSorted[i]
|
|
7876
|
+
const util = this.getShapeUtil(parent)
|
|
7877
|
+
if (
|
|
7878
|
+
util.canReceiveNewChildrenOfType(parent, partial.type) &&
|
|
7879
|
+
!this.isShapeHidden(parent) &&
|
|
7880
|
+
this.isPointInShape(
|
|
7881
|
+
parent,
|
|
7882
|
+
// If no parent is provided, then we can treat the
|
|
7883
|
+
// shape's provided x/y as being in the page's space.
|
|
7884
|
+
{ x: partial.x ?? 0, y: partial.y ?? 0 },
|
|
7885
|
+
{
|
|
7886
|
+
margin: 0,
|
|
7887
|
+
hitInside: true,
|
|
7888
|
+
}
|
|
7889
|
+
)
|
|
7890
|
+
) {
|
|
7891
|
+
parentId = parent.id
|
|
7892
|
+
break
|
|
7893
|
+
}
|
|
7883
7894
|
}
|
|
7884
7895
|
}
|
|
7885
7896
|
|
|
@@ -9506,6 +9517,24 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9506
9517
|
}
|
|
9507
9518
|
}
|
|
9508
9519
|
|
|
9520
|
+
/**
|
|
9521
|
+
* Get an exported image of the given shapes as a data URL.
|
|
9522
|
+
*
|
|
9523
|
+
* @param shapes - The shapes (or shape ids) to export.
|
|
9524
|
+
* @param opts - Options for the export.
|
|
9525
|
+
*
|
|
9526
|
+
* @returns A data URL of the image.
|
|
9527
|
+
* @public
|
|
9528
|
+
*/
|
|
9529
|
+
async toImageDataUrl(shapes: TLShapeId[] | TLShape[], opts: TLImageExportOptions = {}) {
|
|
9530
|
+
const { blob, width, height } = await this.toImage(shapes, opts)
|
|
9531
|
+
return {
|
|
9532
|
+
url: await FileHelpers.blobToDataUrl(blob),
|
|
9533
|
+
width,
|
|
9534
|
+
height,
|
|
9535
|
+
}
|
|
9536
|
+
}
|
|
9537
|
+
|
|
9509
9538
|
/* --------------------- Events --------------------- */
|
|
9510
9539
|
|
|
9511
9540
|
/**
|
|
@@ -9673,6 +9702,52 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9673
9702
|
return this
|
|
9674
9703
|
}
|
|
9675
9704
|
|
|
9705
|
+
/**
|
|
9706
|
+
* Dispatch a pointer move event in the current position of the pointer. This is useful when
|
|
9707
|
+
* external circumstances have changed (e.g. the camera moved or a shape was moved) and you want
|
|
9708
|
+
* the current interaction to respond to that change.
|
|
9709
|
+
*
|
|
9710
|
+
* @example
|
|
9711
|
+
* ```ts
|
|
9712
|
+
* editor.updatePointer()
|
|
9713
|
+
* ```
|
|
9714
|
+
*
|
|
9715
|
+
* @param options - The options for updating the pointer.
|
|
9716
|
+
* @returns The editor instance.
|
|
9717
|
+
* @public
|
|
9718
|
+
*/
|
|
9719
|
+
updatePointer(options?: TLUpdatePointerOptions): this {
|
|
9720
|
+
const event: TLPointerEventInfo = {
|
|
9721
|
+
type: 'pointer',
|
|
9722
|
+
target: 'canvas',
|
|
9723
|
+
name: 'pointer_move',
|
|
9724
|
+
point:
|
|
9725
|
+
options?.point ??
|
|
9726
|
+
// weird but true: what `inputs` calls screen-space is actually viewport space. so
|
|
9727
|
+
// we need to convert back into true screen space first. we should fix this...
|
|
9728
|
+
Vec.Add(
|
|
9729
|
+
this.inputs.currentScreenPoint,
|
|
9730
|
+
this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!.screenBounds
|
|
9731
|
+
),
|
|
9732
|
+
pointerId: options?.pointerId ?? 0,
|
|
9733
|
+
button: options?.button ?? 0,
|
|
9734
|
+
isPen: options?.isPen ?? this.inputs.isPen,
|
|
9735
|
+
shiftKey: options?.shiftKey ?? this.inputs.shiftKey,
|
|
9736
|
+
altKey: options?.altKey ?? this.inputs.altKey,
|
|
9737
|
+
ctrlKey: options?.ctrlKey ?? this.inputs.ctrlKey,
|
|
9738
|
+
metaKey: options?.metaKey ?? this.inputs.metaKey,
|
|
9739
|
+
accelKey: options?.accelKey ?? isAccelKey(this.inputs),
|
|
9740
|
+
}
|
|
9741
|
+
|
|
9742
|
+
if (options?.immediate) {
|
|
9743
|
+
this._flushEventForTick(event)
|
|
9744
|
+
} else {
|
|
9745
|
+
this.dispatch(event)
|
|
9746
|
+
}
|
|
9747
|
+
|
|
9748
|
+
return this
|
|
9749
|
+
}
|
|
9750
|
+
|
|
9676
9751
|
/**
|
|
9677
9752
|
* Puts the editor into focused mode.
|
|
9678
9753
|
*
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { Mocked, vi } from 'vitest'
|
|
1
2
|
import { Editor } from '../../Editor'
|
|
2
3
|
import { TLClickEventInfo, TLPointerEventInfo } from '../../types/event-types'
|
|
3
4
|
import { ClickManager } from './ClickManager'
|
|
4
5
|
|
|
5
6
|
// Mock the Editor class
|
|
6
|
-
|
|
7
|
+
vi.mock('../../Editor')
|
|
7
8
|
|
|
8
9
|
describe('ClickManager', () => {
|
|
9
|
-
let editor:
|
|
10
|
+
let editor: Mocked<Editor>
|
|
10
11
|
let clickManager: ClickManager
|
|
11
12
|
let mockTimers: any
|
|
12
13
|
|
|
@@ -29,14 +30,14 @@ describe('ClickManager', () => {
|
|
|
29
30
|
})
|
|
30
31
|
|
|
31
32
|
beforeEach(() => {
|
|
32
|
-
|
|
33
|
+
vi.useFakeTimers()
|
|
33
34
|
mockTimers = {
|
|
34
|
-
setTimeout:
|
|
35
|
+
setTimeout: vi.fn((fn, delay) => setTimeout(fn, delay)),
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
editor = {
|
|
38
39
|
timers: mockTimers,
|
|
39
|
-
dispatch:
|
|
40
|
+
dispatch: vi.fn(),
|
|
40
41
|
options: {
|
|
41
42
|
doubleClickDurationMs: 300,
|
|
42
43
|
multiClickDurationMs: 300,
|
|
@@ -46,7 +47,7 @@ describe('ClickManager', () => {
|
|
|
46
47
|
inputs: {
|
|
47
48
|
currentScreenPoint: { x: 0, y: 0 },
|
|
48
49
|
},
|
|
49
|
-
getInstanceState:
|
|
50
|
+
getInstanceState: vi.fn(() => ({
|
|
50
51
|
isCoarsePointer: false,
|
|
51
52
|
})),
|
|
52
53
|
} as any
|
|
@@ -55,8 +56,8 @@ describe('ClickManager', () => {
|
|
|
55
56
|
})
|
|
56
57
|
|
|
57
58
|
afterEach(() => {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
vi.useRealTimers()
|
|
60
|
+
vi.clearAllMocks()
|
|
60
61
|
})
|
|
61
62
|
|
|
62
63
|
describe('constructor and initial state', () => {
|
|
@@ -100,7 +101,7 @@ describe('ClickManager', () => {
|
|
|
100
101
|
clickManager.handlePointerEvent(pointerEvent)
|
|
101
102
|
expect(clickManager.clickState).toBe('pendingDouble')
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
vi.advanceTimersByTime(350)
|
|
104
105
|
|
|
105
106
|
expect(clickManager.clickState).toBe('idle')
|
|
106
107
|
})
|
|
@@ -141,7 +142,7 @@ describe('ClickManager', () => {
|
|
|
141
142
|
clickManager.handlePointerEvent(firstDown)
|
|
142
143
|
clickManager.handlePointerEvent(secondDown)
|
|
143
144
|
|
|
144
|
-
|
|
145
|
+
vi.advanceTimersByTime(350)
|
|
145
146
|
|
|
146
147
|
expect(editor.dispatch).toHaveBeenCalledWith(
|
|
147
148
|
expect.objectContaining({
|
|
@@ -235,7 +236,7 @@ describe('ClickManager', () => {
|
|
|
235
236
|
clickManager.handlePointerEvent(pointerDown) // second
|
|
236
237
|
clickManager.handlePointerEvent(pointerDown) // third
|
|
237
238
|
|
|
238
|
-
|
|
239
|
+
vi.advanceTimersByTime(350)
|
|
239
240
|
|
|
240
241
|
expect(editor.dispatch).toHaveBeenCalledWith(
|
|
241
242
|
expect.objectContaining({
|
|
@@ -255,7 +256,7 @@ describe('ClickManager', () => {
|
|
|
255
256
|
clickManager.handlePointerEvent(pointerDown) // third
|
|
256
257
|
clickManager.handlePointerEvent(pointerDown) // fourth
|
|
257
258
|
|
|
258
|
-
|
|
259
|
+
vi.advanceTimersByTime(350)
|
|
259
260
|
|
|
260
261
|
expect(editor.dispatch).toHaveBeenCalledWith(
|
|
261
262
|
expect.objectContaining({
|
|
@@ -277,7 +278,7 @@ describe('ClickManager', () => {
|
|
|
277
278
|
editor.options.doubleClickDurationMs
|
|
278
279
|
)
|
|
279
280
|
|
|
280
|
-
|
|
281
|
+
vi.clearAllMocks()
|
|
281
282
|
|
|
282
283
|
// Second click - should use multiClickDurationMs
|
|
283
284
|
clickManager.handlePointerEvent(pointerDown)
|
|
@@ -392,7 +393,7 @@ describe('ClickManager', () => {
|
|
|
392
393
|
clickManager.cancelDoubleClickTimeout()
|
|
393
394
|
|
|
394
395
|
// Advance time - should not dispatch settle event
|
|
395
|
-
|
|
396
|
+
vi.advanceTimersByTime(350)
|
|
396
397
|
|
|
397
398
|
expect(editor.dispatch).not.toHaveBeenCalled()
|
|
398
399
|
expect(clickManager.clickState).toBe('idle')
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
import { Mock, Mocked, vi } from 'vitest'
|
|
1
2
|
import { Box } from '../../../primitives/Box'
|
|
2
3
|
import { Vec } from '../../../primitives/Vec'
|
|
3
4
|
import { Editor } from '../../Editor'
|
|
4
5
|
import { EdgeScrollManager } from './EdgeScrollManager'
|
|
5
6
|
|
|
6
7
|
// Mock the Editor class
|
|
7
|
-
|
|
8
|
+
vi.mock('../../Editor')
|
|
8
9
|
|
|
9
10
|
describe('EdgeScrollManager', () => {
|
|
10
|
-
let editor:
|
|
11
|
+
let editor: Mocked<
|
|
11
12
|
Editor & {
|
|
12
|
-
user: { getEdgeScrollSpeed:
|
|
13
|
-
getCamera:
|
|
14
|
-
getCameraOptions:
|
|
15
|
-
getZoomLevel:
|
|
16
|
-
getViewportScreenBounds:
|
|
13
|
+
user: { getEdgeScrollSpeed: Mock }
|
|
14
|
+
getCamera: Mock
|
|
15
|
+
getCameraOptions: Mock
|
|
16
|
+
getZoomLevel: Mock
|
|
17
|
+
getViewportScreenBounds: Mock
|
|
17
18
|
}
|
|
18
19
|
>
|
|
19
20
|
let edgeScrollManager: EdgeScrollManager
|
|
@@ -33,33 +34,33 @@ describe('EdgeScrollManager', () => {
|
|
|
33
34
|
isPanning: false,
|
|
34
35
|
},
|
|
35
36
|
user: {
|
|
36
|
-
getEdgeScrollSpeed:
|
|
37
|
+
getEdgeScrollSpeed: vi.fn(() => 1),
|
|
37
38
|
},
|
|
38
|
-
getViewportScreenBounds:
|
|
39
|
-
getInstanceState:
|
|
39
|
+
getViewportScreenBounds: vi.fn(() => new Box(0, 0, 1000, 600)),
|
|
40
|
+
getInstanceState: vi.fn(
|
|
40
41
|
() =>
|
|
41
42
|
({
|
|
42
43
|
isCoarsePointer: false,
|
|
43
44
|
insets: [false, false, false, false], // [top, right, bottom, left]
|
|
44
45
|
}) as any
|
|
45
46
|
),
|
|
46
|
-
getCameraOptions:
|
|
47
|
+
getCameraOptions: vi.fn(() => ({
|
|
47
48
|
isLocked: false,
|
|
48
49
|
panSpeed: 1,
|
|
49
50
|
zoomSpeed: 1,
|
|
50
51
|
zoomSteps: [1],
|
|
51
52
|
wheelBehavior: 'pan' as const,
|
|
52
53
|
})),
|
|
53
|
-
getZoomLevel:
|
|
54
|
-
getCamera:
|
|
55
|
-
setCamera:
|
|
54
|
+
getZoomLevel: vi.fn(() => 1),
|
|
55
|
+
getCamera: vi.fn(() => new Vec(0, 0, 1)),
|
|
56
|
+
setCamera: vi.fn(),
|
|
56
57
|
} as any
|
|
57
58
|
|
|
58
59
|
edgeScrollManager = new EdgeScrollManager(editor as any)
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
afterEach(() => {
|
|
62
|
-
|
|
63
|
+
vi.clearAllMocks()
|
|
63
64
|
})
|
|
64
65
|
|
|
65
66
|
describe('constructor and initialization', () => {
|