UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

273 lines (250 loc) • 9.42 kB
import { RecordProps, TLPropsMigrations, TLShape, TLUnknownBinding } from '@tldraw/tlschema' import { Editor } from '../Editor' /** @public */ export interface TLBindingUtilConstructor< T extends TLUnknownBinding, U extends BindingUtil<T> = BindingUtil<T>, > { new (editor: Editor): U type: T['type'] /** Validations for this binding's props. */ props?: RecordProps<T> /** Migrations for this binding's props. */ migrations?: TLPropsMigrations } /** * Options passed to {@link BindingUtil.onBeforeCreate} and {@link BindingUtil.onAfterCreate}, * describing a the creating a binding. * * @public */ export interface BindingOnCreateOptions<Binding extends TLUnknownBinding> { /** The binding being created. */ binding: Binding } /** * Options passed to {@link BindingUtil.onBeforeChange} and {@link BindingUtil.onAfterChange}, * describing the data associated with a binding being changed. * * @public */ export interface BindingOnChangeOptions<Binding extends TLUnknownBinding> { /** The binding record before the change is made. */ bindingBefore: Binding /** The binding record after the change is made. */ bindingAfter: Binding } /** * Options passed to {@link BindingUtil.onBeforeDelete} and {@link BindingUtil.onAfterDelete}, * describing a binding being deleted. * * @public */ export interface BindingOnDeleteOptions<Binding extends TLUnknownBinding> { /** The binding being deleted. */ binding: Binding } /** * Options passed to {@link BindingUtil.onAfterChangeFromShape} and * {@link BindingUtil.onAfterChangeToShape}, describing a bound shape being changed. * * @public */ export interface BindingOnShapeChangeOptions<Binding extends TLUnknownBinding> { /** The binding record linking these two shapes. */ binding: Binding /** The shape record before the change is made. */ shapeBefore: TLShape /** The shape record after the change is made. */ shapeAfter: TLShape /** * Why did this shape change? * - 'self': the shape itself changed * - 'ancestry': the ancestry of the shape changed, but the shape itself may not have done */ reason: 'self' | 'ancestry' } /** * Options passed to {@link BindingUtil.onBeforeIsolateFromShape} and * {@link BindingUtil.onBeforeIsolateToShape}, describing a shape that is about to be isolated from * the one that it's bound to. * * Isolation happens whenever two bound shapes are separated. For example * 1. One is deleted, but the other is not. * 1. One is copied, but the other is not. * 1. One is duplicated, but the other is not. * * In each of these cases, if the remaining shape depends on the binding for its rendering, it may * now be in an inconsistent state. For example, tldraw's arrow shape depends on the binding to know * where the end of the arrow is. If we removed the binding without doing anything else, the arrow * would suddenly be pointing to the wrong location. Instead, when the shape the arrow is pointing * to is deleted, or the arrow is copied/duplicated, we use an isolation callback. The callback * updates the arrow based on the binding that's about to be removed, so it doesn't end up pointing * to the wrong place. * * For this style of consistency update, use isolation callbacks. For actions specific to deletion * (like deleting a sticker when the shape it's bound to is removed), use the delete callbacks * ({@link BindingUtil.onBeforeDeleteFromShape} and {@link BindingUtil.onBeforeDeleteToShape}) * instead. * * @public */ export interface BindingOnShapeIsolateOptions<Binding extends TLUnknownBinding> { /** The binding record that refers to the shape in question. */ binding: Binding /** * The shape being removed. For deletion, this is the deleted shape. For copy/duplicate, this is * the shape that _isn't_ being copied/duplicated and is getting left behind. */ removedShape: TLShape } /** * Options passed to {@link BindingUtil.onBeforeDeleteFromShape} and * {@link BindingUtil.onBeforeDeleteToShape}, describing a bound shape that is about to be deleted. * * See {@link BindingOnShapeIsolateOptions} for discussion on when to use the delete vs. the isolate * callbacks. * * @public */ export interface BindingOnShapeDeleteOptions<Binding extends TLUnknownBinding> { /** The binding record that refers to the shape in question. */ binding: Binding /** The shape that is about to be deleted. */ shape: TLShape } /** @public */ export abstract class BindingUtil<Binding extends TLUnknownBinding = TLUnknownBinding> { constructor(public editor: Editor) {} static props?: RecordProps<TLUnknownBinding> static migrations?: TLPropsMigrations /** * The type of the binding util, which should match the binding's type. * * @public */ static type: string /** * Get the default props for a binding. * * @public */ abstract getDefaultProps(): Partial<Binding['props']> /** * Called whenever a store operation involving this binding type has completed. This is useful * for working with networks of related bindings that may need to update together. * * @example * ```ts * class MyBindingUtil extends BindingUtil<MyBinding> { * changedBindingIds = new Set<TLBindingId>() * * onOperationComplete() { * doSomethingWithChangedBindings(this.changedBindingIds) * this.changedBindingIds.clear() * } * * onAfterChange({ bindingAfter }: BindingOnChangeOptions<MyBinding>) { * this.changedBindingIds.add(bindingAfter.id) * } * } * ``` * * @public */ onOperationComplete?(): void /** * Called when a binding is about to be created. See {@link BindingOnCreateOptions} for details. * * You can optionally return a new binding to replace the one being created - for example, to * set different initial props. * * @public */ onBeforeCreate?(options: BindingOnCreateOptions<Binding>): Binding | void /** * Called after a binding has been created. See {@link BindingOnCreateOptions} for details. * * @public */ onAfterCreate?(options: BindingOnCreateOptions<Binding>): void /** * Called when a binding is about to be changed. See {@link BindingOnChangeOptions} for details. * * Note that this only fires when the binding record is changing, not when the shapes * associated change. Use {@link BindingUtil.onAfterChangeFromShape} and * {@link BindingUtil.onAfterChangeToShape} for that. * * You can optionally return a new binding to replace the one being changed - for example, to * enforce constraints on the binding's props. * * @public */ onBeforeChange?(options: BindingOnChangeOptions<Binding>): Binding | void /** * Called after a binding has been changed. See {@link BindingOnChangeOptions} for details. * * Note that this only fires when the binding record is changing, not when the shapes * associated change. Use {@link BindingUtil.onAfterChangeFromShape} and * {@link BindingUtil.onAfterChangeToShape} for that. * * @public */ onAfterChange?(options: BindingOnChangeOptions<Binding>): void /** * Called when a binding is about to be deleted. See {@link BindingOnDeleteOptions} for details. * * @public */ onBeforeDelete?(options: BindingOnDeleteOptions<Binding>): void /** * Called after a binding has been deleted. See {@link BindingOnDeleteOptions} for details. * * @public */ onAfterDelete?(options: BindingOnDeleteOptions<Binding>): void /** * Called after the shape referenced in a binding's `fromId` is changed. Use this to propagate * any changes to the binding itself or the other shape as needed. See * {@link BindingOnShapeChangeOptions} for details. * * @public */ onAfterChangeFromShape?(options: BindingOnShapeChangeOptions<Binding>): void /** * Called after the shape referenced in a binding's `toId` is changed. Use this to propagate any * changes to the binding itself or the other shape as needed. See * {@link BindingOnShapeChangeOptions} for details. * * @public */ onAfterChangeToShape?(options: BindingOnShapeChangeOptions<Binding>): void /** * Called before the shape referenced in a binding's `fromId` is about to be deleted. Use this * with care - you may want to use {@link BindingUtil.onBeforeIsolateToShape} instead. See * {@link BindingOnShapeDeleteOptions} for details. * * @public */ onBeforeDeleteFromShape?(options: BindingOnShapeDeleteOptions<Binding>): void /** * Called before the shape referenced in a binding's `toId` is about to be deleted. Use this * with care - you may want to use {@link BindingUtil.onBeforeIsolateFromShape} instead. See * {@link BindingOnShapeDeleteOptions} for details. * * @public */ onBeforeDeleteToShape?(options: BindingOnShapeDeleteOptions<Binding>): void /** * Called before the shape referenced in a binding's `fromId` is about to be isolated from the * shape referenced in `toId`. See {@link BindingOnShapeIsolateOptions} for discussion on what * isolation means, and when/how to use this callback. */ onBeforeIsolateFromShape?(options: BindingOnShapeIsolateOptions<Binding>): void /** * Called before the shape referenced in a binding's `toId` is about to be isolated from the * shape referenced in `fromId`. See {@link BindingOnShapeIsolateOptions} for discussion on what * isolation means, and when/how to use this callback. */ onBeforeIsolateToShape?(options: BindingOnShapeIsolateOptions<Binding>): void }