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
+ })
+}