rework canBind callback (#3797)
This PR reworks the `canBind` callback to work with customizable bindings. It now accepts an object with a the shape, the other shape (optional - it may not exist yet), the direction, and the type of the binding. Devs can use this to create shapes that only participate in certain binding types, can have bindings from but not to them, etc. If you're implementing a binding, you can see if binding two shapes is allowed using `editor.canBindShapes(fromShape, toShape, 'my binding type')` ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `improvement` — Improving existing features ### Release Notes #### Breaking changes The `canBind` flag now accepts an options object instead of just the shape in question. If you're relying on its arguments, you need to change from `canBind(shape) {}` to `canBind({shape}) {}`.
This commit is contained in:
parent
2b2778b4f9
commit
87e3d60c90
24 changed files with 188 additions and 72 deletions
|
@ -23,7 +23,6 @@ export class CardShapeUtil extends ShapeUtil<ICardShape> {
|
|||
// [3]
|
||||
override isAspectRatioLocked = (_shape: ICardShape) => false
|
||||
override canResize = (_shape: ICardShape) => true
|
||||
override canBind = (_shape: ICardShape) => true
|
||||
|
||||
// [4]
|
||||
getDefaultProps(): ICardShape['props'] {
|
||||
|
|
|
@ -44,7 +44,6 @@ export class MyShapeUtil extends ShapeUtil<ICustomShape> {
|
|||
}
|
||||
|
||||
// [c]
|
||||
override canBind = () => true
|
||||
override canEdit = () => false
|
||||
override canResize = () => true
|
||||
override isAspectRatioLocked = () => false
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
TLOnTranslateEndHandler,
|
||||
TLOnTranslateStartHandler,
|
||||
TLShapeId,
|
||||
TLShapeUtilCanBindOpts,
|
||||
TLUiComponents,
|
||||
TLUiOverrides,
|
||||
Tldraw,
|
||||
|
@ -44,7 +45,10 @@ class PinShapeUtil extends ShapeUtil<PinShape> {
|
|||
return {}
|
||||
}
|
||||
|
||||
override canBind = () => false
|
||||
override canBind({ toShapeType }: TLShapeUtilCanBindOpts<PinShape>) {
|
||||
// bindings can go _from_ pins to other shapes, but not the other way round
|
||||
return toShapeType !== 'pin'
|
||||
}
|
||||
override canEdit = () => false
|
||||
override canResize = () => false
|
||||
override hideRotateHandle = () => true
|
||||
|
@ -93,7 +97,9 @@ class PinShapeUtil extends ShapeUtil<PinShape> {
|
|||
.getShapesAtPoint(pageAnchor, { hitInside: true })
|
||||
.filter(
|
||||
(shape) =>
|
||||
shape.type !== 'pin' && shape.parentId === pin.parentId && shape.index < pin.index
|
||||
this.editor.canBindShapes({ fromShape: pin, toShape: shape, binding: 'pin' }) &&
|
||||
shape.parentId === pin.parentId &&
|
||||
shape.index < pin.index
|
||||
)
|
||||
|
||||
for (const target of targets) {
|
||||
|
|
|
@ -65,8 +65,6 @@ export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
|
|||
|
||||
override canResize = (_shape: SpeechBubbleShape) => true
|
||||
|
||||
override canBind = (_shape: SpeechBubbleShape) => true
|
||||
|
||||
override canEdit = () => true
|
||||
|
||||
// [3]
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
TLEventHandlers,
|
||||
TLOnTranslateEndHandler,
|
||||
TLOnTranslateStartHandler,
|
||||
TLShapeUtilCanBindOpts,
|
||||
TLUiComponents,
|
||||
TLUiOverrides,
|
||||
Tldraw,
|
||||
|
@ -40,7 +41,10 @@ class StickerShapeUtil extends ShapeUtil<StickerShape> {
|
|||
return {}
|
||||
}
|
||||
|
||||
override canBind = () => false
|
||||
override canBind({ toShapeType }: TLShapeUtilCanBindOpts<StickerShape>) {
|
||||
// bindings can go _from_ stickers to other shapes, but not the other way round
|
||||
return toShapeType !== 'sticker'
|
||||
}
|
||||
override canEdit = () => false
|
||||
override canResize = () => false
|
||||
override hideRotateHandle = () => true
|
||||
|
@ -86,7 +90,8 @@ class StickerShapeUtil extends ShapeUtil<StickerShape> {
|
|||
const pageAnchor = this.editor.getShapePageTransform(sticker).applyToPoint({ x: 0, y: 0 })
|
||||
const target = this.editor.getShapeAtPoint(pageAnchor, {
|
||||
hitInside: true,
|
||||
filter: (shape) => shape.id !== sticker.id,
|
||||
filter: (shape) =>
|
||||
this.editor.canBindShapes({ fromShape: sticker, toShape: shape, binding: 'sticker' }),
|
||||
})
|
||||
|
||||
if (!target) return
|
||||
|
|
|
@ -46,8 +46,9 @@ import { TLAssetId } from '@tldraw/tlschema';
|
|||
import { TLAssetPartial } from '@tldraw/tlschema';
|
||||
import { TLBaseShape } from '@tldraw/tlschema';
|
||||
import { TLBinding } from '@tldraw/tlschema';
|
||||
import { TLBindingCreate } from '@tldraw/tlschema';
|
||||
import { TLBindingId } from '@tldraw/tlschema';
|
||||
import { TLBindingPartial } from '@tldraw/tlschema';
|
||||
import { TLBindingUpdate } from '@tldraw/tlschema';
|
||||
import { TLBookmarkAsset } from '@tldraw/tlschema';
|
||||
import { TLCamera } from '@tldraw/tlschema';
|
||||
import { TLCursor } from '@tldraw/tlschema';
|
||||
|
@ -702,6 +703,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
};
|
||||
bringForward(shapes: TLShape[] | TLShapeId[]): this;
|
||||
bringToFront(shapes: TLShape[] | TLShapeId[]): this;
|
||||
// (undocumented)
|
||||
canBindShapes({ fromShape, toShape, binding, }: {
|
||||
binding: {
|
||||
type: TLBinding['type'];
|
||||
} | TLBinding | TLBinding['type'];
|
||||
fromShape: {
|
||||
type: TLShape['type'];
|
||||
} | TLShape | TLShape['type'];
|
||||
toShape: {
|
||||
type: TLShape['type'];
|
||||
} | TLShape | TLShape['type'];
|
||||
}): boolean;
|
||||
cancel(): this;
|
||||
cancelDoubleClick(): void;
|
||||
// @internal (undocumented)
|
||||
|
@ -715,9 +728,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
crash(error: unknown): this;
|
||||
createAssets(assets: TLAsset[]): this;
|
||||
// (undocumented)
|
||||
createBinding(partial: RequiredKeys<TLBindingPartial, 'fromId' | 'toId' | 'type'>): this;
|
||||
createBinding<B extends TLBinding = TLBinding>(partial: TLBindingCreate<B>): this;
|
||||
// (undocumented)
|
||||
createBindings(partials: RequiredKeys<TLBindingPartial, 'fromId' | 'toId' | 'type'>[]): this;
|
||||
createBindings(partials: TLBindingCreate[]): this;
|
||||
// @internal (undocumented)
|
||||
createErrorAnnotations(origin: string, willCrashApp: 'unknown' | boolean): {
|
||||
extras: {
|
||||
|
@ -788,7 +801,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getBindingsInvolvingShape<Binding extends TLUnknownBinding = TLBinding>(shape: TLShape | TLShapeId, type?: Binding['type']): Binding[];
|
||||
// (undocumented)
|
||||
getBindingsToShape<Binding extends TLUnknownBinding = TLBinding>(shape: TLShape | TLShapeId, type: Binding['type']): Binding[];
|
||||
getBindingUtil<S extends TLUnknownBinding>(binding: S | TLBindingPartial<S>): BindingUtil<S>;
|
||||
getBindingUtil<S extends TLUnknownBinding>(binding: {
|
||||
type: S['type'];
|
||||
} | S): BindingUtil<S>;
|
||||
// (undocumented)
|
||||
getBindingUtil<S extends TLUnknownBinding>(type: S['type']): BindingUtil<S>;
|
||||
// (undocumented)
|
||||
|
@ -1043,15 +1058,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
ungroupShapes(ids: TLShape[]): this;
|
||||
updateAssets(assets: TLAssetPartial[]): this;
|
||||
// (undocumented)
|
||||
updateBinding(partial: TLBindingPartial): this;
|
||||
updateBinding<B extends TLBinding = TLBinding>(partial: TLBindingUpdate<B>): this;
|
||||
// (undocumented)
|
||||
updateBindings(partials: (null | TLBindingPartial | undefined)[]): this;
|
||||
updateBindings(partials: (null | TLBindingUpdate | undefined)[]): this;
|
||||
updateCurrentPageState(partial: Partial<Omit<TLInstancePageState, 'editingShapeId' | 'focusedGroupId' | 'pageId' | 'selectedShapeIds'>>, historyOptions?: TLHistoryBatchOptions): this;
|
||||
// (undocumented)
|
||||
_updateCurrentPageState: (partial: Partial<Omit<TLInstancePageState, 'selectedShapeIds'>>, historyOptions?: TLHistoryBatchOptions) => void;
|
||||
updateDocumentSettings(settings: Partial<TLDocument>): this;
|
||||
updateInstanceState(partial: Partial<Omit<TLInstance, 'currentPageId'>>, historyOptions?: TLHistoryBatchOptions): this;
|
||||
updatePage(partial: RequiredKeys<TLPage, 'id'>): this;
|
||||
updatePage(partial: RequiredKeys<Partial<TLPage>, 'id'>): this;
|
||||
updateShape<T extends TLUnknownShape>(partial: null | TLShapePartial<T> | undefined): this;
|
||||
updateShapes<T extends TLUnknownShape>(partials: (null | TLShapePartial<T> | undefined)[]): this;
|
||||
updateViewportScreenBounds(screenBounds: Box, center?: boolean): this;
|
||||
|
@ -1706,7 +1721,7 @@ export function refreshPage(): void;
|
|||
export function releasePointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent<Element>): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export type RequiredKeys<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>;
|
||||
export type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function resizeBox(shape: TLBaseBoxShape, info: {
|
||||
|
@ -1785,7 +1800,7 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|||
// @internal
|
||||
backgroundComponent?(shape: Shape): any;
|
||||
canBeLaidOut: TLShapeUtilFlag<Shape>;
|
||||
canBind: <K>(_shape: Shape, _otherShape?: K) => boolean;
|
||||
canBind(opts: TLShapeUtilCanBindOpts<Shape>): boolean;
|
||||
canCrop: TLShapeUtilFlag<Shape>;
|
||||
canDropShapes(shape: Shape, shapes: TLShape[]): boolean;
|
||||
canEdit: TLShapeUtilFlag<Shape>;
|
||||
|
@ -2686,6 +2701,13 @@ export interface TLShapeIndicatorProps {
|
|||
shapeId: TLShapeId;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLShape> {
|
||||
bindingType: string;
|
||||
fromShapeType: string;
|
||||
toShapeType: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLShapeUtilCanvasSvgDef {
|
||||
// (undocumented)
|
||||
|
|
|
@ -168,6 +168,7 @@ export {
|
|||
type TLOnTranslateStartHandler,
|
||||
type TLResizeInfo,
|
||||
type TLResizeMode,
|
||||
type TLShapeUtilCanBindOpts,
|
||||
type TLShapeUtilCanvasSvgDef,
|
||||
type TLShapeUtilConstructor,
|
||||
type TLShapeUtilFlag,
|
||||
|
|
|
@ -18,8 +18,9 @@ import {
|
|||
TLAssetId,
|
||||
TLAssetPartial,
|
||||
TLBinding,
|
||||
TLBindingCreate,
|
||||
TLBindingId,
|
||||
TLBindingPartial,
|
||||
TLBindingUpdate,
|
||||
TLCamera,
|
||||
TLCursor,
|
||||
TLCursorType,
|
||||
|
@ -853,7 +854,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getBindingUtil<S extends TLUnknownBinding>(binding: S | TLBindingPartial<S>): BindingUtil<S>
|
||||
getBindingUtil<S extends TLUnknownBinding>(binding: S | { type: S['type'] }): BindingUtil<S>
|
||||
getBindingUtil<S extends TLUnknownBinding>(type: S['type']): BindingUtil<S>
|
||||
getBindingUtil<T extends BindingUtil>(
|
||||
type: T extends BindingUtil<infer R> ? R['type'] : string
|
||||
|
@ -3619,7 +3620,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
updatePage(partial: RequiredKeys<TLPage, 'id'>): this {
|
||||
updatePage(partial: RequiredKeys<Partial<TLPage>, 'id'>): this {
|
||||
if (this.getInstanceState().isReadonly) return this
|
||||
|
||||
const prev = this.getPage(partial.id)
|
||||
|
@ -5146,27 +5147,36 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return result.filter((b) => b.type === type) as Binding[]
|
||||
}
|
||||
|
||||
createBindings(partials: RequiredKeys<TLBindingPartial, 'type' | 'toId' | 'fromId'>[]) {
|
||||
const bindings = partials.map((partial) => {
|
||||
createBindings(partials: TLBindingCreate[]) {
|
||||
const bindings: TLBinding[] = []
|
||||
for (const partial of partials) {
|
||||
const fromShape = this.getShape(partial.fromId)
|
||||
const toShape = this.getShape(partial.toId)
|
||||
if (!fromShape || !toShape) continue
|
||||
if (!this.canBindShapes({ fromShape, toShape, binding: partial })) continue
|
||||
|
||||
const util = this.getBindingUtil<TLUnknownBinding>(partial.type)
|
||||
const defaultProps = util.getDefaultProps()
|
||||
return this.store.schema.types.binding.create({
|
||||
const binding = this.store.schema.types.binding.create({
|
||||
...partial,
|
||||
id: partial.id ?? createBindingId(),
|
||||
props: {
|
||||
...defaultProps,
|
||||
...partial.props,
|
||||
},
|
||||
})
|
||||
})
|
||||
}) as TLBinding
|
||||
|
||||
bindings.push(binding)
|
||||
}
|
||||
|
||||
this.store.put(bindings)
|
||||
return this
|
||||
}
|
||||
createBinding(partial: RequiredKeys<TLBindingPartial, 'type' | 'fromId' | 'toId'>) {
|
||||
createBinding<B extends TLBinding = TLBinding>(partial: TLBindingCreate<B>) {
|
||||
return this.createBindings([partial])
|
||||
}
|
||||
|
||||
updateBindings(partials: (TLBindingPartial | null | undefined)[]) {
|
||||
updateBindings(partials: (TLBindingUpdate | null | undefined)[]) {
|
||||
const updated: TLBinding[] = []
|
||||
|
||||
for (const partial of partials) {
|
||||
|
@ -5178,6 +5188,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const updatedBinding = applyPartialToRecordWithProps(current, partial)
|
||||
if (updatedBinding === current) continue
|
||||
|
||||
const fromShape = this.getShape(updatedBinding.fromId)
|
||||
const toShape = this.getShape(updatedBinding.toId)
|
||||
if (!fromShape || !toShape) continue
|
||||
if (!this.canBindShapes({ fromShape, toShape, binding: updatedBinding })) continue
|
||||
|
||||
updated.push(updatedBinding)
|
||||
}
|
||||
|
||||
|
@ -5186,7 +5201,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return this
|
||||
}
|
||||
|
||||
updateBinding(partial: TLBindingPartial) {
|
||||
updateBinding<B extends TLBinding = TLBinding>(partial: TLBindingUpdate<B>) {
|
||||
return this.updateBindings([partial])
|
||||
}
|
||||
|
||||
|
@ -5198,6 +5213,30 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
deleteBinding(binding: TLBinding | TLBindingId) {
|
||||
return this.deleteBindings([binding])
|
||||
}
|
||||
canBindShapes({
|
||||
fromShape,
|
||||
toShape,
|
||||
binding,
|
||||
}: {
|
||||
fromShape: TLShape | { type: TLShape['type'] } | TLShape['type']
|
||||
toShape: TLShape | { type: TLShape['type'] } | TLShape['type']
|
||||
binding: TLBinding | { type: TLBinding['type'] } | TLBinding['type']
|
||||
}): boolean {
|
||||
const fromShapeType = typeof fromShape === 'string' ? fromShape : fromShape.type
|
||||
const toShapeType = typeof toShape === 'string' ? toShape : toShape.type
|
||||
const bindingType = typeof binding === 'string' ? binding : binding.type
|
||||
|
||||
const canBindOpts = { fromShapeType, toShapeType, bindingType }
|
||||
|
||||
if (fromShapeType === toShapeType) {
|
||||
return this.getShapeUtil(fromShapeType).canBind(canBindOpts)
|
||||
}
|
||||
|
||||
return (
|
||||
this.getShapeUtil(fromShapeType).canBind(canBindOpts) &&
|
||||
this.getShapeUtil(toShapeType).canBind(canBindOpts)
|
||||
)
|
||||
}
|
||||
|
||||
/* -------------------- Commands -------------------- */
|
||||
|
||||
|
|
|
@ -32,6 +32,21 @@ export interface TLShapeUtilConstructor<
|
|||
/** @public */
|
||||
export type TLShapeUtilFlag<T> = (shape: T) => boolean
|
||||
|
||||
/**
|
||||
* Options passed to {@link ShapeUtil.canBind}. A binding that could be made. At least one of
|
||||
* `fromShapeType` or `toShapeType` will belong to this shape util.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLShape> {
|
||||
/** The type of shape referenced by the `fromId` of the binding. */
|
||||
fromShapeType: string
|
||||
/** The type of shape referenced by the `toId` of the binding. */
|
||||
toShapeType: string
|
||||
/** The type of binding. */
|
||||
bindingType: string
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface TLShapeUtilCanvasSvgDef {
|
||||
key: string
|
||||
|
@ -97,12 +112,13 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|||
canScroll: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Whether the shape can be bound to by an arrow.
|
||||
* Whether the shape can be bound to. See {@link TLShapeUtilCanBindOpts} for details.
|
||||
*
|
||||
* @param _otherShape - The other shape attempting to bind to this shape.
|
||||
* @public
|
||||
*/
|
||||
canBind = <K>(_shape: Shape, _otherShape?: K) => true
|
||||
canBind(opts: TLShapeUtilCanBindOpts<Shape>): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the shape can be double clicked to edit.
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Box } from '../../primitives/Box'
|
|||
import { VecLike } from '../../primitives/Vec'
|
||||
|
||||
/** @public */
|
||||
export type RequiredKeys<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T, K>
|
||||
export type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>
|
||||
/** @public */
|
||||
export type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ import { TLSelectionHandle } from '@tldraw/editor';
|
|||
import { TLShape } from '@tldraw/editor';
|
||||
import { TLShapeId } from '@tldraw/editor';
|
||||
import { TLShapePartial } from '@tldraw/editor';
|
||||
import { TLShapeUtilCanBindOpts } from '@tldraw/editor';
|
||||
import { TLShapeUtilCanvasSvgDef } from '@tldraw/editor';
|
||||
import { TLShapeUtilFlag } from '@tldraw/editor';
|
||||
import { TLStore } from '@tldraw/editor';
|
||||
|
@ -169,7 +170,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
// (undocumented)
|
||||
canBeLaidOut: TLShapeUtilFlag<TLArrowShape>;
|
||||
// (undocumented)
|
||||
canBind: () => boolean;
|
||||
canBind({ toShapeType }: TLShapeUtilCanBindOpts<TLArrowShape>): boolean;
|
||||
// (undocumented)
|
||||
canEdit: () => boolean;
|
||||
// (undocumented)
|
||||
|
@ -618,8 +619,6 @@ export class FrameShapeTool extends BaseBoxShapeTool {
|
|||
|
||||
// @public (undocumented)
|
||||
export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
|
||||
// (undocumented)
|
||||
canBind: () => boolean;
|
||||
// (undocumented)
|
||||
canDropShapes: (shape: TLFrameShape, _shapes: TLShape[]) => boolean;
|
||||
// (undocumented)
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
TLOnTranslateHandler,
|
||||
TLOnTranslateStartHandler,
|
||||
TLShapePartial,
|
||||
TLShapeUtilCanBindOpts,
|
||||
TLShapeUtilCanvasSvgDef,
|
||||
TLShapeUtilFlag,
|
||||
Vec,
|
||||
|
@ -75,7 +76,10 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
static override migrations = arrowShapeMigrations
|
||||
|
||||
override canEdit = () => true
|
||||
override canBind = () => false
|
||||
override canBind({ toShapeType }: TLShapeUtilCanBindOpts<TLArrowShape>): boolean {
|
||||
// bindings can go from arrows to shapes, but not from shapes to arrows
|
||||
return toShapeType !== 'arrow'
|
||||
}
|
||||
override canSnap = () => false
|
||||
override hideResizeHandles: TLShapeUtilFlag<TLArrowShape> = () => true
|
||||
override hideRotateHandle: TLShapeUtilFlag<TLArrowShape> = () => true
|
||||
|
@ -153,7 +157,6 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
index: 'a0',
|
||||
x: info.start.handle.x,
|
||||
y: info.start.handle.y,
|
||||
canBind: true,
|
||||
},
|
||||
{
|
||||
id: ARROW_HANDLES.MIDDLE,
|
||||
|
@ -161,7 +164,6 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
index: 'a2',
|
||||
x: info.middle.x,
|
||||
y: info.middle.y,
|
||||
canBind: false,
|
||||
},
|
||||
{
|
||||
id: ARROW_HANDLES.END,
|
||||
|
@ -169,7 +171,6 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
index: 'a3',
|
||||
x: info.end.handle.x,
|
||||
y: info.end.handle.y,
|
||||
canBind: true,
|
||||
},
|
||||
].filter(Boolean) as TLHandle[]
|
||||
}
|
||||
|
@ -223,7 +224,10 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
hitFrameInside: true,
|
||||
margin: 0,
|
||||
filter: (targetShape) => {
|
||||
return !targetShape.isLocked && this.editor.getShapeUtil(targetShape).canBind(targetShape)
|
||||
return (
|
||||
!targetShape.isLocked &&
|
||||
this.editor.canBindShapes({ fromShape: shape, toShape: targetShape, binding: 'arrow' })
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -384,7 +388,10 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
hitFrameInside: true,
|
||||
margin: 0,
|
||||
filter: (targetShape) => {
|
||||
return !targetShape.isLocked && this.editor.getShapeUtil(targetShape).canBind(targetShape)
|
||||
return (
|
||||
!targetShape.isLocked &&
|
||||
this.editor.canBindShapes({ fromShape: shape, toShape: targetShape, binding: 'arrow' })
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -12,7 +12,10 @@ export class Pointing extends StateNode {
|
|||
|
||||
const target = this.editor.getShapeAtPoint(this.editor.inputs.currentPagePoint, {
|
||||
filter: (targetShape) => {
|
||||
return !targetShape.isLocked && this.editor.getShapeUtil(targetShape).canBind(targetShape)
|
||||
return (
|
||||
!targetShape.isLocked &&
|
||||
this.editor.canBindShapes({ fromShape: 'arrow', toShape: targetShape, binding: 'arrow' })
|
||||
)
|
||||
},
|
||||
margin: 0,
|
||||
hitInside: true,
|
||||
|
@ -47,7 +50,7 @@ export class Pointing extends StateNode {
|
|||
|
||||
this.editor.setCurrentTool('select.dragging_handle', {
|
||||
shape: this.shape,
|
||||
handle: { id: 'end', type: 'vertex', index: 'a3', x: 0, y: 0, canBind: true },
|
||||
handle: { id: 'end', type: 'vertex', index: 'a3', x: 0, y: 0 },
|
||||
isCreating: true,
|
||||
onInteractionEnd: 'arrow',
|
||||
})
|
||||
|
|
|
@ -35,8 +35,6 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
|
|||
static override props = frameShapeProps
|
||||
static override migrations = frameShapeMigrations
|
||||
|
||||
override canBind = () => true
|
||||
|
||||
override canEdit = () => true
|
||||
|
||||
override getDefaultProps(): TLFrameShape['props'] {
|
||||
|
|
|
@ -283,7 +283,10 @@ export class DraggingHandle extends StateNode {
|
|||
const next: TLShapePartial<any> = { id: shape.id, type: shape.type, ...changes }
|
||||
|
||||
// Arrows
|
||||
if (initialHandle.canBind && this.editor.isShapeOfType<TLArrowShape>(shape, 'arrow')) {
|
||||
if (
|
||||
initialHandle.type === 'vertex' &&
|
||||
this.editor.isShapeOfType<TLArrowShape>(shape, 'arrow')
|
||||
) {
|
||||
const bindingAfter = getArrowBindings(editor, shape)[initialHandle.id as 'start' | 'end']
|
||||
|
||||
if (bindingAfter) {
|
||||
|
|
|
@ -253,10 +253,10 @@ export class TestEditor extends Editor {
|
|||
}
|
||||
|
||||
expectShapeToMatch = <T extends TLShape = TLShape>(
|
||||
...model: RequiredKeys<TLShapePartial<T>, 'id'>[]
|
||||
...model: RequiredKeys<Partial<TLShapePartial<T>>, 'id'>[]
|
||||
) => {
|
||||
model.forEach((model) => {
|
||||
const shape = this.getShape(model.id)!
|
||||
const shape = this.getShape(model.id!)!
|
||||
const next = { ...shape, ...model }
|
||||
expect(shape).toCloselyMatchObject(next)
|
||||
})
|
||||
|
|
|
@ -223,7 +223,6 @@ describe('Custom shapes', () => {
|
|||
|
||||
override isAspectRatioLocked = (_shape: CardShape) => false
|
||||
override canResize = (_shape: CardShape) => true
|
||||
override canBind = (_shape: CardShape) => true
|
||||
|
||||
override getDefaultProps(): CardShape['props'] {
|
||||
return {
|
||||
|
|
|
@ -94,19 +94,16 @@ describe('Making an arrow on the page', () => {
|
|||
x: 0,
|
||||
y: 0,
|
||||
type: 'vertex',
|
||||
canBind: true,
|
||||
},
|
||||
{
|
||||
x: 50,
|
||||
y: 0,
|
||||
type: 'virtual',
|
||||
canBind: false,
|
||||
},
|
||||
{
|
||||
x: 100,
|
||||
y: 0,
|
||||
type: 'vertex',
|
||||
canBind: true,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
@ -488,7 +485,8 @@ describe('When starting an arrow inside of multiple shapes', () => {
|
|||
|
||||
expect(
|
||||
editor.getShapeAtPoint(new Vec(25, 25), {
|
||||
filter: (shape) => editor.getShapeUtil(shape).canBind(shape),
|
||||
filter: (shape) =>
|
||||
editor.canBindShapes({ fromShape: 'arrow', toShape: shape, binding: 'arrow' }),
|
||||
hitInside: true,
|
||||
hitFrameInside: true,
|
||||
margin: 0,
|
||||
|
|
|
@ -2,7 +2,7 @@ import {
|
|||
createBindingId,
|
||||
createShapeId,
|
||||
TLArrowShape,
|
||||
TLBindingPartial,
|
||||
TLBindingCreate,
|
||||
TLShapePartial,
|
||||
} from '@tldraw/editor'
|
||||
import { getArrowBindings } from '../lib/shapes/arrow/shared'
|
||||
|
@ -92,7 +92,7 @@ it('creates new bindings for arrows when pasting', async () => {
|
|||
// blood moat incoming
|
||||
describe('When duplicating shapes that include arrows', () => {
|
||||
let shapes: TLShapePartial[]
|
||||
let bindings: TLBindingPartial[]
|
||||
let bindings: TLBindingCreate[]
|
||||
|
||||
beforeEach(() => {
|
||||
const box1 = createShapeId()
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
PI,
|
||||
TLArrowShape,
|
||||
TLArrowShapeProps,
|
||||
TLBindingPartial,
|
||||
TLBindingCreate,
|
||||
TLShapeId,
|
||||
TLShapePartial,
|
||||
createBindingId,
|
||||
|
@ -467,7 +467,7 @@ describe('flipping rotated shapes', () => {
|
|||
|
||||
describe('When flipping shapes that include arrows', () => {
|
||||
let shapes: TLShapePartial[]
|
||||
let bindings: TLBindingPartial[]
|
||||
let bindings: TLBindingCreate[]
|
||||
|
||||
beforeEach(() => {
|
||||
const box1 = createShapeId()
|
||||
|
|
|
@ -945,16 +945,30 @@ export interface TLBaseShape<Type extends string, Props extends object> extends
|
|||
// @public
|
||||
export type TLBinding = TLDefaultBinding | TLUnknownBinding;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBindingCreate<T extends TLBinding = TLBinding> = Expand<{
|
||||
fromId: T['fromId'];
|
||||
id?: TLBindingId;
|
||||
meta?: Partial<T['meta']>;
|
||||
props?: Partial<T['props']>;
|
||||
toId: T['toId'];
|
||||
type: T['type'];
|
||||
typeName?: T['typeName'];
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBindingId = RecordId<TLUnknownBinding>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLBindingPartial<T extends TLBinding = TLBinding> = T extends T ? {
|
||||
export type TLBindingUpdate<T extends TLBinding = TLBinding> = Expand<{
|
||||
fromId?: T['fromId'];
|
||||
id: TLBindingId;
|
||||
meta?: Partial<T['meta']>;
|
||||
props?: Partial<T['props']>;
|
||||
toId?: T['toId'];
|
||||
type: T['type'];
|
||||
} & Partial<Omit<T, 'id' | 'meta' | 'props' | 'type'>> : never;
|
||||
typeName?: T['typeName'];
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export type TLBookmarkAsset = TLBaseAsset<'bookmark', {
|
||||
|
@ -1092,8 +1106,6 @@ export type TLGroupShape = TLBaseShape<'group', TLGroupShapeProps>;
|
|||
|
||||
// @public
|
||||
export interface TLHandle {
|
||||
// (undocumented)
|
||||
canBind?: boolean;
|
||||
// (undocumented)
|
||||
canSnap?: boolean;
|
||||
id: string;
|
||||
|
|
|
@ -60,8 +60,9 @@ export {
|
|||
isBindingId,
|
||||
rootBindingMigrations,
|
||||
type TLBinding,
|
||||
type TLBindingCreate,
|
||||
type TLBindingId,
|
||||
type TLBindingPartial,
|
||||
type TLBindingUpdate,
|
||||
type TLDefaultBinding,
|
||||
type TLUnknownBinding,
|
||||
} from './records/TLBinding'
|
||||
|
|
|
@ -22,7 +22,6 @@ export interface TLHandle {
|
|||
/** A unique identifier for the handle. */
|
||||
id: string
|
||||
type: TLHandleType
|
||||
canBind?: boolean
|
||||
canSnap?: boolean
|
||||
index: IndexKey
|
||||
x: number
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
createRecordMigrationSequence,
|
||||
createRecordType,
|
||||
} from '@tldraw/store'
|
||||
import { mapObjectMapValues } from '@tldraw/utils'
|
||||
import { Expand, mapObjectMapValues } from '@tldraw/utils'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { TLArrowBinding } from '../bindings/TLArrowBinding'
|
||||
|
@ -34,14 +34,26 @@ export type TLUnknownBinding = TLBaseBinding<string, object>
|
|||
export type TLBinding = TLDefaultBinding | TLUnknownBinding
|
||||
|
||||
/** @public */
|
||||
export type TLBindingPartial<T extends TLBinding = TLBinding> = T extends T
|
||||
? {
|
||||
id: TLBindingId
|
||||
type: T['type']
|
||||
props?: Partial<T['props']>
|
||||
meta?: Partial<T['meta']>
|
||||
} & Partial<Omit<T, 'type' | 'id' | 'props' | 'meta'>>
|
||||
: never
|
||||
export type TLBindingUpdate<T extends TLBinding = TLBinding> = Expand<{
|
||||
id: TLBindingId
|
||||
type: T['type']
|
||||
typeName?: T['typeName']
|
||||
fromId?: T['fromId']
|
||||
toId?: T['toId']
|
||||
props?: Partial<T['props']>
|
||||
meta?: Partial<T['meta']>
|
||||
}>
|
||||
|
||||
/** @public */
|
||||
export type TLBindingCreate<T extends TLBinding = TLBinding> = Expand<{
|
||||
id?: TLBindingId
|
||||
type: T['type']
|
||||
typeName?: T['typeName']
|
||||
fromId: T['fromId']
|
||||
toId: T['toId']
|
||||
props?: Partial<T['props']>
|
||||
meta?: Partial<T['meta']>
|
||||
}>
|
||||
|
||||
/** @public */
|
||||
export type TLBindingId = RecordId<TLUnknownBinding>
|
||||
|
|
Loading…
Reference in a new issue