diff --git a/apps/examples/src/examples/speech-bubble/CustomShapeWithHandles.tsx b/apps/examples/src/examples/speech-bubble/CustomShapeWithHandles.tsx index cf9b5936b110..44d5f09e0e40 100644 --- a/apps/examples/src/examples/speech-bubble/CustomShapeWithHandles.tsx +++ b/apps/examples/src/examples/speech-bubble/CustomShapeWithHandles.tsx @@ -1,6 +1,10 @@ import { Tldraw } from '@tldraw/tldraw' import '@tldraw/tldraw/tldraw.css' -import { speechBubbleControl } from './SpeechBubble/SpeechBubbleHandle' +import { + DraggingSpeechBubble, + PointingSpeechBubble, + speechBubbleControl, +} from './SpeechBubble/SpeechBubbleHandle' import { SpeechBubbleTool } from './SpeechBubble/SpeechBubbleTool' import { SpeechBubbleUtil } from './SpeechBubble/SpeechBubbleUtil' import { components, customAssetUrls, uiOverrides } from './SpeechBubble/ui-overrides' @@ -18,6 +22,7 @@ export default function CustomShapeWithHandles() {
{ + editor.root.find('select')!.addChild(DraggingSpeechBubble).addChild(PointingSpeechBubble) editor.addControls(speechBubbleControl) }} shapeUtils={shapeUtils} diff --git a/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleHandle.tsx b/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleHandle.tsx index bf43bf72f774..01873e3f42f5 100644 --- a/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleHandle.tsx +++ b/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleHandle.tsx @@ -1,9 +1,23 @@ -import { Control, Editor } from '@tldraw/tldraw' +import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS } from '@tldraw/editor/src/lib/constants' +import { + TLClickEventInfo, + WithPreventDefault, +} from '@tldraw/editor/src/lib/editor/types/event-types' +import { + Circle2d, + Control, + ControlProps, + Editor, + SVGContainer, + StateNode, + TLPointerEventInfo, + Vec, +} from '@tldraw/tldraw' import { SpeechBubbleShape } from './SpeechBubbleUtil' export function speechBubbleControl(editor: Editor) { // we only show the control in select.idle - if (!editor.isIn('select.idle')) return null + if (!editor.isInAny('select.idle', 'select.pointingSpeechBubble')) return null // it's only relevant when we have a single speech bubble shape selected const shape = editor.getOnlySelectedShape() @@ -12,11 +26,148 @@ export function speechBubbleControl(editor: Editor) { } // return the control - this handles the actual interaction. - return new SpeechBubbleControl(shape) + return new SpeechBubbleControl(editor, shape) } class SpeechBubbleControl extends Control { - constructor(readonly shape: SpeechBubbleShape) { - super() + constructor( + editor: Editor, + readonly shape: SpeechBubbleShape + ) { + super(editor, 'speech-bubble-handle') + } + + override getGeometry() { + const radius = this.editor.getIsCoarsePointer() ? COARSE_HANDLE_RADIUS : HANDLE_RADIUS + const tailInShapeSpace = { + x: this.shape.props.tail.x * this.shape.props.w, + y: this.shape.props.tail.y * this.shape.props.h, + } + + const tailInPageSpace = this.editor + .getShapePageTransform(this.shape) + .applyToPoint(tailInShapeSpace) + + return Circle2d.fromCenter({ + ...tailInPageSpace, + radius: radius / this.editor.getZoomLevel(), + isFilled: true, + }) + } + + override component({ isHovered }: ControlProps) { + const geom = this.getGeometry() + const zoom = this.editor.getZoomLevel() + + return ( + + {isHovered && ( + + )} + + + ) + } + + override onPointerDown(info: WithPreventDefault): void { + info.preventDefault() + this.editor.root.transition('select.pointingSpeechBubbleTail', this.shape) + } + + override onDoubleClick(info: WithPreventDefault): void { + if (info.phase !== 'up') return + + info.preventDefault() + this.editor.updateShape({ + id: this.shape.id, + type: 'speech-bubble', + props: { + tail: { x: 0.5, y: 1.5 }, + }, + }) + } +} + +export class PointingSpeechBubble extends StateNode { + override id = 'pointingSpeechBubbleTail' + + shape!: SpeechBubbleShape + + override onEnter = (shape: SpeechBubbleShape) => { + this.shape = shape + } + + override onPointerMove = () => { + if (this.editor.inputs.isDragging) { + this.editor.root.transition('select.draggingSpeechBubbleTail', this.shape) + } + } + + override onPointerUp = () => { + this.onCancel() + } + + override onCancel = () => { + this.editor.root.transition('select.idle') + } +} + +export class DraggingSpeechBubble extends StateNode { + override id = 'draggingSpeechBubbleTail' + + initialShape!: SpeechBubbleShape + initialPageTailPoint!: Vec + markId!: string + + // you can pass stuff to this i think? but it doesn't get typed. kinda tricky imo. + override onEnter = (shape: SpeechBubbleShape) => { + this.editor.mark('draggingSpeechBubbleTail') + this.initialShape = shape + const transform = this.editor.getShapePageTransform(shape) + this.initialPageTailPoint = transform.applyToPoint({ + x: shape.props.tail.x * shape.props.w, + y: shape.props.tail.y * shape.props.h, + }) + } + + override onPointerMove = () => { + const currentShape = this.editor.getShape(this.initialShape.id) + if (!currentShape) return + + const delta = Vec.Sub(this.editor.inputs.currentPagePoint, this.editor.inputs.originPagePoint) + const pageTailPoint = Vec.Add(this.initialPageTailPoint, delta) + const shapeTailPoint = this.editor.getPointInShapeSpace(this.initialShape.id, pageTailPoint) + + this.editor.updateShape({ + id: this.initialShape.id, + type: 'speech-bubble', + props: { + tail: { + x: shapeTailPoint.x / currentShape.props.w, + y: shapeTailPoint.y / currentShape.props.h, + }, + }, + }) + } + + override onPointerUp = () => { + this.editor.root.transition('select.idle') + } + + override onCancel = () => { + this.editor.root.transition('select.idle') + this.editor.bailToMark('draggingSpeechBubbleTail') } } diff --git a/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleUtil.tsx b/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleUtil.tsx index 481aa935e177..54027c032c05 100644 --- a/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleUtil.tsx +++ b/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleUtil.tsx @@ -8,13 +8,10 @@ import { TLBaseShape, TLDefaultColorStyle, TLDefaultSizeStyle, - TLHandle, TLOnBeforeUpdateHandler, - TLOnHandleDragHandler, TLOnResizeHandler, Vec, VecModel, - ZERO_INDEX_KEY, deepCopy, getDefaultColorTheme, resizeBox, @@ -83,34 +80,34 @@ export class SpeechBubbleUtil extends ShapeUtil { return body } - // [4] - override getHandles(shape: SpeechBubbleShape): TLHandle[] { - const { tail, w, h } = shape.props - - return [ - { - id: 'tail', - type: 'vertex', - index: ZERO_INDEX_KEY, - // props.tail coordinates are normalized - // but here we need them in shape space - x: tail.x * w, - y: tail.y * h, - }, - ] - } - - override onHandleDrag: TLOnHandleDragHandler = (shape, { handle }) => { - return { - ...shape, - props: { - tail: { - x: handle.x / shape.props.w, - y: handle.y / shape.props.h, - }, - }, - } - } + // // [4] + // override getHandles(shape: SpeechBubbleShape): TLHandle[] { + // const { tail, w, h } = shape.props + + // return [ + // { + // id: 'tail', + // type: 'vertex', + // index: ZERO_INDEX_KEY, + // // props.tail coordinates are normalized + // // but here we need them in shape space + // x: tail.x * w, + // y: tail.y * h, + // }, + // ] + // } + + // override onHandleDrag: TLOnHandleDragHandler = (shape, { handle }) => { + // return { + // ...shape, + // props: { + // tail: { + // x: handle.x / shape.props.w, + // y: handle.y / shape.props.h, + // }, + // }, + // } + // } // [5] override onBeforeUpdate: TLOnBeforeUpdateHandler | undefined = ( diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index b9a72fc860d3..56d44e0bdc68 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -26,6 +26,7 @@ import { PointerEventHandler } from 'react'; import { react } from '@tldraw/state'; import { default as React_2 } from 'react'; import * as React_3 from 'react'; +import { ReactNode } from 'react'; import { SerializedSchema } from '@tldraw/store'; import { SerializedStore } from '@tldraw/store'; import { ShapeProps } from '@tldraw/tlschema'; @@ -310,21 +311,13 @@ export function canonicalizeRotation(a: number): number; // @public (undocumented) export class Circle2d extends Geometry2d { - constructor(config: Omit & { - x?: number; - y?: number; - radius: number; - isFilled: boolean; - }); + constructor(config: Circle2dOpts); // (undocumented) _center: Vec; // (undocumented) - config: Omit & { - x?: number; - y?: number; - radius: number; - isFilled: boolean; - }; + config: Circle2dOpts; + // (undocumented) + static fromCenter(config: Circle2dOpts): Circle2d; // (undocumented) getBounds(): Box; // (undocumented) @@ -361,6 +354,50 @@ export function ContainerProvider({ container, children, }: { children: React.ReactNode; }): JSX_2.Element; +// @public (undocumented) +export abstract class Control { + constructor(editor: Editor, id: string); + // (undocumented) + component(props: ControlProps): ReactNode; + // (undocumented) + readonly editor: Editor; + // (undocumented) + abstract getGeometry(): Geometry2d; + // (undocumented) + getIndex(): number; + // (undocumented) + handleEvent(info: TLClickEventInfo | TLPointerEventInfo | TLWheelEventInfo): void; + // (undocumented) + readonly id: string; + // (undocumented) + onDoubleClick?(info: WithPreventDefault): void; + // (undocumented) + onMiddleClick?(info: WithPreventDefault): void; + // (undocumented) + onPointerDown?(info: WithPreventDefault): void; + // (undocumented) + onPointerMove?(info: WithPreventDefault): void; + // (undocumented) + onPointerUp?(info: WithPreventDefault): void; + // (undocumented) + onQuadrupleClick?(info: WithPreventDefault): void; + // (undocumented) + onRightClick?(info: WithPreventDefault): void; + // (undocumented) + onTripleClick?(info: WithPreventDefault): void; + // (undocumented) + onWheel?(info: WithPreventDefault): void; +} + +// @public (undocumented) +export type ControlFn = (editor: Editor) => Control | Control[] | null; + +// @public (undocumented) +export interface ControlProps { + // (undocumented) + isHovered: boolean; +} + // @public (undocumented) export const coreShapes: readonly [typeof GroupShapeUtil]; @@ -569,6 +606,8 @@ export class Edge2d extends Geometry2d { // @public (undocumented) export class Editor extends EventEmitter { constructor({ store, user, shapeUtils, tools, getContainer, initialState, inferDarkMode, }: TLEditorOptions); + // (undocumented) + addControls(controlFn: ControlFn): void; addOpenMenu(id: string): this; alignShapes(shapes: TLShape[] | TLShapeId[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this; animateShape(partial: null | TLShapePartial | undefined, animationOptions?: TLAnimationOptions): this; @@ -663,12 +702,29 @@ export class Editor extends EventEmitter { getAsset(asset: TLAsset | TLAssetId): TLAsset | undefined; getAssetForExternalContent(info: TLExternalAssetContent): Promise; getAssets(): (TLBookmarkAsset | TLImageAsset | TLVideoAsset)[]; + getAtPoint(point: VecLike, opts?: { + shapes?: { + renderingOnly?: boolean | undefined; + margin?: number | undefined; + hitInside?: boolean | undefined; + hitLabels?: boolean | undefined; + hitFrameInside?: boolean | undefined; + filter?: ((shape: TLShape) => boolean) | undefined; + } | undefined; + controls?: { + filter?: ((control: Control) => boolean) | undefined; + } | undefined; + }): CanvasItem | null; getCamera(): TLCamera; getCameraState(): "idle" | "moving"; getCanRedo(): boolean; getCanUndo(): boolean; getContainer: () => HTMLElement; getContentFromCurrentPage(shapes: TLShape[] | TLShapeId[]): TLContent | undefined; + // (undocumented) + getControls(): readonly Control[]; + // (undocumented) + getControlsGroupedByIndex(): ReadonlyMap; // @internal getCrashingError(): unknown; getCroppingShapeId(): null | TLShapeId; @@ -693,10 +749,14 @@ export class Editor extends EventEmitter { getHighestIndexForParent(parent: TLPage | TLParentId | TLShape): IndexKey; getHintingShape(): NonNullable[]; getHintingShapeIds(): TLShapeId[]; + // (undocumented) + getHovered(): CanvasItem | null; getHoveredShape(): TLShape | undefined; getHoveredShapeId(): null | TLShapeId; getInitialMetaForShape(_shape: TLShape): JsonObject; getInstanceState(): TLInstance; + // (undocumented) + getIsCoarsePointer(): boolean; getIsMenuOpen(): boolean; getOnlySelectedShape(): null | TLShape; getOpenMenus(): string[]; @@ -729,6 +789,7 @@ export class Editor extends EventEmitter { getShape(shape: TLParentId | TLShape): T | undefined; getShapeAncestors(shape: TLShape | TLShapeId, acc?: TLShape[]): TLShape[]; getShapeAndDescendantIds(ids: TLShapeId[]): Set; + // (undocumented) getShapeAtPoint(point: VecLike, opts?: { renderingOnly?: boolean | undefined; margin?: number | undefined; @@ -778,6 +839,7 @@ export class Editor extends EventEmitter { originScreenPoint: Vec; previousPagePoint: Vec; previousScreenPoint: Vec; + _currentPagePoint: Atom; currentPagePoint: Vec; currentScreenPoint: Vec; keys: Set; @@ -834,6 +896,8 @@ export class Editor extends EventEmitter { registerExternalContentHandler(type: T, handler: ((info: T extends TLExternalContent['type'] ? TLExternalContent & { type: T; } : TLExternalContent) => void) | null): this; + // (undocumented) + removeControls(controlFn: ControlFn): void; renamePage(page: TLPage | TLPageId, name: string, historyOptions?: TLCommandHistoryOptions): this; renderingBoundsMargin: number; reparentShapes(shapes: TLShape[] | TLShapeId[], parentId: TLParentId, insertIndex?: IndexKey): this; @@ -1724,6 +1788,8 @@ export class Stadium2d extends Ellipse2d { export abstract class StateNode implements Partial { constructor(editor: Editor, parent?: StateNode); // (undocumented) + addChild(NodeCtor: TLStateNodeConstructor): this; + // (undocumented) static children?: () => TLStateNodeConstructor[]; // (undocumented) children?: Record; @@ -1734,13 +1800,15 @@ export abstract class StateNode implements Partial { enter: (info: any, from: string) => void; // (undocumented) exit: (info: any, from: string) => void; + // (undocumented) + find(path: string | string[]): StateNode | undefined; getCurrent(): StateNode | undefined; // (undocumented) getCurrentToolIdMask(): string | undefined; getIsActive(): boolean; getPath(): string; // (undocumented) - handleEvent: (info: Exclude) => void; + handleEvent: (info: WithPreventDefault>) => void; // (undocumented) static id: string; // (undocumented) @@ -1925,7 +1993,7 @@ export type TLBrushProps = { }; // @public (undocumented) -export type TLCancelEvent = (info: TLCancelEventInfo) => void; +export type TLCancelEvent = (info: WithPreventDefault) => void; // @public (undocumented) export type TLCancelEventInfo = { @@ -1934,7 +2002,7 @@ export type TLCancelEventInfo = { }; // @public (undocumented) -export type TLClickEvent = (info: TLClickEventInfo) => void; +export type TLClickEvent = (info: WithPreventDefault) => void; // @public (undocumented) export type TLClickEventInfo = TLBaseEventInfo & { @@ -1984,7 +2052,7 @@ export type TLCommandHistoryOptions = Partial<{ }>; // @public (undocumented) -export type TLCompleteEvent = (info: TLCompleteEventInfo) => void; +export type TLCompleteEvent = (info: WithPreventDefault) => void; // @public (undocumented) export type TLCompleteEventInfo = { @@ -2247,7 +2315,7 @@ export type TLHoveredShapeIndicatorProps = { }; // @public (undocumented) -export type TLInterruptEvent = (info: TLInterruptEventInfo) => void; +export type TLInterruptEvent = (info: WithPreventDefault) => void; // @public (undocumented) export type TLInterruptEventInfo = { @@ -2256,7 +2324,7 @@ export type TLInterruptEventInfo = { }; // @public (undocumented) -export type TLKeyboardEvent = (info: TLKeyboardEventInfo) => void; +export type TLKeyboardEvent = (info: WithPreventDefault) => void; // @public (undocumented) export type TLKeyboardEventInfo = TLBaseEventInfo & { @@ -2334,7 +2402,7 @@ export type TLOnTranslateHandler = TLEventChangeHandler; export type TLOnTranslateStartHandler = TLEventStartHandler; // @public (undocumented) -export type TLPinchEvent = (info: TLPinchEventInfo) => void; +export type TLPinchEvent = (info: WithPreventDefault) => void; // @public (undocumented) export type TLPinchEventInfo = TLBaseEventInfo & { @@ -2348,7 +2416,7 @@ export type TLPinchEventInfo = TLBaseEventInfo & { export type TLPinchEventName = 'pinch_end' | 'pinch_start' | 'pinch'; // @public (undocumented) -export type TLPointerEvent = (info: TLPointerEventInfo) => void; +export type TLPointerEvent = (info: WithPreventDefault) => void; // @public (undocumented) export type TLPointerEventInfo = TLBaseEventInfo & { @@ -2596,7 +2664,7 @@ export interface TLUserPreferences { } // @public (undocumented) -export type TLWheelEvent = (info: TLWheelEventInfo) => void; +export type TLWheelEvent = (info: WithPreventDefault) => void; // @public (undocumented) export type TLWheelEventInfo = TLBaseEventInfo & { diff --git a/packages/editor/api/api.json b/packages/editor/api/api.json index ced4388f4f39..ac5f9f649d13 100644 --- a/packages/editor/api/api.json +++ b/packages/editor/api/api.json @@ -4266,21 +4266,8 @@ }, { "kind": "Reference", - "text": "Omit", - "canonicalReference": "!Omit:type" - }, - { - "kind": "Content", - "text": "<" - }, - { - "kind": "Reference", - "text": "Geometry2dOptions", - "canonicalReference": "@tldraw/editor!~Geometry2dOptions:interface" - }, - { - "kind": "Content", - "text": ", 'isClosed'> & {\n x?: number;\n y?: number;\n radius: number;\n isFilled: boolean;\n }" + "text": "Circle2dOpts", + "canonicalReference": "@tldraw/editor!~Circle2dOpts:type" }, { "kind": "Content", @@ -4295,7 +4282,7 @@ "parameterName": "config", "parameterTypeTokenRange": { "startIndex": 1, - "endIndex": 5 + "endIndex": 2 }, "isOptional": false } @@ -4312,38 +4299,75 @@ }, { "kind": "Reference", - "text": "Omit", - "canonicalReference": "!Omit:type" + "text": "Circle2dOpts", + "canonicalReference": "@tldraw/editor!~Circle2dOpts:type" }, { "kind": "Content", - "text": "<" + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "config", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Circle2d.fromCenter:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "static fromCenter(config: " }, { "kind": "Reference", - "text": "Geometry2dOptions", - "canonicalReference": "@tldraw/editor!~Geometry2dOptions:interface" + "text": "Circle2dOpts", + "canonicalReference": "@tldraw/editor!~Circle2dOpts:type" }, { "kind": "Content", - "text": ", 'isClosed'> & {\n x?: number;\n y?: number;\n radius: number;\n isFilled: boolean;\n }" + "text": "): " + }, + { + "kind": "Reference", + "text": "Circle2d", + "canonicalReference": "@tldraw/editor!Circle2d:class" }, { "kind": "Content", "text": ";" } ], - "isReadonly": false, - "isOptional": false, - "releaseTag": "Public", - "name": "config", - "propertyTypeTokenRange": { - "startIndex": 1, - "endIndex": 5 + "isStatic": true, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 }, - "isStatic": false, + "releaseTag": "Public", "isProtected": false, - "isAbstract": false + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "config", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "fromCenter" }, { "kind": "Method", @@ -4450,7 +4474,1154 @@ }, { "kind": "Content", - "text": "boolean" + "text": "boolean" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 7, + "endIndex": 8 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "A", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "B", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": false + }, + { + "parameterName": "_zoom", + "parameterTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "hitTestLineSegment" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Circle2d#nearestPoint:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "nearestPoint(point: " + }, + { + "kind": "Reference", + "text": "Vec", + "canonicalReference": "@tldraw/editor!Vec:class" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "Vec", + "canonicalReference": "@tldraw/editor!Vec:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "point", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "nearestPoint" + }, + { + "kind": "Property", + "canonicalReference": "@tldraw/editor!Circle2d#radius:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "radius: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "radius", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + }, + { + "kind": "Property", + "canonicalReference": "@tldraw/editor!Circle2d#x:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "x: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "x", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + }, + { + "kind": "Property", + "canonicalReference": "@tldraw/editor!Circle2d#y:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "y: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "y", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + } + ], + "extendsTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "implementsTokenRanges": [] + }, + { + "kind": "Function", + "canonicalReference": "@tldraw/editor!clamp:function(1)", + "docComment": "/**\n * Clamp a value into a range.\n *\n * @param n - The number to clamp.\n *\n * @param min - The minimum value.\n *\n * @example\n * ```ts\n * const A = clamp(0, 1) // 1\n * ```\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function clamp(n: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ", min: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "packages/editor/src/lib/primitives/utils.ts", + "returnTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "n", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "min", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": false + } + ], + "name": "clamp" + }, + { + "kind": "Function", + "canonicalReference": "@tldraw/editor!clamp:function(2)", + "docComment": "/**\n * Clamp a value into a range.\n *\n * @param n - The number to clamp.\n *\n * @param min - The minimum value.\n *\n * @param max - The maximum value.\n *\n * @example\n * ```ts\n * const A = clamp(0, 1, 10) // 1\n * const B = clamp(11, 1, 10) // 10\n * const C = clamp(5, 1, 10) // 5\n * ```\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function clamp(n: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ", min: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ", max: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "packages/editor/src/lib/primitives/utils.ts", + "returnTypeTokenRange": { + "startIndex": 7, + "endIndex": 8 + }, + "releaseTag": "Public", + "overloadIndex": 2, + "parameters": [ + { + "parameterName": "n", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "min", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": false + }, + { + "parameterName": "max", + "parameterTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + }, + "isOptional": false + } + ], + "name": "clamp" + }, + { + "kind": "Function", + "canonicalReference": "@tldraw/editor!clampRadians:function(1)", + "docComment": "/**\n * Clamp radians within 0 and 2PI\n *\n * @param r - The radian value.\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function clampRadians(r: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "packages/editor/src/lib/primitives/utils.ts", + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "r", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + } + ], + "name": "clampRadians" + }, + { + "kind": "Function", + "canonicalReference": "@tldraw/editor!clockwiseAngleDist:function(1)", + "docComment": "/**\n * Get the clockwise angle distance between two angles.\n *\n * @param a0 - The first angle.\n *\n * @param a1 - The second angle.\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function clockwiseAngleDist(a0: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ", a1: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "fileUrlPath": "packages/editor/src/lib/primitives/utils.ts", + "returnTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "a0", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "a1", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": false + } + ], + "name": "clockwiseAngleDist" + }, + { + "kind": "Class", + "canonicalReference": "@tldraw/editor!Control:class", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare abstract class Control " + } + ], + "fileUrlPath": "packages/editor/src/lib/editor/types/Control.tsx", + "releaseTag": "Public", + "isAbstract": true, + "name": "Control", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Constructor", + "canonicalReference": "@tldraw/editor!Control:constructor(1)", + "docComment": "/**\n * Constructs a new instance of the `Control` class\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "constructor(editor: " + }, + { + "kind": "Reference", + "text": "Editor", + "canonicalReference": "@tldraw/editor!Editor:class" + }, + { + "kind": "Content", + "text": ", id: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ");" + } + ], + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "editor", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "id", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": false + } + ] + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#component:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "component(props: " + }, + { + "kind": "Reference", + "text": "ControlProps", + "canonicalReference": "@tldraw/editor!ControlProps:interface" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "ReactNode", + "canonicalReference": "@types/react!React.ReactNode:type" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "props", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "component" + }, + { + "kind": "Property", + "canonicalReference": "@tldraw/editor!Control#editor:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "readonly editor: " + }, + { + "kind": "Reference", + "text": "Editor", + "canonicalReference": "@tldraw/editor!Editor:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": true, + "isOptional": false, + "releaseTag": "Public", + "name": "editor", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#getGeometry:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "abstract getGeometry(): " + }, + { + "kind": "Reference", + "text": "Geometry2d", + "canonicalReference": "@tldraw/editor!Geometry2d:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": true, + "name": "getGeometry" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#getIndex:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "getIndex(): " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "getIndex" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#handleEvent:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "handleEvent(info: " + }, + { + "kind": "Reference", + "text": "TLClickEventInfo", + "canonicalReference": "@tldraw/editor!TLClickEventInfo:type" + }, + { + "kind": "Content", + "text": " | " + }, + { + "kind": "Reference", + "text": "TLPointerEventInfo", + "canonicalReference": "@tldraw/editor!TLPointerEventInfo:type" + }, + { + "kind": "Content", + "text": " | " + }, + { + "kind": "Reference", + "text": "TLWheelEventInfo", + "canonicalReference": "@tldraw/editor!TLWheelEventInfo:type" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 7, + "endIndex": 8 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "info", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 6 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "handleEvent" + }, + { + "kind": "Property", + "canonicalReference": "@tldraw/editor!Control#id:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "readonly id: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": true, + "isOptional": false, + "releaseTag": "Public", + "name": "id", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#onDoubleClick:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onDoubleClick?(info: " + }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "TLClickEventInfo", + "canonicalReference": "@tldraw/editor!TLClickEventInfo:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 6, + "endIndex": 7 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "info", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isOptional": false + } + ], + "isOptional": true, + "isAbstract": false, + "name": "onDoubleClick" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#onMiddleClick:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onMiddleClick?(info: " + }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "TLPointerEventInfo", + "canonicalReference": "@tldraw/editor!TLPointerEventInfo:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 6, + "endIndex": 7 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "info", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isOptional": false + } + ], + "isOptional": true, + "isAbstract": false, + "name": "onMiddleClick" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#onPointerDown:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onPointerDown?(info: " + }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "TLPointerEventInfo", + "canonicalReference": "@tldraw/editor!TLPointerEventInfo:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 6, + "endIndex": 7 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "info", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isOptional": false + } + ], + "isOptional": true, + "isAbstract": false, + "name": "onPointerDown" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#onPointerMove:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onPointerMove?(info: " + }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "TLPointerEventInfo", + "canonicalReference": "@tldraw/editor!TLPointerEventInfo:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 6, + "endIndex": 7 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "info", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isOptional": false + } + ], + "isOptional": true, + "isAbstract": false, + "name": "onPointerMove" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#onPointerUp:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onPointerUp?(info: " + }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "TLPointerEventInfo", + "canonicalReference": "@tldraw/editor!TLPointerEventInfo:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 6, + "endIndex": 7 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "info", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isOptional": false + } + ], + "isOptional": true, + "isAbstract": false, + "name": "onPointerUp" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#onQuadrupleClick:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onQuadrupleClick?(info: " + }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "TLClickEventInfo", + "canonicalReference": "@tldraw/editor!TLClickEventInfo:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 6, + "endIndex": 7 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "info", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isOptional": false + } + ], + "isOptional": true, + "isAbstract": false, + "name": "onQuadrupleClick" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#onRightClick:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onRightClick?(info: " + }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "TLPointerEventInfo", + "canonicalReference": "@tldraw/editor!TLPointerEventInfo:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" }, { "kind": "Content", @@ -4459,64 +5630,60 @@ ], "isStatic": false, "returnTypeTokenRange": { - "startIndex": 7, - "endIndex": 8 + "startIndex": 6, + "endIndex": 7 }, "releaseTag": "Public", "isProtected": false, "overloadIndex": 1, "parameters": [ { - "parameterName": "A", + "parameterName": "info", "parameterTypeTokenRange": { "startIndex": 1, - "endIndex": 2 - }, - "isOptional": false - }, - { - "parameterName": "B", - "parameterTypeTokenRange": { - "startIndex": 3, - "endIndex": 4 - }, - "isOptional": false - }, - { - "parameterName": "_zoom", - "parameterTypeTokenRange": { - "startIndex": 5, - "endIndex": 6 + "endIndex": 5 }, "isOptional": false } ], - "isOptional": false, + "isOptional": true, "isAbstract": false, - "name": "hitTestLineSegment" + "name": "onRightClick" }, { "kind": "Method", - "canonicalReference": "@tldraw/editor!Circle2d#nearestPoint:member(1)", + "canonicalReference": "@tldraw/editor!Control#onTripleClick:member(1)", "docComment": "", "excerptTokens": [ { "kind": "Content", - "text": "nearestPoint(point: " + "text": "onTripleClick?(info: " }, { "kind": "Reference", - "text": "Vec", - "canonicalReference": "@tldraw/editor!Vec:class" + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" }, { "kind": "Content", - "text": "): " + "text": "<" }, { "kind": "Reference", - "text": "Vec", - "canonicalReference": "@tldraw/editor!Vec:class" + "text": "TLClickEventInfo", + "canonicalReference": "@tldraw/editor!TLClickEventInfo:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" }, { "kind": "Content", @@ -4525,366 +5692,188 @@ ], "isStatic": false, "returnTypeTokenRange": { - "startIndex": 3, - "endIndex": 4 + "startIndex": 6, + "endIndex": 7 }, "releaseTag": "Public", "isProtected": false, "overloadIndex": 1, "parameters": [ { - "parameterName": "point", + "parameterName": "info", "parameterTypeTokenRange": { "startIndex": 1, - "endIndex": 2 + "endIndex": 5 }, "isOptional": false } ], - "isOptional": false, + "isOptional": true, "isAbstract": false, - "name": "nearestPoint" + "name": "onTripleClick" }, { - "kind": "Property", - "canonicalReference": "@tldraw/editor!Circle2d#radius:member", + "kind": "Method", + "canonicalReference": "@tldraw/editor!Control#onWheel:member(1)", "docComment": "", "excerptTokens": [ { "kind": "Content", - "text": "radius: " + "text": "onWheel?(info: " }, { - "kind": "Content", - "text": "number" + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" }, { "kind": "Content", - "text": ";" - } - ], - "isReadonly": false, - "isOptional": false, - "releaseTag": "Public", - "name": "radius", - "propertyTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, - "isStatic": false, - "isProtected": false, - "isAbstract": false - }, - { - "kind": "Property", - "canonicalReference": "@tldraw/editor!Circle2d#x:member", - "docComment": "", - "excerptTokens": [ - { - "kind": "Content", - "text": "x: " + "text": "<" }, { - "kind": "Content", - "text": "number" + "kind": "Reference", + "text": "TLWheelEventInfo", + "canonicalReference": "@tldraw/editor!TLWheelEventInfo:type" }, { "kind": "Content", - "text": ";" - } - ], - "isReadonly": false, - "isOptional": false, - "releaseTag": "Public", - "name": "x", - "propertyTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, - "isStatic": false, - "isProtected": false, - "isAbstract": false - }, - { - "kind": "Property", - "canonicalReference": "@tldraw/editor!Circle2d#y:member", - "docComment": "", - "excerptTokens": [ + "text": ">" + }, { "kind": "Content", - "text": "y: " + "text": "): " }, { "kind": "Content", - "text": "number" + "text": "void" }, { "kind": "Content", "text": ";" } ], - "isReadonly": false, - "isOptional": false, - "releaseTag": "Public", - "name": "y", - "propertyTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 6, + "endIndex": 7 + }, + "releaseTag": "Public", "isProtected": false, - "isAbstract": false + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "info", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isOptional": false + } + ], + "isOptional": true, + "isAbstract": false, + "name": "onWheel" } ], - "extendsTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, "implementsTokenRanges": [] }, { - "kind": "Function", - "canonicalReference": "@tldraw/editor!clamp:function(1)", - "docComment": "/**\n * Clamp a value into a range.\n *\n * @param n - The number to clamp.\n *\n * @param min - The minimum value.\n *\n * @example\n * ```ts\n * const A = clamp(0, 1) // 1\n * ```\n *\n * @public\n */\n", - "excerptTokens": [ - { - "kind": "Content", - "text": "export declare function clamp(n: " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": ", min: " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": "): " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": ";" - } - ], - "fileUrlPath": "packages/editor/src/lib/primitives/utils.ts", - "returnTypeTokenRange": { - "startIndex": 5, - "endIndex": 6 - }, - "releaseTag": "Public", - "overloadIndex": 1, - "parameters": [ - { - "parameterName": "n", - "parameterTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, - "isOptional": false - }, - { - "parameterName": "min", - "parameterTypeTokenRange": { - "startIndex": 3, - "endIndex": 4 - }, - "isOptional": false - } - ], - "name": "clamp" - }, - { - "kind": "Function", - "canonicalReference": "@tldraw/editor!clamp:function(2)", - "docComment": "/**\n * Clamp a value into a range.\n *\n * @param n - The number to clamp.\n *\n * @param min - The minimum value.\n *\n * @param max - The maximum value.\n *\n * @example\n * ```ts\n * const A = clamp(0, 1, 10) // 1\n * const B = clamp(11, 1, 10) // 10\n * const C = clamp(5, 1, 10) // 5\n * ```\n *\n * @public\n */\n", + "kind": "TypeAlias", + "canonicalReference": "@tldraw/editor!ControlFn:type", + "docComment": "/**\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "export declare function clamp(n: " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": ", min: " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": ", max: " - }, - { - "kind": "Content", - "text": "number" + "text": "export type ControlFn = " }, { "kind": "Content", - "text": "): " + "text": "(editor: " }, { - "kind": "Content", - "text": "number" + "kind": "Reference", + "text": "Editor", + "canonicalReference": "@tldraw/editor!Editor:class" }, { "kind": "Content", - "text": ";" - } - ], - "fileUrlPath": "packages/editor/src/lib/primitives/utils.ts", - "returnTypeTokenRange": { - "startIndex": 7, - "endIndex": 8 - }, - "releaseTag": "Public", - "overloadIndex": 2, - "parameters": [ - { - "parameterName": "n", - "parameterTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, - "isOptional": false - }, - { - "parameterName": "min", - "parameterTypeTokenRange": { - "startIndex": 3, - "endIndex": 4 - }, - "isOptional": false + "text": ") => " }, { - "parameterName": "max", - "parameterTypeTokenRange": { - "startIndex": 5, - "endIndex": 6 - }, - "isOptional": false - } - ], - "name": "clamp" - }, - { - "kind": "Function", - "canonicalReference": "@tldraw/editor!clampRadians:function(1)", - "docComment": "/**\n * Clamp radians within 0 and 2PI\n *\n * @param r - The radian value.\n *\n * @public\n */\n", - "excerptTokens": [ - { - "kind": "Content", - "text": "export declare function clampRadians(r: " + "kind": "Reference", + "text": "Control", + "canonicalReference": "@tldraw/editor!Control:class" }, { "kind": "Content", - "text": "number" + "text": " | " }, { - "kind": "Content", - "text": "): " + "kind": "Reference", + "text": "Control", + "canonicalReference": "@tldraw/editor!Control:class" }, { "kind": "Content", - "text": "number" + "text": "[] | null" }, { "kind": "Content", "text": ";" } ], - "fileUrlPath": "packages/editor/src/lib/primitives/utils.ts", - "returnTypeTokenRange": { - "startIndex": 3, - "endIndex": 4 - }, + "fileUrlPath": "packages/editor/src/lib/editor/types/Control.tsx", "releaseTag": "Public", - "overloadIndex": 1, - "parameters": [ - { - "parameterName": "r", - "parameterTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, - "isOptional": false - } - ], - "name": "clampRadians" + "name": "ControlFn", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 8 + } }, { - "kind": "Function", - "canonicalReference": "@tldraw/editor!clockwiseAngleDist:function(1)", - "docComment": "/**\n * Get the clockwise angle distance between two angles.\n *\n * @param a0 - The first angle.\n *\n * @param a1 - The second angle.\n *\n * @public\n */\n", + "kind": "Interface", + "canonicalReference": "@tldraw/editor!ControlProps:interface", + "docComment": "/**\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "export declare function clockwiseAngleDist(a0: " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": ", a1: " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": "): " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": ";" + "text": "export interface ControlProps " } ], - "fileUrlPath": "packages/editor/src/lib/primitives/utils.ts", - "returnTypeTokenRange": { - "startIndex": 5, - "endIndex": 6 - }, + "fileUrlPath": "packages/editor/src/lib/editor/types/Control.tsx", "releaseTag": "Public", - "overloadIndex": 1, - "parameters": [ + "name": "ControlProps", + "preserveMemberOrder": false, + "members": [ { - "parameterName": "a0", - "parameterTypeTokenRange": { + "kind": "PropertySignature", + "canonicalReference": "@tldraw/editor!ControlProps#isHovered:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "isHovered: " + }, + { + "kind": "Content", + "text": "boolean" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "isHovered", + "propertyTypeTokenRange": { "startIndex": 1, "endIndex": 2 - }, - "isOptional": false - }, - { - "parameterName": "a1", - "parameterTypeTokenRange": { - "startIndex": 3, - "endIndex": 4 - }, - "isOptional": false + } } ], - "name": "clockwiseAngleDist" + "extendsTokenRanges": [] }, { "kind": "Variable", @@ -7473,6 +8462,55 @@ } ] }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#addControls:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "addControls(controlFn: " + }, + { + "kind": "Reference", + "text": "ControlFn", + "canonicalReference": "@tldraw/editor!ControlFn:type" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "controlFn", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "addControls" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#addOpenMenu:member(1)", @@ -10017,6 +11055,94 @@ "isAbstract": false, "name": "getAssets" }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#getAtPoint:member(1)", + "docComment": "/**\n * Get the thing at the current point.\n *\n * @param point - The point to check.\n *\n * @param opts - Options for the check: `hitInside` to check if the point is inside the shape, `margin` to check if the point is within a margin of the shape, `hitFrameInside` to check if the point is inside the frame, and `filter` to filter the shapes to check.\n *\n * @returns The shape at the given point, or undefined if there is no shape at the point.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "getAtPoint(point: " + }, + { + "kind": "Reference", + "text": "VecLike", + "canonicalReference": "@tldraw/editor!VecLike:type" + }, + { + "kind": "Content", + "text": ", opts?: " + }, + { + "kind": "Content", + "text": "{\n shapes?: {\n renderingOnly?: boolean | undefined;\n margin?: number | undefined;\n hitInside?: boolean | undefined;\n hitLabels?: boolean | undefined;\n hitFrameInside?: boolean | undefined;\n filter?: ((shape: " + }, + { + "kind": "Reference", + "text": "TLShape", + "canonicalReference": "@tldraw/tlschema!TLShape:type" + }, + { + "kind": "Content", + "text": ") => boolean) | undefined;\n } | undefined;\n controls?: {\n filter?: ((control: " + }, + { + "kind": "Reference", + "text": "Control", + "canonicalReference": "@tldraw/editor!Control:class" + }, + { + "kind": "Content", + "text": ") => boolean) | undefined;\n } | undefined;\n }" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "CanvasItem", + "canonicalReference": "@tldraw/editor!~CanvasItem:type" + }, + { + "kind": "Content", + "text": " | null" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 9, + "endIndex": 11 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "point", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "opts", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 8 + }, + "isOptional": true + } + ], + "isOptional": false, + "isAbstract": false, + "name": "getAtPoint" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#getCamera:member(1)", @@ -10248,6 +11374,91 @@ "isAbstract": false, "name": "getContentFromCurrentPage" }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#getControls:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "getControls(): " + }, + { + "kind": "Content", + "text": "readonly " + }, + { + "kind": "Reference", + "text": "Control", + "canonicalReference": "@tldraw/editor!Control:class" + }, + { + "kind": "Content", + "text": "[]" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 4 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "getControls" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#getControlsGroupedByIndex:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "getControlsGroupedByIndex(): " + }, + { + "kind": "Reference", + "text": "ReadonlyMap", + "canonicalReference": "!ReadonlyMap:interface" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "getControlsGroupedByIndex" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#getCroppingShapeId:member(1)", @@ -11118,6 +12329,42 @@ "isAbstract": false, "name": "getHintingShapeIds" }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#getHovered:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "getHovered(): " + }, + { + "kind": "Reference", + "text": "CanvasItem", + "canonicalReference": "@tldraw/editor!~CanvasItem:type" + }, + { + "kind": "Content", + "text": " | null" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 3 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "getHovered" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#getHoveredShape:member(1)", @@ -11272,6 +12519,37 @@ "isAbstract": false, "name": "getInstanceState" }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#getIsCoarsePointer:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "getIsCoarsePointer(): " + }, + { + "kind": "Content", + "text": "boolean" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "getIsCoarsePointer" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#getIsMenuOpen:member(1)", @@ -12456,7 +13734,7 @@ { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#getShapeAtPoint:member(1)", - "docComment": "/**\n * Get the shape at the current point.\n *\n * @param point - The point to check.\n *\n * @param opts - Options for the check: `hitInside` to check if the point is inside the shape, `margin` to check if the point is within a margin of the shape, `hitFrameInside` to check if the point is inside the frame, and `filter` to filter the shapes to check.\n *\n * @returns The shape at the given point, or undefined if there is no shape at the point.\n */\n", + "docComment": "", "excerptTokens": [ { "kind": "Content", @@ -14284,7 +15562,25 @@ }, { "kind": "Content", - "text": ";\n currentPagePoint: " + "text": ";\n _currentPagePoint: " + }, + { + "kind": "Reference", + "text": "Atom", + "canonicalReference": "@tldraw/state!Atom:interface" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "Vec", + "canonicalReference": "@tldraw/editor!Vec:class" + }, + { + "kind": "Content", + "text": ", unknown>;\n currentPagePoint: " }, { "kind": "Reference", @@ -14342,7 +15638,7 @@ "name": "inputs", "propertyTypeTokenRange": { "startIndex": 1, - "endIndex": 20 + "endIndex": 24 }, "isStatic": false, "isProtected": false, @@ -15808,68 +17104,138 @@ ], "isOptional": false, "isAbstract": false, - "name": "registerExternalAssetHandler" + "name": "registerExternalAssetHandler" + }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#registerExternalContentHandler:member(1)", + "docComment": "/**\n * Register an external content handler. This handler will be called when the editor receives external content of the provided type. For example, the 'image' type handler will be called when a user drops an image onto the canvas.\n *\n * @param type - The type of external content.\n *\n * @param handler - The handler to use for this content type.\n *\n * @example\n * ```ts\n * editor.registerExternalContentHandler('text', myHandler)\n * ```\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "registerExternalContentHandler(type: " + }, + { + "kind": "Content", + "text": "T" + }, + { + "kind": "Content", + "text": ", handler: " + }, + { + "kind": "Content", + "text": "((info: T extends " + }, + { + "kind": "Reference", + "text": "TLExternalContent", + "canonicalReference": "@tldraw/editor!TLExternalContent:type" + }, + { + "kind": "Content", + "text": "['type'] ? " + }, + { + "kind": "Reference", + "text": "TLExternalContent", + "canonicalReference": "@tldraw/editor!TLExternalContent:type" + }, + { + "kind": "Content", + "text": " & {\n type: T;\n } : " + }, + { + "kind": "Reference", + "text": "TLExternalContent", + "canonicalReference": "@tldraw/editor!TLExternalContent:type" + }, + { + "kind": "Content", + "text": ") => void) | null" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "this" + }, + { + "kind": "Content", + "text": ";" + } + ], + "typeParameters": [ + { + "typeParameterName": "T", + "constraintTokenRange": { + "startIndex": 1, + "endIndex": 3 + }, + "defaultTypeTokenRange": { + "startIndex": 0, + "endIndex": 0 + } + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 14, + "endIndex": 15 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "type", + "parameterTypeTokenRange": { + "startIndex": 4, + "endIndex": 5 + }, + "isOptional": false + }, + { + "parameterName": "handler", + "parameterTypeTokenRange": { + "startIndex": 6, + "endIndex": 13 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "registerExternalContentHandler" }, { "kind": "Method", - "canonicalReference": "@tldraw/editor!Editor#registerExternalContentHandler:member(1)", - "docComment": "/**\n * Register an external content handler. This handler will be called when the editor receives external content of the provided type. For example, the 'image' type handler will be called when a user drops an image onto the canvas.\n *\n * @param type - The type of external content.\n *\n * @param handler - The handler to use for this content type.\n *\n * @example\n * ```ts\n * editor.registerExternalContentHandler('text', myHandler)\n * ```\n *\n * @public\n */\n", + "canonicalReference": "@tldraw/editor!Editor#removeControls:member(1)", + "docComment": "", "excerptTokens": [ { "kind": "Content", - "text": "registerExternalContentHandler(type: " - }, - { - "kind": "Content", - "text": "T" - }, - { - "kind": "Content", - "text": ", handler: " - }, - { - "kind": "Content", - "text": "((info: T extends " - }, - { - "kind": "Reference", - "text": "TLExternalContent", - "canonicalReference": "@tldraw/editor!TLExternalContent:type" - }, - { - "kind": "Content", - "text": "['type'] ? " + "text": "removeControls(controlFn: " }, { "kind": "Reference", - "text": "TLExternalContent", - "canonicalReference": "@tldraw/editor!TLExternalContent:type" - }, - { - "kind": "Content", - "text": " & {\n type: T;\n } : " - }, - { - "kind": "Reference", - "text": "TLExternalContent", - "canonicalReference": "@tldraw/editor!TLExternalContent:type" - }, - { - "kind": "Content", - "text": ") => void) | null" + "text": "ControlFn", + "canonicalReference": "@tldraw/editor!ControlFn:type" }, { "kind": "Content", @@ -15877,55 +17243,34 @@ }, { "kind": "Content", - "text": "this" + "text": "void" }, { "kind": "Content", "text": ";" } ], - "typeParameters": [ - { - "typeParameterName": "T", - "constraintTokenRange": { - "startIndex": 1, - "endIndex": 3 - }, - "defaultTypeTokenRange": { - "startIndex": 0, - "endIndex": 0 - } - } - ], "isStatic": false, "returnTypeTokenRange": { - "startIndex": 14, - "endIndex": 15 + "startIndex": 3, + "endIndex": 4 }, "releaseTag": "Public", "isProtected": false, "overloadIndex": 1, "parameters": [ { - "parameterName": "type", - "parameterTypeTokenRange": { - "startIndex": 4, - "endIndex": 5 - }, - "isOptional": false - }, - { - "parameterName": "handler", + "parameterName": "controlFn", "parameterTypeTokenRange": { - "startIndex": 6, - "endIndex": 13 + "startIndex": 1, + "endIndex": 2 }, "isOptional": false } ], "isOptional": false, "isAbstract": false, - "name": "registerExternalContentHandler" + "name": "removeControls" }, { "kind": "Method", @@ -33026,6 +34371,55 @@ } ] }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!StateNode#addChild:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "addChild(NodeCtor: " + }, + { + "kind": "Reference", + "text": "TLStateNodeConstructor", + "canonicalReference": "@tldraw/editor!TLStateNodeConstructor:interface" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "this" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "NodeCtor", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "addChild" + }, { "kind": "Property", "canonicalReference": "@tldraw/editor!StateNode#children:member", @@ -33200,6 +34594,59 @@ "isProtected": false, "isAbstract": false }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!StateNode#find:member(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "find(path: " + }, + { + "kind": "Content", + "text": "string | string[]" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "StateNode", + "canonicalReference": "@tldraw/editor!StateNode:class" + }, + { + "kind": "Content", + "text": " | undefined" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 5 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "path", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + } + ], + "isOptional": false, + "isAbstract": false, + "name": "find" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!StateNode#getCurrent:member(1)", @@ -33342,6 +34789,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "Exclude", @@ -33367,7 +34823,7 @@ }, { "kind": "Content", - "text": ">) => void" + "text": ">>) => void" }, { "kind": "Content", @@ -33380,7 +34836,7 @@ "name": "handleEvent", "propertyTypeTokenRange": { "startIndex": 1, - "endIndex": 8 + "endIndex": 10 }, "isStatic": false, "isProtected": false, @@ -35579,6 +37035,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "TLCancelEventInfo", @@ -35586,7 +37051,7 @@ }, { "kind": "Content", - "text": ") => void" + "text": ">) => void" }, { "kind": "Content", @@ -35598,7 +37063,7 @@ "name": "TLCancelEvent", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { @@ -35640,6 +37105,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "TLClickEventInfo", @@ -35647,7 +37121,7 @@ }, { "kind": "Content", - "text": ") => void" + "text": ">) => void" }, { "kind": "Content", @@ -35659,7 +37133,7 @@ "name": "TLClickEvent", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { @@ -35943,6 +37417,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "TLCompleteEventInfo", @@ -35950,7 +37433,7 @@ }, { "kind": "Content", - "text": ") => void" + "text": ">) => void" }, { "kind": "Content", @@ -35962,7 +37445,7 @@ "name": "TLCompleteEvent", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { @@ -38423,6 +39906,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "TLInterruptEventInfo", @@ -38430,7 +39922,7 @@ }, { "kind": "Content", - "text": ") => void" + "text": ">) => void" }, { "kind": "Content", @@ -38442,7 +39934,7 @@ "name": "TLInterruptEvent", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { @@ -38484,6 +39976,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "TLKeyboardEventInfo", @@ -38491,7 +39992,7 @@ }, { "kind": "Content", - "text": ") => void" + "text": ">) => void" }, { "kind": "Content", @@ -38503,7 +40004,7 @@ "name": "TLKeyboardEvent", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { @@ -39642,6 +41143,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "TLPinchEventInfo", @@ -39649,7 +41159,7 @@ }, { "kind": "Content", - "text": ") => void" + "text": ">) => void" }, { "kind": "Content", @@ -39661,7 +41171,7 @@ "name": "TLPinchEvent", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { @@ -39761,6 +41271,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "TLPointerEventInfo", @@ -39768,7 +41287,7 @@ }, { "kind": "Content", - "text": ") => void" + "text": ">) => void" }, { "kind": "Content", @@ -39780,7 +41299,7 @@ "name": "TLPointerEvent", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { @@ -41720,6 +43239,15 @@ "kind": "Content", "text": "(info: " }, + { + "kind": "Reference", + "text": "WithPreventDefault", + "canonicalReference": "@tldraw/editor!~WithPreventDefault:type" + }, + { + "kind": "Content", + "text": "<" + }, { "kind": "Reference", "text": "TLWheelEventInfo", @@ -41727,7 +43255,7 @@ }, { "kind": "Content", - "text": ") => void" + "text": ">) => void" }, { "kind": "Content", @@ -41739,7 +43267,7 @@ "name": "TLWheelEvent", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 4d384758f053..6cebe55f23e9 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -192,7 +192,7 @@ export { getArrowTerminalsInArrowSpace } from './lib/editor/shapes/shared/arrow/ export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/resizeBox' export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool' export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode' -export { Control, type ControlFn } from './lib/editor/types/Control' +export { Control, type ControlFn, type ControlProps } from './lib/editor/types/Control' export { type SvgExportContext, type SvgExportDef } from './lib/editor/types/SvgExportContext' export { type TLContent } from './lib/editor/types/clipboard-types' export { type TLEventMap, type TLEventMapHandler } from './lib/editor/types/emit-types' diff --git a/packages/editor/src/lib/components/GeometryDebuggingView.tsx b/packages/editor/src/lib/components/GeometryDebuggingView.tsx index 9bfccd678460..ca926ef6cada 100644 --- a/packages/editor/src/lib/components/GeometryDebuggingView.tsx +++ b/packages/editor/src/lib/components/GeometryDebuggingView.tsx @@ -1,23 +1,10 @@ import { track } from '@tldraw/state' import { modulate } from '@tldraw/utils' -import { useEffect, useState } from 'react' import { useEditor } from '../hooks/useEditor' +import { Vec } from '../primitives/Vec' import { Geometry2d } from '../primitives/geometry/Geometry2d' import { Group2d } from '../primitives/geometry/Group2d' -function useTick(isEnabled = true) { - const [_, setTick] = useState(0) - const editor = useEditor() - useEffect(() => { - if (!isEnabled) return - const update = () => setTick((tick) => tick + 1) - editor.on('tick', update) - return () => { - editor.off('tick', update) - } - }, [editor, isEnabled]) -} - export const GeometryDebuggingView = track(function GeometryDebuggingView({ showStroke = true, showVertices = true, @@ -29,10 +16,9 @@ export const GeometryDebuggingView = track(function GeometryDebuggingView({ }) { const editor = useEditor() - useTick(showClosestPointOnOutline) - const zoomLevel = editor.getZoomLevel() const renderingShapes = editor.getRenderingShapes() + const controls = editor.getControls() const { inputs: { currentPagePoint }, } = editor @@ -71,17 +57,17 @@ export const GeometryDebuggingView = track(function GeometryDebuggingView({ strokeLinecap="round" strokeLinejoin="round" > - {showStroke && } + {showStroke && } {showVertices && vertices.map((v, i) => ( ))} {showClosestPointOnOutline && dist < 150 && ( @@ -92,7 +78,49 @@ export const GeometryDebuggingView = track(function GeometryDebuggingView({ y2={pointInShapeSpace.y} opacity={1 - dist / 150} stroke={hitInside ? 'goldenrod' : 'dodgerblue'} - strokeWidth="2" + strokeWidth={2 / zoomLevel} + /> + )} + + ) + })} + + {controls.map((control, i) => { + const geometry = control.getGeometry() + + const nearestPointOnControl = geometry.nearestPoint(currentPagePoint) + const distanceToPoint = Vec.Dist(nearestPointOnControl, currentPagePoint) + const dist = distanceToPoint * zoomLevel + const hitInside = distanceToPoint < 0 + + const { vertices } = geometry + + return ( + + {showStroke && ( + + )} + {showVertices && + vertices.map((v, i) => ( + + ))} + {showClosestPointOnOutline && dist < 150 && ( + )} @@ -102,12 +130,25 @@ export const GeometryDebuggingView = track(function GeometryDebuggingView({ ) }) -function GeometryStroke({ geometry }: { geometry: Geometry2d }) { +function GeometryStroke({ + geometry, + defaultColor = 'red', + zoomLevel, +}: { + geometry: Geometry2d + defaultColor?: string + zoomLevel: number +}) { if (geometry instanceof Group2d) { return ( <> {[...geometry.children, ...geometry.ignoredChildren].map((child, i) => ( - + ))} ) @@ -115,8 +156,8 @@ function GeometryStroke({ geometry }: { geometry: Geometry2d }) { return ( +
@@ -561,3 +563,41 @@ function InFrontOfTheCanvasWrapper() { if (!InFrontOfTheCanvas) return null return } + +function ControlsWrapper() { + const editor = useEditor() + const controls = useValue('controls', () => editor.getControls(), [editor]) + const hoveredControl = useValue( + 'hovered control', + () => { + const hovered = editor.getHovered() + if (!hovered || hovered.type !== 'control') return null + return hovered.control + }, + [editor] + ) + return ( + <> + {controls.map((control) => { + return ( + + ) + })} + + ) +} + +const ControlWrapper = track(function ControlWrapper({ + control, + isHovered, +}: { + control: Control + isHovered: boolean +}) { + if (!control.component) return null + return control.component({ isHovered }) +}) diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index 9fac862433ae..13b178b0635c 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -53,6 +53,7 @@ import { getIndicesAbove, getIndicesBetween, getOwnProperty, + groupBy, hasOwnProperty, objectMapValues, sortById, @@ -121,6 +122,7 @@ import { RootState } from './tools/RootState' import { StateNode, TLStateNodeConstructor } from './tools/StateNode' import { Control, ControlFn } from './types/Control' import { SvgExportContext, SvgExportDef } from './types/SvgExportContext' +import { CanvasItem } from './types/canvas-types' import { TLContent } from './types/clipboard-types' import { TLEventMap } from './types/emit-types' import { @@ -128,6 +130,7 @@ import { TLPinchEventInfo, TLPointerEventInfo, TLWheelEventInfo, + WithPreventDefault, } from './types/event-types' import { TLExternalAssetContent, TLExternalContent } from './types/external-content' import { TLCommandHistoryOptions } from './types/history-types' @@ -1196,6 +1199,10 @@ export class Editor extends EventEmitter { return this.store.get(TLINSTANCE_ID)! } + @computed getIsCoarsePointer(): boolean { + return this.getInstanceState().isCoarsePointer + } + /** * Update the instance's state. * @@ -4248,14 +4255,6 @@ export class Editor extends EventEmitter { .find((shape) => this.isPointInShape(shape, point, { hitInside: true, margin: 0 })) } - /** - * Get the shape at the current point. - * - * @param point - The point to check. - * @param opts - Options for the check: `hitInside` to check if the point is inside the shape, `margin` to check if the point is within a margin of the shape, `hitFrameInside` to check if the point is inside the frame, and `filter` to filter the shapes to check. - * - * @returns The shape at the given point, or undefined if there is no shape at the point. - */ getShapeAtPoint( point: VecLike, opts = {} as { @@ -4431,6 +4430,82 @@ export class Editor extends EventEmitter { return inMarginClosestToEdgeHit || inHollowSmallestAreaHit || undefined } + /** + * Get the thing at the current point. + * + * @param point - The point to check. + * @param opts - Options for the check: `hitInside` to check if the point is inside the shape, `margin` to check if the point is within a margin of the shape, `hitFrameInside` to check if the point is inside the frame, and `filter` to filter the shapes to check. + * + * @returns The shape at the given point, or undefined if there is no shape at the point. + */ + getAtPoint( + point: VecLike, + opts = {} as { + shapes?: { + renderingOnly?: boolean + margin?: number + hitInside?: boolean + hitLabels?: boolean + hitFrameInside?: boolean + filter?: (shape: TLShape) => boolean + } + controls?: { + filter?: (control: Control) => boolean + } + } + ): CanvasItem | null { + const pointVec = Vec.From(point) + + // controls are always "above" shapes, so we check them first. + const controlOpts = opts.controls ?? {} + + // we check controls according to their index. the higher the index, the higher the priority + // of the control. if two controls have the same index, we look for the one that is closer + // to the cursor. + const controlsByIndex = Array.from(this.getControlsGroupedByIndex()) + .sort(([idxA], [idxB]) => idxA - idxB) + .reverse() + + for (const [_, controls] of controlsByIndex) { + let closestControl: Control | null = null + let distanceToClosestControl = Infinity + + for (const control of controls) { + if (controlOpts.filter && !controlOpts.filter(control)) continue + + const geometry = control.getGeometry() + const distance = geometry.distanceToPoint(pointVec) + + if (distance < 0 && distance < distanceToClosestControl) { + closestControl = control + distanceToClosestControl = distance + } + } + + if (closestControl) { + return { type: 'control', control: closestControl } + } + } + + const shape = this.getShapeAtPoint(point, opts.shapes) + if (shape) { + return { type: 'shape', shape } + } + + return null + } + + @computed getHovered(): CanvasItem | null { + return this.getAtPoint(this.inputs.currentPagePoint, { + shapes: { + hitInside: false, + hitLabels: false, + margin: HIT_TEST_MARGIN / this.getZoomLevel(), + renderingOnly: true, + }, + }) + } + /** * Get the shapes, if any, at a given page point. * @@ -8260,8 +8335,14 @@ export class Editor extends EventEmitter { previousPagePoint: new Vec(), /** The previous pointer position in screen space. */ previousScreenPoint: new Vec(), + _currentPagePoint: atom('current page point', new Vec()), /** The most recent pointer position in the current page space. */ - currentPagePoint: new Vec(), + get currentPagePoint() { + return this._currentPagePoint.get() + }, + set currentPagePoint(v: Vec) { + this._currentPagePoint.set(v) + }, /** The most recent pointer position in screen space. */ currentScreenPoint: new Vec(), /** A set containing the currently pressed keys. */ @@ -8298,8 +8379,7 @@ export class Editor extends EventEmitter { private _updateInputsFromEvent( info: TLPointerEventInfo | TLPinchEventInfo | TLWheelEventInfo ): void { - const { previousScreenPoint, previousPagePoint, currentScreenPoint, currentPagePoint } = - this.inputs + const { previousScreenPoint, previousPagePoint, currentScreenPoint } = this.inputs const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)! const { x: cx, y: cy, z: cz } = this.getCamera() @@ -8309,14 +8389,15 @@ export class Editor extends EventEmitter { const sz = info.point.z previousScreenPoint.setTo(currentScreenPoint) - previousPagePoint.setTo(currentPagePoint) + previousPagePoint.setTo(this.inputs.currentPagePoint) // The "screen bounds" is relative to the user's actual screen. // The "screen point" is relative to the "screen bounds"; // it will be 0,0 when its actual screen position is equal // to screenBounds.point. This is confusing! currentScreenPoint.set(sx, sy) - currentPagePoint.set(sx / cz - cx, sy / cz - cy, sz ?? 0.5) + this.inputs.currentPagePoint = new Vec(sx / cz - cx, sy / cz - cy, sz ?? 0.5) + const { currentPagePoint } = this.inputs this.inputs.isPen = info.type === 'pointer' && info.isPen @@ -8514,7 +8595,7 @@ export class Editor extends EventEmitter { } } - this.root.handleEvent(info) + this.handleEvent(info) return } @@ -8892,10 +8973,8 @@ export class Editor extends EventEmitter { case 'pointer_down': { const otherEvent = this._clickManager.transformPointerDownEvent(info) if (info.name !== otherEvent.name) { - this.root.handleEvent(info) - this.emit('event', info) - this.root.handleEvent(otherEvent) - this.emit('event', otherEvent) + this.handleEvent(info) + this.handleEvent(otherEvent) return } @@ -8904,10 +8983,8 @@ export class Editor extends EventEmitter { case 'pointer_up': { const otherEvent = this._clickManager.transformPointerUpEvent(info) if (info.name !== otherEvent.name) { - this.root.handleEvent(info) - this.emit('event', info) - this.root.handleEvent(otherEvent) - this.emit('event', otherEvent) + this.handleEvent(info) + this.handleEvent(otherEvent) return } @@ -8923,13 +9000,43 @@ export class Editor extends EventEmitter { // Send the event to the statechart. It will be handled by all // active states, starting at the root. - this.root.handleEvent(info) - this.emit('event', info) + this.handleEvent(info) }) return this } + private handleEvent(rawInfo: TLEventInfo) { + const info: WithPreventDefault = { + ...rawInfo, + preventDefault: () => { + info.defaultPrevented = true + }, + defaultPrevented: false, + } + + console.log('handleEvent', info.type, info.name) + if (info.name === 'double_click') { + console.trace(info) + } + if (info.type === 'pointer' || info.type === 'click' || info.type === 'wheel') { + // if we're a pointerish event, we might get handled by a control: + const hovered = this.getHovered() + if (hovered && hovered.type === 'control' && !this.getIsMenuOpen()) { + hovered.control.handleEvent(info) + } + } + + console.log(' -> defaultPrevented', info.defaultPrevented) + // if we weren't prevented by the control, defer to the normal state chart: + if (info.type !== 'pinch' && !info.defaultPrevented) { + this.root.handleEvent(info) + } + + // next, we hand it to the actual event system: + this.emit('event', info) + } + // controls private controlsMap: Atom>> = atom( 'controls', @@ -8969,6 +9076,9 @@ export class Editor extends EventEmitter { } return results } + @computed getControlsGroupedByIndex(): ReadonlyMap { + return groupBy(this.getControls(), (control) => control.getIndex()) + } } function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) { diff --git a/packages/editor/src/lib/editor/tools/StateNode.ts b/packages/editor/src/lib/editor/tools/StateNode.ts index 4744c7d671c0..3739913aa916 100644 --- a/packages/editor/src/lib/editor/tools/StateNode.ts +++ b/packages/editor/src/lib/editor/tools/StateNode.ts @@ -1,4 +1,5 @@ import { Atom, Computed, atom, computed } from '@tldraw/state' +import { assert, assertExists } from '@tldraw/utils' import type { Editor } from '../Editor' import { EVENT_NAME_MAP, @@ -8,6 +9,7 @@ import { TLExitEventHandler, TLPinchEventInfo, TLTickEventHandler, + WithPreventDefault, } from '../types/event-types' type TLStateNodeType = 'branch' | 'leaf' | 'root' @@ -104,6 +106,34 @@ export abstract class StateNode implements Partial { } private _isActive: Atom + find(path: string | string[]): StateNode | undefined { + if (typeof path === 'string') { + return this.find(path.split('.')) + } + + assert(path.length > 0, 'empty path') + const [childId, ...rest] = path + + const child = this.children?.[childId] + + if (!child) return undefined + if (rest.length) return child.find(rest) + return child + } + + addChild(NodeCtor: TLStateNodeConstructor) { + if (this.type === 'leaf') { + throw new Error('Cannot add child to leaf node') + } + + const children = assertExists(this.children) + if (children[NodeCtor.id]) return this + + const node = new NodeCtor(this.editor, this) + children[node.id] = node + return this + } + /** * Transition to a new active child state node. * @@ -145,7 +175,7 @@ export abstract class StateNode implements Partial { return this } - handleEvent = (info: Exclude) => { + handleEvent = (info: WithPreventDefault>) => { const cbName = EVENT_NAME_MAP[info.name] const x = this.getCurrent() this[cbName]?.(info as any) diff --git a/packages/editor/src/lib/editor/types/Control.tsx b/packages/editor/src/lib/editor/types/Control.tsx index 2806d858e8f1..6d1c8d9e7ed1 100644 --- a/packages/editor/src/lib/editor/types/Control.tsx +++ b/packages/editor/src/lib/editor/types/Control.tsx @@ -1,12 +1,66 @@ +import { computed } from '@tldraw/state' import { ReactNode } from 'react' import { Geometry2d } from '../../primitives/geometry/Geometry2d' import { Editor } from '../Editor' +import { + EVENT_NAME_MAP, + TLClickEventInfo, + TLPointerEventInfo, + TLWheelEventInfo, + WithPreventDefault, +} from './event-types' +/** @public */ export type ControlFn = (editor: Editor) => null | Control | Control[] +/** @public */ +export interface ControlProps { + isHovered: boolean +} + +/** @public */ export abstract class Control { + constructor( + readonly editor: Editor, + readonly id: string + ) { + computedify(this, ['getGeometry']) + } + abstract getGeometry(): Geometry2d - component(): ReactNode { + + getIndex() { + return 0 + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + component(props: ControlProps): ReactNode { return null } + + handleEvent(info: TLPointerEventInfo | TLWheelEventInfo | TLClickEventInfo) { + const handler = EVENT_NAME_MAP[info.name] + ;(this as any)[handler]?.(info) + } + + onWheel?(info: WithPreventDefault): void + onPointerDown?(info: WithPreventDefault): void + onPointerMove?(info: WithPreventDefault): void + onPointerUp?(info: WithPreventDefault): void + onDoubleClick?(info: WithPreventDefault): void + onTripleClick?(info: WithPreventDefault): void + onQuadrupleClick?(info: WithPreventDefault): void + onRightClick?(info: WithPreventDefault): void + onMiddleClick?(info: WithPreventDefault): void +} + +function computedify any }>( + obj: Obj, + keys: readonly Key[] +) { + for (const key of keys) { + const original = obj[key] + const cache = computed(key, () => original.call(obj)) + ;(obj[key] as any) = () => cache.get() + } } diff --git a/packages/editor/src/lib/editor/types/canvas-types.ts b/packages/editor/src/lib/editor/types/canvas-types.ts new file mode 100644 index 000000000000..6dbad06260da --- /dev/null +++ b/packages/editor/src/lib/editor/types/canvas-types.ts @@ -0,0 +1,14 @@ +import { TLShape } from '@tldraw/tlschema' +import { Control } from './Control' + +export interface ShapeCanvasItem { + readonly type: 'shape' + readonly shape: TLShape +} + +export interface ControlCanvasItem { + readonly type: 'control' + readonly control: Control +} + +export type CanvasItem = ShapeCanvasItem | ControlCanvasItem diff --git a/packages/editor/src/lib/editor/types/event-types.ts b/packages/editor/src/lib/editor/types/event-types.ts index d1959c9a39c1..03640e282f69 100644 --- a/packages/editor/src/lib/editor/types/event-types.ts +++ b/packages/editor/src/lib/editor/types/event-types.ts @@ -40,6 +40,11 @@ export type TLEventName = | 'complete' | 'interrupt' +export type WithPreventDefault = T & { + preventDefault: () => void + defaultPrevented: boolean +} + /** @public */ export interface TLBaseEventInfo { type: UiEventType @@ -112,21 +117,21 @@ export type TLEventInfo = | TLInterruptEventInfo /** @public */ -export type TLPointerEvent = (info: TLPointerEventInfo) => void +export type TLPointerEvent = (info: WithPreventDefault) => void /** @public */ -export type TLClickEvent = (info: TLClickEventInfo) => void +export type TLClickEvent = (info: WithPreventDefault) => void /** @public */ -export type TLKeyboardEvent = (info: TLKeyboardEventInfo) => void +export type TLKeyboardEvent = (info: WithPreventDefault) => void /** @public */ -export type TLPinchEvent = (info: TLPinchEventInfo) => void +export type TLPinchEvent = (info: WithPreventDefault) => void /** @public */ -export type TLWheelEvent = (info: TLWheelEventInfo) => void +export type TLWheelEvent = (info: WithPreventDefault) => void /** @public */ -export type TLCancelEvent = (info: TLCancelEventInfo) => void +export type TLCancelEvent = (info: WithPreventDefault) => void /** @public */ -export type TLCompleteEvent = (info: TLCompleteEventInfo) => void +export type TLCompleteEvent = (info: WithPreventDefault) => void /** @public */ -export type TLInterruptEvent = (info: TLInterruptEventInfo) => void +export type TLInterruptEvent = (info: WithPreventDefault) => void /** @public */ export type UiEvent = diff --git a/packages/editor/src/lib/primitives/geometry/Circle2d.ts b/packages/editor/src/lib/primitives/geometry/Circle2d.ts index e4089bbbc4f7..de59da3b1ab6 100644 --- a/packages/editor/src/lib/primitives/geometry/Circle2d.ts +++ b/packages/editor/src/lib/primitives/geometry/Circle2d.ts @@ -5,6 +5,13 @@ import { PI2, getPointOnCircle } from '../utils' import { Geometry2d, Geometry2dOptions } from './Geometry2d' import { getVerticesCountForLength } from './geometry-constants' +type Circle2dOpts = Omit & { + x?: number + y?: number + radius: number + isFilled: boolean +} + /** @public */ export class Circle2d extends Geometry2d { _center: Vec @@ -12,14 +19,15 @@ export class Circle2d extends Geometry2d { x: number y: number - constructor( - public config: Omit & { - x?: number - y?: number - radius: number - isFilled: boolean - } - ) { + static fromCenter(config: Circle2dOpts) { + return new Circle2d({ + ...config, + x: (config.x ?? 0) - config.radius, + y: (config.y ?? 0) - config.radius, + }) + } + + constructor(public config: Circle2dOpts) { super({ isClosed: true, ...config }) const { x = 0, y = 0, radius } = config this.x = x diff --git a/packages/tldraw/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts b/packages/tldraw/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts index 7d1c8344ecdf..84081ef14c09 100644 --- a/packages/tldraw/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts +++ b/packages/tldraw/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts @@ -1,20 +1,12 @@ -import { Editor, HIT_TEST_MARGIN, TLShape } from '@tldraw/editor' +import { Editor, TLShape } from '@tldraw/editor' export function getHitShapeOnCanvasPointerDown(editor: Editor): TLShape | undefined { - const zoomLevel = editor.getZoomLevel() - const { - inputs: { currentPagePoint }, - } = editor - - return ( - // hovered shape at point - editor.getShapeAtPoint(currentPagePoint, { - hitInside: false, - hitLabels: false, - margin: HIT_TEST_MARGIN / zoomLevel, - renderingOnly: true, - }) ?? - // selected shape at point - editor.getSelectedShapeAtPoint(currentPagePoint) - ) + const hovered = editor.getHovered() + if (!hovered) { + return editor.getSelectedShapeAtPoint(editor.inputs.currentPagePoint) + } + if (hovered.type !== 'shape') { + return undefined + } + return hovered.shape } diff --git a/packages/tldraw/src/lib/tools/selection-logic/updateHoveredId.ts b/packages/tldraw/src/lib/tools/selection-logic/updateHoveredId.ts index 9f0d4420260d..6a5b30484667 100644 --- a/packages/tldraw/src/lib/tools/selection-logic/updateHoveredId.ts +++ b/packages/tldraw/src/lib/tools/selection-logic/updateHoveredId.ts @@ -1,16 +1,14 @@ -import { Editor, HIT_TEST_MARGIN, TLShape } from '@tldraw/editor' +import { Editor, TLShape } from '@tldraw/editor' export function updateHoveredId(editor: Editor) { // todo: consider replacing `get hoveredShapeId` with this; it would mean keeping hoveredShapeId in memory rather than in the store and possibly re-computing it more often than necessary - const hitShape = editor.getShapeAtPoint(editor.inputs.currentPagePoint, { - hitInside: false, - hitLabels: false, - margin: HIT_TEST_MARGIN / editor.getZoomLevel(), - renderingOnly: true, - }) - - if (!hitShape) return editor.setHoveredShape(null) + const hovered = editor.getHovered() + if (!hovered || hovered.type !== 'shape') { + editor.setHoveredShape(null) + return + } + const hitShape = hovered.shape let shapeToHover: TLShape | undefined = undefined const outermostShape = editor.getOutermostSelectableShape(hitShape) diff --git a/packages/utils/api-report.md b/packages/utils/api-report.md index 63b0f9f1958e..2cfc5d6d32b1 100644 --- a/packages/utils/api-report.md +++ b/packages/utils/api-report.md @@ -104,6 +104,9 @@ export function getOwnProperty(obj: Partial>, // @internal (undocumented) export function getOwnProperty(obj: object, key: string): unknown; +// @internal (undocumented) +export function groupBy(items: readonly T[], getKey: (item: T) => U): Map; + // @internal (undocumented) export function hasOwnProperty(obj: object, key: string): boolean; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 4993266ea3e1..4eb6d032b336 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -2,6 +2,7 @@ export { areArraysShallowEqual, compact, dedupe, + groupBy, last, minBy, partition, diff --git a/packages/utils/src/lib/array.ts b/packages/utils/src/lib/array.ts index a7970696ff00..ca25bffbf67c 100644 --- a/packages/utils/src/lib/array.ts +++ b/packages/utils/src/lib/array.ts @@ -83,3 +83,29 @@ export function areArraysShallowEqual(arr1: readonly T[], arr2: readonly T[]) } return true } + +/** @internal */ +export function groupBy(items: readonly T[], getKey: (item: T) => U): Map { + const map = new Map() + for (const item of items) { + const key = getKey(item) + const collection = map.get(key) + if (collection) { + collection.push(item) + } else { + map.set(key, [item]) + } + } + return map +} + +/** @internal */ +export function sortBy(items: readonly T[], getKey: (item: T) => U): T[] { + return items.slice().sort((a, b) => { + const keyA = getKey(a) + const keyB = getKey(b) + if (keyA < keyB) return -1 + if (keyA > keyB) return 1 + return 0 + }) +}