diff --git a/apps/examples/src/examples/custom-options/CustomOptionsExample.tsx b/apps/examples/src/examples/custom-options/CustomOptionsExample.tsx new file mode 100644 index 000000000..55d221169 --- /dev/null +++ b/apps/examples/src/examples/custom-options/CustomOptionsExample.tsx @@ -0,0 +1,15 @@ +import { Tldraw, TldrawOptions } from 'tldraw' +import 'tldraw/tldraw.css' + +const options: Partial = { + maxPages: 3, + animationMediumMs: 5000, +} + +export default function CustomOptionsExample() { + return ( +
+ +
+ ) +} diff --git a/apps/examples/src/examples/custom-options/README.md b/apps/examples/src/examples/custom-options/README.md new file mode 100644 index 000000000..88edf5257 --- /dev/null +++ b/apps/examples/src/examples/custom-options/README.md @@ -0,0 +1,11 @@ +--- +title: Custom options +component: ./CustomOptionsExample.tsx +category: basic +priority: 5 +--- + +Use the `options` property to override tldraw's options. In this example, we limit the maximum +number of pages to 3, and slow down camera animations like zoom in and zoom out. + +--- diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index e15088226..edbe12c69 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -89,12 +89,6 @@ import { whyAmIRunning } from '@tldraw/state'; // @public export function angleDistance(fromAngle: number, toAngle: number, direction: number): number; -// @internal (undocumented) -export const ANIMATION_MEDIUM_MS = 320; - -// @internal (undocumented) -export const ANIMATION_SHORT_MS = 80; - // @internal (undocumented) export function applyRotationToSnapshotShapes({ delta, editor, snapshot, stage, }: { delta: number; @@ -391,9 +385,6 @@ export class Box { // @public (undocumented) export type BoxLike = Box | BoxModel; -// @internal (undocumented) -export const CAMERA_SLIDE_FRICTION = 0.09; - // @public (undocumented) export function canonicalizeRotation(a: number): number; @@ -607,6 +598,50 @@ export function DefaultSpinner(): JSX_2.Element; // @public (undocumented) export const DefaultSvgDefs: () => null; +// @public (undocumented) +export const defaultTldrawOptions: { + readonly adjacentShapeMargin: 10; + readonly animationMediumMs: 320; + readonly cameraMovingTimoutMs: 64; + readonly cameraSlideFriction: 0.09; + readonly coarseDragDistanceSquared: 36; + readonly coarseHandleRadius: 20; + readonly coarsePointerWidth: 12; + readonly collaboratorCheckIntervalMs: 1200; + readonly collaboratorIdleTimeoutMs: 3000; + readonly collaboratorInactiveTimeoutMs: 60000; + readonly defaultSvgPadding: 32; + readonly doubleClickDurationMs: 450; + readonly dragDistanceSquared: 16; + readonly edgeScrollDistance: 8; + readonly edgeScrollSpeed: 20; + readonly followChaseViewportSnap: 2; + readonly gridSteps: readonly [{ + readonly mid: 0.15; + readonly min: -1; + readonly step: 64; + }, { + readonly mid: 0.375; + readonly min: 0.05; + readonly step: 16; + }, { + readonly mid: 1; + readonly min: 0.15; + readonly step: 4; + }, { + readonly mid: 2.5; + readonly min: 0.7; + readonly step: 1; + }]; + readonly handleRadius: 12; + readonly hitTestMargin: 8; + readonly longPressDurationMs: 500; + readonly maxPages: 40; + readonly maxShapesPerPage: 4000; + readonly multiClickDurationMs: 200; + readonly textShadowLod: 0.35; +}; + // @public (undocumented) export const defaultUserPreferences: Readonly<{ animationSpeed: 0 | 1; @@ -622,12 +657,6 @@ export const defaultUserPreferences: Readonly<{ // @public export function degreesToRadians(d: number): number; -// @internal (undocumented) -export const DOUBLE_CLICK_DURATION = 450; - -// @internal (undocumented) -export const DRAG_DISTANCE = 16; - // @public (undocumented) export const EASINGS: { readonly easeInCubic: (t: number) => number; @@ -683,7 +712,7 @@ export class Edge2d extends Geometry2d { // @public (undocumented) export class Editor extends EventEmitter { - constructor({ store, user, shapeUtils, bindingUtils, tools, getContainer, cameraOptions, initialState, autoFocus, inferDarkMode, }: TLEditorOptions); + constructor({ store, user, shapeUtils, bindingUtils, tools, getContainer, cameraOptions, initialState, autoFocus, inferDarkMode, options, }: TLEditorOptions); addOpenMenu(id: string): this; alignShapes(shapes: TLShape[] | TLShapeId[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this; animateShape(partial: null | TLShapePartial | undefined, opts?: Partial<{ @@ -998,6 +1027,8 @@ export class Editor extends EventEmitter { mark(markId?: string): this; moveShapesToPage(shapes: TLShape[] | TLShapeId[], pageId: TLPageId): this; nudgeShapes(shapes: TLShape[] | TLShapeId[], offset: VecLike): this; + // (undocumented) + readonly options: TldrawOptions; packShapes(shapes: TLShape[] | TLShapeId[], gap: number): this; pageToScreen(point: VecLike): Vec; pageToViewport(point: VecLike): Vec; @@ -1051,7 +1082,7 @@ export class Editor extends EventEmitter { readonly sideEffects: StoreSideEffects; slideCamera(opts?: { direction: VecLike; - friction: number; + friction?: number | undefined; speed: number; speedThreshold?: number | undefined; }): this; @@ -1281,13 +1312,6 @@ export function getSvgPathFromPoints(points: VecLike[], closed?: boolean): strin // @public (undocumented) export function getUserPreferences(): TLUserPreferences; -// @public (undocumented) -export const GRID_STEPS: { - mid: number; - min: number; - step: number; -}[]; - // @public (undocumented) export class Group2d extends Geometry2d { constructor(config: Omit & { @@ -1409,9 +1433,6 @@ export class HistoryManager { undo: () => this; } -// @public (undocumented) -export const HIT_TEST_MARGIN = 8; - // @public (undocumented) export function HTMLContainer({ children, className, ...rest }: HTMLContainerProps): JSX_2.Element; @@ -1589,18 +1610,9 @@ export interface MatModel { f: number; } -// @internal (undocumented) -export const MAX_PAGES = 40; - -// @internal (undocumented) -export const MAX_SHAPES_PER_PAGE = 4000; - // @public export function moveCameraWhenCloseToEdge(editor: Editor): void; -// @internal (undocumented) -export const MULTI_CLICK_DURATION = 200; - // @internal (undocumented) export function normalizeWheel(event: React.WheelEvent | WheelEvent): { x: number; @@ -2075,9 +2087,6 @@ export abstract class StateNode implements Partial { // @public (undocumented) export const stopEventPropagation: (e: any) => any; -// @internal (undocumented) -export const SVG_PADDING = 32; - // @public (undocumented) export function SVGContainer({ children, className, ...rest }: SVGContainerProps): JSX_2.Element; @@ -2281,6 +2290,7 @@ export interface TldrawEditorBaseProps { inferDarkMode?: boolean; initialState?: string; onMount?: TLOnMountHandler; + options?: Partial; shapeUtils?: readonly TLAnyShapeUtilConstructor[]; tools?: readonly TLStateNodeConstructor[]; user?: TLUser; @@ -2299,6 +2309,62 @@ export type TldrawEditorProps = Expand; +// @public +export interface TldrawOptions { + // (undocumented) + readonly adjacentShapeMargin: number; + // (undocumented) + readonly animationMediumMs: number; + // (undocumented) + readonly cameraMovingTimoutMs: number; + // (undocumented) + readonly cameraSlideFriction: number; + // (undocumented) + readonly coarseDragDistanceSquared: number; + // (undocumented) + readonly coarseHandleRadius: number; + // (undocumented) + readonly coarsePointerWidth: number; + // (undocumented) + readonly collaboratorCheckIntervalMs: number; + // (undocumented) + readonly collaboratorIdleTimeoutMs: number; + // (undocumented) + readonly collaboratorInactiveTimeoutMs: number; + // (undocumented) + readonly defaultSvgPadding: number; + // (undocumented) + readonly doubleClickDurationMs: number; + // (undocumented) + readonly dragDistanceSquared: number; + // (undocumented) + readonly edgeScrollDistance: number; + // (undocumented) + readonly edgeScrollSpeed: number; + // (undocumented) + readonly followChaseViewportSnap: number; + // (undocumented) + readonly gridSteps: readonly { + readonly mid: number; + readonly min: number; + readonly step: number; + }[]; + // (undocumented) + readonly handleRadius: number; + // (undocumented) + readonly hitTestMargin: number; + // (undocumented) + readonly longPressDurationMs: number; + // (undocumented) + readonly maxPages: number; + // (undocumented) + readonly maxShapesPerPage: number; + // (undocumented) + readonly multiClickDurationMs: number; + // (undocumented) + readonly textShadowLod: number; +} + // @public (undocumented) export type TLEditorComponents = Partial<{ [K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null; @@ -2312,6 +2378,8 @@ export interface TLEditorOptions { getContainer: () => HTMLElement; inferDarkMode?: boolean; initialState?: string; + // (undocumented) + options?: Partial; shapeUtils: readonly TLShapeUtilConstructor[]; store: TLStore; tools: readonly TLStateNodeConstructor[]; diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 2258cdf54..98ff9b0b8 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -106,22 +106,7 @@ export { export { createTLUser } from './lib/config/createTLUser' export { type TLAnyBindingUtilConstructor } from './lib/config/defaultBindings' export { coreShapes, type TLAnyShapeUtilConstructor } from './lib/config/defaultShapes' -export { - ANIMATION_MEDIUM_MS, - ANIMATION_SHORT_MS, - CAMERA_SLIDE_FRICTION, - DEFAULT_ANIMATION_OPTIONS, - DEFAULT_CAMERA_OPTIONS, - DOUBLE_CLICK_DURATION, - DRAG_DISTANCE, - GRID_STEPS, - HIT_TEST_MARGIN, - MAX_PAGES, - MAX_SHAPES_PER_PAGE, - MULTI_CLICK_DURATION, - SIDES, - SVG_PADDING, -} from './lib/constants' +export { DEFAULT_ANIMATION_OPTIONS, DEFAULT_CAMERA_OPTIONS, SIDES } from './lib/constants' export { Editor, type TLEditorOptions, type TLResizeShapeOptions } from './lib/editor/Editor' export { BindingUnbindReason, @@ -247,6 +232,7 @@ export { useSafeId } from './lib/hooks/useSafeId' export { useSelectionEvents } from './lib/hooks/useSelectionEvents' export { useTLStore } from './lib/hooks/useTLStore' export { useTransform } from './lib/hooks/useTransform' +export { defaultTldrawOptions, type TldrawOptions } from './lib/options' export { Box, ROTATE_CORNER_TO_SELECTION_CORNER, diff --git a/packages/editor/src/lib/TldrawEditor.tsx b/packages/editor/src/lib/TldrawEditor.tsx index 7074173d4..8f30cc481 100644 --- a/packages/editor/src/lib/TldrawEditor.tsx +++ b/packages/editor/src/lib/TldrawEditor.tsx @@ -33,6 +33,7 @@ import { useEvent } from './hooks/useEvent' import { useForceUpdate } from './hooks/useForceUpdate' import { useLocalStore } from './hooks/useLocalStore' import { useZoomCss } from './hooks/useZoomCss' +import { TldrawOptions } from './options' import { stopEventPropagation } from './utils/dom' import { TLStoreWithStatus } from './utils/sync/StoreWithStatus' @@ -124,6 +125,11 @@ export interface TldrawEditorBaseProps { * Camera options for the editor. */ cameraOptions?: Partial + + /** + * Options for the editor. + */ + options?: Partial } /** @@ -293,6 +299,7 @@ function TldrawEditorWithReadyStore({ autoFocus = true, inferDarkMode, cameraOptions, + options, }: Required< TldrawEditorProps & { store: TLStore @@ -317,6 +324,7 @@ function TldrawEditorWithReadyStore({ autoFocus: initialAutoFocus, inferDarkMode, cameraOptions, + options, }) setEditor(editor) @@ -334,6 +342,7 @@ function TldrawEditorWithReadyStore({ initialAutoFocus, inferDarkMode, cameraOptions, + options, ]) const crashingError = useSyncExternalStore( diff --git a/packages/editor/src/lib/components/LiveCollaborators.tsx b/packages/editor/src/lib/components/LiveCollaborators.tsx index 097f99de8..13b6d0ebc 100644 --- a/packages/editor/src/lib/components/LiveCollaborators.tsx +++ b/packages/editor/src/lib/components/LiveCollaborators.tsx @@ -1,11 +1,7 @@ import { track } from '@tldraw/state' import { TLInstancePresence } from '@tldraw/tlschema' import { useEffect, useRef, useState } from 'react' -import { - COLLABORATOR_CHECK_INTERVAL, - COLLABORATOR_IDLE_TIMEOUT, - COLLABORATOR_INACTIVE_TIMEOUT, -} from '../constants' +import { Editor } from '../editor/Editor' import { useEditor } from '../hooks/useEditor' import { useEditorComponents } from '../hooks/useEditorComponents' import { usePeerIds } from '../hooks/usePeerIds' @@ -29,7 +25,7 @@ const CollaboratorGuard = track(function CollaboratorGuard({ }) { const editor = useEditor() const presence = usePresence(collaboratorId) - const collaboratorState = useCollaboratorState(presence) + const collaboratorState = useCollaboratorState(editor, presence) if (!(presence && presence.currentPageId === editor.getCurrentPageId())) { // No need to render if we don't have a presence or if they're on a different page @@ -153,28 +149,28 @@ const Collaborator = track(function Collaborator({ ) }) -function getStateFromElapsedTime(elapsed: number) { - return elapsed > COLLABORATOR_INACTIVE_TIMEOUT +function getStateFromElapsedTime(editor: Editor, elapsed: number) { + return elapsed > editor.options.collaboratorInactiveTimeoutMs ? 'inactive' - : elapsed > COLLABORATOR_IDLE_TIMEOUT + : elapsed > editor.options.collaboratorIdleTimeoutMs ? 'idle' : 'active' } -function useCollaboratorState(latestPresence: TLInstancePresence | null) { +function useCollaboratorState(editor: Editor, latestPresence: TLInstancePresence | null) { const rLastActivityTimestamp = useRef(latestPresence?.lastActivityTimestamp ?? -1) const [state, setState] = useState<'active' | 'idle' | 'inactive'>(() => - getStateFromElapsedTime(Date.now() - rLastActivityTimestamp.current) + getStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current) ) useEffect(() => { const interval = setInterval(() => { - setState(getStateFromElapsedTime(Date.now() - rLastActivityTimestamp.current)) - }, COLLABORATOR_CHECK_INTERVAL) + setState(getStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current)) + }, editor.options.collaboratorCheckIntervalMs) return () => clearInterval(interval) - }, []) + }, [editor]) if (latestPresence) { // We can do this on every render, it's free and cheaper than an effect diff --git a/packages/editor/src/lib/components/default-components/DefaultCanvas.tsx b/packages/editor/src/lib/components/default-components/DefaultCanvas.tsx index 70e8531f2..6daa49f0e 100644 --- a/packages/editor/src/lib/components/default-components/DefaultCanvas.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultCanvas.tsx @@ -3,7 +3,6 @@ import { TLHandle, TLShapeId } from '@tldraw/tlschema' import { dedupe, modulate, objectMapValues } from '@tldraw/utils' import classNames from 'classnames' import { Fragment, JSX, useEffect, useRef, useState } from 'react' -import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS, TEXT_SHADOW_LOD } from '../../constants' import { useCanvasEvents } from '../../hooks/useCanvasEvents' import { useCoarsePointer } from '../../hooks/useCoarsePointer' import { useContainer } from '../../hooks/useContainer' @@ -65,9 +64,9 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) { // If we're below the lod distance for text shadows, turn them off if ( rMemoizedStuff.current.allowTextOutline && - z < TEXT_SHADOW_LOD !== rMemoizedStuff.current.lodDisableTextOutline + z < editor.options.textShadowLod !== rMemoizedStuff.current.lodDisableTextOutline ) { - const lodDisableTextOutline = z < TEXT_SHADOW_LOD + const lodDisableTextOutline = z < editor.options.textShadowLod container.style.setProperty( '--tl-text-outline', lodDisableTextOutline @@ -297,7 +296,8 @@ function HandlesWrapperInner({ shapeId }: { shapeId: TLShapeId }) { if (!handles) return null const minDistBetweenVirtualHandlesAndRegularHandles = - ((isCoarse ? COARSE_HANDLE_RADIUS : HANDLE_RADIUS) / zoomLevel) * 2 + ((isCoarse ? editor.options.coarseHandleRadius : editor.options.handleRadius) / zoomLevel) * + 2 return ( handles diff --git a/packages/editor/src/lib/components/default-components/DefaultGrid.tsx b/packages/editor/src/lib/components/default-components/DefaultGrid.tsx index 0f83c2437..477bc81cd 100644 --- a/packages/editor/src/lib/components/default-components/DefaultGrid.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultGrid.tsx @@ -1,5 +1,5 @@ import { modulate } from '@tldraw/utils' -import { GRID_STEPS } from '../../constants' +import { useEditor } from '../../hooks/useEditor' /** @public */ export interface TLGridProps { @@ -11,10 +11,12 @@ export interface TLGridProps { /** @public */ export function DefaultGrid({ x, y, z, size }: TLGridProps) { + const editor = useEditor() + const { gridSteps } = editor.options return ( - {GRID_STEPS.map(({ min, mid, step }, i) => { + {gridSteps.map(({ min, mid, step }, i) => { const s = step * size * z const xo = 0.5 + x * z const yo = 0.5 + y * z @@ -35,7 +37,7 @@ export function DefaultGrid({ x, y, z, size }: TLGridProps) { ) })} - {GRID_STEPS.map(({ step }, i) => ( + {gridSteps.map(({ step }, i) => ( ))} diff --git a/packages/editor/src/lib/components/default-components/DefaultHandle.tsx b/packages/editor/src/lib/components/default-components/DefaultHandle.tsx index 02e3f33c9..2a14d69d7 100644 --- a/packages/editor/src/lib/components/default-components/DefaultHandle.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultHandle.tsx @@ -1,6 +1,7 @@ import { TLHandle, TLShapeId } from '@tldraw/tlschema' import classNames from 'classnames' -import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS, SIDES } from '../../constants' +import { SIDES } from '../../constants' +import { useEditor } from '../../hooks/useEditor' /** @public */ export interface TLHandleProps { @@ -13,7 +14,8 @@ export interface TLHandleProps { /** @public */ export function DefaultHandle({ handle, isCoarse, className, zoom }: TLHandleProps) { - const br = (isCoarse ? COARSE_HANDLE_RADIUS : HANDLE_RADIUS) / zoom + const editor = useEditor() + const br = (isCoarse ? editor.options.coarseHandleRadius : editor.options.handleRadius) / zoom if (handle.type === 'clone') { // bouba diff --git a/packages/editor/src/lib/constants.ts b/packages/editor/src/lib/constants.ts index 48961508f..fca0864fb 100644 --- a/packages/editor/src/lib/constants.ts +++ b/packages/editor/src/lib/constants.ts @@ -1,16 +1,6 @@ import { TLCameraOptions } from './editor/types/misc-types' import { EASINGS } from './primitives/easings' -/** @internal */ -export const MAX_SHAPES_PER_PAGE = 4000 -/** @internal */ -export const MAX_PAGES = 40 - -/** @internal */ -export const ANIMATION_SHORT_MS = 80 -/** @internal */ -export const ANIMATION_MEDIUM_MS = 320 - /** @internal */ export const DEFAULT_CAMERA_OPTIONS: TLCameraOptions = { isLocked: false, @@ -20,49 +10,12 @@ export const DEFAULT_CAMERA_OPTIONS: TLCameraOptions = { zoomSteps: [0.1, 0.25, 0.5, 1, 2, 4, 8], } -/** @internal */ -export const FOLLOW_CHASE_VIEWPORT_SNAP = 2 - -/** @internal */ -export const DOUBLE_CLICK_DURATION = 450 -/** @internal */ -export const MULTI_CLICK_DURATION = 200 - -/** @internal */ -export const COARSE_DRAG_DISTANCE = 36 // 6 squared - -/** @internal */ -export const DRAG_DISTANCE = 16 // 4 squared - -/** @internal */ -export const SVG_PADDING = 32 - /** @internal */ export const DEFAULT_ANIMATION_OPTIONS = { duration: 0, easing: EASINGS.easeInOutCubic, } -/** @internal */ -export const CAMERA_SLIDE_FRICTION = 0.09 - -/** @public */ -export const GRID_STEPS = [ - { min: -1, mid: 0.15, step: 64 }, - { min: 0.05, mid: 0.375, step: 16 }, - { min: 0.15, mid: 1, step: 4 }, - { min: 0.7, mid: 2.5, step: 1 }, -] - -/** @internal */ -export const COLLABORATOR_INACTIVE_TIMEOUT = 60000 - -/** @internal */ -export const COLLABORATOR_IDLE_TIMEOUT = 3000 - -/** @internal */ -export const COLLABORATOR_CHECK_INTERVAL = 1200 - /** * Negative pointer ids are reserved for internal use. * @@ -71,36 +24,9 @@ export const INTERNAL_POINTER_IDS = { CAMERA_MOVE: -10, } as const -/** @internal */ -export const CAMERA_MOVING_TIMEOUT = 64 - -/** @public */ -export const HIT_TEST_MARGIN = 8 - -/** @internal */ -export const EDGE_SCROLL_SPEED = 20 - -/** @internal */ -export const EDGE_SCROLL_DISTANCE = 8 - -/** @internal */ -export const COARSE_POINTER_WIDTH = 12 - -/** @internal */ -export const COARSE_HANDLE_RADIUS = 20 - -/** @internal */ -export const HANDLE_RADIUS = 12 - /** @public */ export const SIDES = ['top', 'right', 'bottom', 'left'] as const -/** @internal */ -export const LONG_PRESS_DURATION = 500 - -/** @internal */ -export const TEXT_SHADOW_LOD = 0.35 - export const LEFT_MOUSE_BUTTON = 0 export const RIGHT_MOUSE_BUTTON = 2 export const MIDDLE_MOUSE_BUTTON = 1 diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index ac0c75680..469fe3f50 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -83,26 +83,16 @@ import { TLUser, createTLUser } from '../config/createTLUser' import { checkBindings } from '../config/defaultBindings' import { checkShapesAndAddCore } from '../config/defaultShapes' import { - ANIMATION_MEDIUM_MS, - CAMERA_MOVING_TIMEOUT, - CAMERA_SLIDE_FRICTION, - COARSE_DRAG_DISTANCE, - COLLABORATOR_IDLE_TIMEOUT, DEFAULT_ANIMATION_OPTIONS, DEFAULT_CAMERA_OPTIONS, - DRAG_DISTANCE, - FOLLOW_CHASE_VIEWPORT_SNAP, - HIT_TEST_MARGIN, INTERNAL_POINTER_IDS, LEFT_MOUSE_BUTTON, - LONG_PRESS_DURATION, - MAX_PAGES, - MAX_SHAPES_PER_PAGE, MIDDLE_MOUSE_BUTTON, RIGHT_MOUSE_BUTTON, STYLUS_ERASER_BUTTON, ZOOM_TO_FIT_PADDING, } from '../constants' +import { TldrawOptions, defaultTldrawOptions } from '../options' import { Box, BoxLike } from '../primitives/Box' import { Mat, MatLike } from '../primitives/Mat' import { Vec, VecLike } from '../primitives/Vec' @@ -217,6 +207,8 @@ export interface TLEditorOptions { * Options for the editor's camera. */ cameraOptions?: Partial + + options?: Partial } /** @public */ @@ -232,9 +224,11 @@ export class Editor extends EventEmitter { initialState, autoFocus, inferDarkMode, + options, }: TLEditorOptions) { super() + this.options = { ...defaultTldrawOptions, ...options } this.store = store this.history = new HistoryManager({ store, @@ -700,6 +694,8 @@ export class Editor extends EventEmitter { this.performanceTracker = new PerformanceTracker() } + readonly options: TldrawOptions + /** * The editor's store * @@ -2876,7 +2872,7 @@ export class Editor extends EventEmitter { opts = {} as { speed: number direction: VecLike - friction: number + friction?: number speedThreshold?: number } ): this { @@ -2887,7 +2883,12 @@ export class Editor extends EventEmitter { this.stopCameraAnimation() - const { speed, friction, direction, speedThreshold = 0.01 } = opts + const { + speed, + friction = this.options.cameraSlideFriction, + direction, + speedThreshold = 0.01, + } = opts let currentSpeed = Math.min(speed, 1) const cancel = () => { @@ -2963,7 +2964,7 @@ export class Editor extends EventEmitter { if (index < 0) return highlightedUserIds.splice(index, 1) this.updateInstanceState({ highlightedUserIds }) - }, COLLABORATOR_IDLE_TIMEOUT) + }, this.options.collaboratorIdleTimeoutMs) }) return this @@ -3279,7 +3280,10 @@ export class Editor extends EventEmitter { Math.abs(targetViewport.maxY - currentViewport.maxY) // Stop chasing if we're close enough! - if (diffX < FOLLOW_CHASE_VIEWPORT_SNAP && diffY < FOLLOW_CHASE_VIEWPORT_SNAP) { + if ( + diffX < this.options.followChaseViewportSnap && + diffY < this.options.followChaseViewportSnap + ) { this._isLockedOnFollowingUser.set(true) return } @@ -3364,8 +3368,8 @@ export class Editor extends EventEmitter { opacity: number }[] = [] - let nextIndex = MAX_SHAPES_PER_PAGE * 2 - let nextBackgroundIndex = MAX_SHAPES_PER_PAGE + let nextIndex = this.options.maxShapesPerPage * 2 + let nextBackgroundIndex = this.options.maxShapesPerPage const erasingShapeIds = this.getErasingShapeIds() @@ -3403,7 +3407,7 @@ export class Editor extends EventEmitter { if (util.providesBackgroundForChildren(shape)) { backgroundIndexToRestore = nextBackgroundIndex nextBackgroundIndex = nextIndex - nextIndex += MAX_SHAPES_PER_PAGE + nextIndex += this.options.maxShapesPerPage } for (const childId of childIds) { @@ -3444,7 +3448,7 @@ export class Editor extends EventEmitter { } private _tickCameraState = () => { // always reset the timeout - this._cameraStateTimeoutRemaining = CAMERA_MOVING_TIMEOUT + this._cameraStateTimeoutRemaining = this.options.cameraMovingTimoutMs // If the state is idle, then start the tick if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return this._cameraState.set('moving') @@ -3667,7 +3671,7 @@ export class Editor extends EventEmitter { createPage(page: Partial): this { this.history.batch(() => { if (this.getInstanceState().isReadonly) return - if (this.getPages().length >= MAX_PAGES) return + if (this.getPages().length >= this.options.maxPages) return const pages = this.getPages() const name = getIncrementedName( @@ -3734,7 +3738,7 @@ export class Editor extends EventEmitter { * @public */ duplicatePage(page: TLPageId | TLPage, createId: TLPageId = PageRecordType.createId()): this { - if (this.getPages().length >= MAX_PAGES) return this + if (this.getPages().length >= this.options.maxPages) return this const id = typeof page === 'string' ? page : page.id const freshPage = this.getPage(id) // get the most recent version of the page anyway if (!freshPage) return this @@ -4543,7 +4547,7 @@ export class Editor extends EventEmitter { } else { // For open shapes (e.g. lines or draw shapes) always use the margin. // If the distance is less than the margin, return the shape as the hit. - if (distance < HIT_TEST_MARGIN / zoomLevel) { + if (distance < this.options.hitTestMargin / zoomLevel) { return shape } } @@ -5445,7 +5449,7 @@ export class Editor extends EventEmitter { ) const maxShapesReached = - shapesToCreate.length + this.getCurrentPageShapeIds().size > MAX_SHAPES_PER_PAGE + shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage if (maxShapesReached) { alertMaxShapes(this) @@ -5464,7 +5468,7 @@ export class Editor extends EventEmitter { const viewportPageBounds = this.getViewportPageBounds() if (selectionPageBounds && !viewportPageBounds.contains(selectionPageBounds)) { this.centerOnPoint(selectionPageBounds.center, { - animation: { duration: ANIMATION_MEDIUM_MS }, + animation: { duration: this.options.animationMediumMs }, }) } } @@ -5508,7 +5512,7 @@ export class Editor extends EventEmitter { // If there is no space on pageId, or if the selected shapes // would take the new page above the limit, don't move the shapes - if (this.getPageShapeIds(pageId).size + content.shapes.length > MAX_SHAPES_PER_PAGE) { + if (this.getPageShapeIds(pageId).size + content.shapes.length > this.options.maxShapesPerPage) { alertMaxShapes(this, pageId) return this } @@ -6611,7 +6615,8 @@ export class Editor extends EventEmitter { const currentPageShapeIds = this.getCurrentPageShapeIds() - const maxShapesReached = shapes.length + currentPageShapeIds.size > MAX_SHAPES_PER_PAGE + const maxShapesReached = + shapes.length + currentPageShapeIds.size > this.options.maxShapesPerPage if (maxShapesReached) { // can't create more shapes than fit on the page @@ -7855,7 +7860,7 @@ export class Editor extends EventEmitter { return newShape }) - if (newShapes.length + this.getCurrentPageShapeIds().size > MAX_SHAPES_PER_PAGE) { + if (newShapes.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage) { // There's some complexity here involving children // that might be created without their parents, so // if we're going over the limit then just don't paste. @@ -8613,7 +8618,7 @@ export class Editor extends EventEmitter { point: this.inputs.currentScreenPoint, name: 'long_press', }) - }, LONG_PRESS_DURATION) + }, this.options.longPressDurationMs) } // Save the selected ids at pointer down @@ -8681,7 +8686,10 @@ export class Editor extends EventEmitter { inputs.isPointing && !inputs.isDragging && Vec.Dist2(originPagePoint, currentPagePoint) > - (instanceState.isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) / cz + (instanceState.isCoarsePointer + ? this.options.coarseDragDistanceSquared + : this.options.dragDistanceSquared) / + cz ) { // Start dragging inputs.isDragging = true @@ -8734,11 +8742,7 @@ export class Editor extends EventEmitter { } if (slideSpeed > 0) { - this.slideCamera({ - speed: slideSpeed, - direction: slideDirection, - friction: CAMERA_SLIDE_FRICTION, - }) + this.slideCamera({ speed: slideSpeed, direction: slideDirection }) } } else { if (info.button === STYLUS_ERASER_BUTTON) { @@ -8851,7 +8855,7 @@ export class Editor extends EventEmitter { function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) { const name = editor.getPage(pageId)!.name - editor.emit('max-shapes', { name, pageId, count: MAX_SHAPES_PER_PAGE }) + editor.emit('max-shapes', { name, pageId, count: editor.options.maxShapesPerPage }) } function applyPartialToRecordWithProps< diff --git a/packages/editor/src/lib/editor/getSvgJsx.tsx b/packages/editor/src/lib/editor/getSvgJsx.tsx index f86bef475..b0f2bb2c9 100644 --- a/packages/editor/src/lib/editor/getSvgJsx.tsx +++ b/packages/editor/src/lib/editor/getSvgJsx.tsx @@ -6,7 +6,6 @@ import { getDefaultColorTheme, } from '@tldraw/tlschema' import { Fragment, ReactElement } from 'react' -import { SVG_PADDING } from '../constants' import { Editor } from './Editor' import { SvgExportContext, SvgExportContextProvider, SvgExportDef } from './types/SvgExportContext' import { TLSvgOptions } from './types/misc-types' @@ -22,7 +21,12 @@ export async function getSvgJsx( if (ids.length === 0) return if (!window.document) throw Error('No document') - const { scale = 1, background = false, padding = SVG_PADDING, preserveAspectRatio = false } = opts + const { + scale = 1, + background = false, + padding = editor.options.defaultSvgPadding, + preserveAspectRatio = false, + } = opts const isDarkMode = opts.darkMode ?? editor.user.getIsDarkMode() const theme = getDefaultColorTheme({ isDarkMode }) diff --git a/packages/editor/src/lib/editor/managers/ClickManager.ts b/packages/editor/src/lib/editor/managers/ClickManager.ts index 4c7a4253b..a7d72779d 100644 --- a/packages/editor/src/lib/editor/managers/ClickManager.ts +++ b/packages/editor/src/lib/editor/managers/ClickManager.ts @@ -1,9 +1,3 @@ -import { - COARSE_DRAG_DISTANCE, - DOUBLE_CLICK_DURATION, - DRAG_DISTANCE, - MULTI_CLICK_DURATION, -} from '../../constants' import { Vec } from '../../primitives/Vec' import { uniqueId } from '../../utils/uniqueId' import type { Editor } from '../Editor' @@ -72,7 +66,9 @@ export class ClickManager { this._clickState = 'idle' } }, - state === 'idle' || state === 'pendingDouble' ? DOUBLE_CLICK_DURATION : MULTI_CLICK_DURATION + state === 'idle' || state === 'pendingDouble' + ? this.editor.options.doubleClickDurationMs + : this.editor.options.multiClickDurationMs ) } @@ -199,7 +195,9 @@ export class ClickManager { this._clickState !== 'idle' && this._clickScreenPoint && Vec.Dist2(this._clickScreenPoint, this.editor.inputs.currentScreenPoint) > - (this.editor.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) + (this.editor.getInstanceState().isCoarsePointer + ? this.editor.options.coarseDragDistanceSquared + : this.editor.options.dragDistanceSquared) ) { this.cancelDoubleClickTimeout() } diff --git a/packages/editor/src/lib/options.ts b/packages/editor/src/lib/options.ts new file mode 100644 index 000000000..56c29a314 --- /dev/null +++ b/packages/editor/src/lib/options.ts @@ -0,0 +1,80 @@ +/** + * Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}. + * + * @example + * ```tsx + * const options: Partial = { + * maxPages: 3, + * maxShapesPerPage: 1000, + * } + * + * function MyTldrawComponent() { + * return + * } + * ``` + * + * @public + */ +export interface TldrawOptions { + readonly maxShapesPerPage: number + readonly maxPages: number + readonly animationMediumMs: number + readonly followChaseViewportSnap: number + readonly doubleClickDurationMs: number + readonly multiClickDurationMs: number + readonly coarseDragDistanceSquared: number + readonly dragDistanceSquared: number + readonly defaultSvgPadding: number + readonly cameraSlideFriction: number + readonly gridSteps: readonly { + readonly min: number + readonly mid: number + readonly step: number + }[] + readonly collaboratorInactiveTimeoutMs: number + readonly collaboratorIdleTimeoutMs: number + readonly collaboratorCheckIntervalMs: number + readonly cameraMovingTimoutMs: number + readonly hitTestMargin: number + readonly edgeScrollSpeed: number + readonly edgeScrollDistance: number + readonly coarsePointerWidth: number + readonly coarseHandleRadius: number + readonly handleRadius: number + readonly longPressDurationMs: number + readonly textShadowLod: number + readonly adjacentShapeMargin: number +} + +/** @public */ +export const defaultTldrawOptions = { + maxShapesPerPage: 4000, + maxPages: 40, + animationMediumMs: 320, + followChaseViewportSnap: 2, + doubleClickDurationMs: 450, + multiClickDurationMs: 200, + coarseDragDistanceSquared: 36, // 6 squared + dragDistanceSquared: 16, // 4 squared + defaultSvgPadding: 32, + cameraSlideFriction: 0.09, + gridSteps: [ + { min: -1, mid: 0.15, step: 64 }, + { min: 0.05, mid: 0.375, step: 16 }, + { min: 0.15, mid: 1, step: 4 }, + { min: 0.7, mid: 2.5, step: 1 }, + ], + collaboratorInactiveTimeoutMs: 60000, + collaboratorIdleTimeoutMs: 3000, + collaboratorCheckIntervalMs: 1200, + cameraMovingTimoutMs: 64, + hitTestMargin: 8, + edgeScrollSpeed: 20, + edgeScrollDistance: 8, + coarsePointerWidth: 12, + coarseHandleRadius: 20, + handleRadius: 12, + longPressDurationMs: 500, + textShadowLod: 0.35, + adjacentShapeMargin: 10, +} as const satisfies TldrawOptions diff --git a/packages/editor/src/lib/utils/edgeScrolling.ts b/packages/editor/src/lib/utils/edgeScrolling.ts index 4c23eb0c1..1a0419b11 100644 --- a/packages/editor/src/lib/utils/edgeScrolling.ts +++ b/packages/editor/src/lib/utils/edgeScrolling.ts @@ -1,4 +1,3 @@ -import { COARSE_POINTER_WIDTH, EDGE_SCROLL_DISTANCE, EDGE_SCROLL_SPEED } from '../constants' import { Editor } from '../editor/Editor' import { Vec } from '../primitives/Vec' @@ -9,14 +8,15 @@ import { Vec } from '../primitives/Vec' * @internal */ function getEdgeProximityFactor( + editor: Editor, position: number, dimension: number, isCoarse: boolean, insetStart: boolean, insetEnd: boolean ) { - const dist = EDGE_SCROLL_DISTANCE - const pw = isCoarse ? COARSE_POINTER_WIDTH : 0 // pointer width + const dist = editor.options.edgeScrollDistance + const pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width const pMin = position - pw const pMax = position + pw const min = insetStart ? 0 : dist @@ -53,13 +53,13 @@ export function moveCameraWhenCloseToEdge(editor: Editor) { isCoarsePointer, insets: [t, r, b, l], } = editor.getInstanceState() - const proximityFactorX = getEdgeProximityFactor(x, screenBounds.w, isCoarsePointer, l, r) - const proximityFactorY = getEdgeProximityFactor(y, screenBounds.h, isCoarsePointer, t, b) + const proximityFactorX = getEdgeProximityFactor(editor, x, screenBounds.w, isCoarsePointer, l, r) + const proximityFactorY = getEdgeProximityFactor(editor, y, screenBounds.h, isCoarsePointer, t, b) if (proximityFactorX === 0 && proximityFactorY === 0) return // Determines the base speed of the scroll - const pxSpeed = editor.user.getEdgeScrollSpeed() * EDGE_SCROLL_SPEED + const pxSpeed = editor.user.getEdgeScrollSpeed() * editor.options.edgeScrollSpeed const scrollDeltaX = (pxSpeed * proximityFactorX * screenSizeFactorX) / zoomLevel const scrollDeltaY = (pxSpeed * proximityFactorY * screenSizeFactorY) / zoomLevel diff --git a/packages/tldraw/src/lib/shapes/draw/toolStates/Drawing.ts b/packages/tldraw/src/lib/shapes/draw/toolStates/Drawing.ts index 0361e076e..2fc0c33aa 100644 --- a/packages/tldraw/src/lib/shapes/draw/toolStates/Drawing.ts +++ b/packages/tldraw/src/lib/shapes/draw/toolStates/Drawing.ts @@ -1,5 +1,4 @@ import { - DRAG_DISTANCE, Mat, StateNode, TLDefaultSizeStyle, @@ -317,7 +316,8 @@ export class Drawing extends StateNode { } const hasMovedFarEnough = - Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE + Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > + this.editor.options.dragDistanceSquared // Find the distance from where the pointer was when shift was released and // where it is now; if it's far enough away, then update the page point where @@ -388,7 +388,8 @@ export class Drawing extends StateNode { } const hasMovedFarEnough = - Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE + Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > + this.editor.options.dragDistanceSquared // Find the distance from where the pointer was when shift was released and // where it is now; if it's far enough away, then update the page point where diff --git a/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx b/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx index 670d4965f..ed6ee03ef 100644 --- a/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx @@ -36,7 +36,6 @@ import { import { getFontDefForExport } from '../shared/defaultStyleDefs' import { startEditingShapeWithLabel } from '../../tools/SelectTool/selectHelpers' -import { ADJACENT_SHAPE_MARGIN } from '../../ui/constants' import { useForceSolid } from '../shared/useForceSolid' import { CLONE_HANDLE_MARGIN, @@ -402,7 +401,7 @@ function useNoteKeydownHandler(id: TLShapeId) { const offsetLength = NOTE_SIZE + - ADJACENT_SHAPE_MARGIN + + editor.options.adjacentShapeMargin + // If we're growing down, we need to account for the current shape's growY (isCmdEnter && !e.shiftKey ? shape.props.growY : 0) diff --git a/packages/tldraw/src/lib/tools/EraserTool/childStates/Erasing.ts b/packages/tldraw/src/lib/tools/EraserTool/childStates/Erasing.ts index c3367bef3..67677a502 100644 --- a/packages/tldraw/src/lib/tools/EraserTool/childStates/Erasing.ts +++ b/packages/tldraw/src/lib/tools/EraserTool/childStates/Erasing.ts @@ -1,5 +1,4 @@ import { - HIT_TEST_MARGIN, StateNode, TLEventHandlers, TLFrameShape, @@ -90,7 +89,7 @@ export class Erasing extends StateNode { this.pushPointToScribble() const erasing = new Set(erasingShapeIds) - const minDist = HIT_TEST_MARGIN / zoomLevel + const minDist = this.editor.options.hitTestMargin / zoomLevel for (const shape of currentPageShapes) { if (editor.isShapeOfType(shape, 'group')) continue diff --git a/packages/tldraw/src/lib/tools/EraserTool/childStates/Pointing.ts b/packages/tldraw/src/lib/tools/EraserTool/childStates/Pointing.ts index bf46a8c01..554ccd668 100644 --- a/packages/tldraw/src/lib/tools/EraserTool/childStates/Pointing.ts +++ b/packages/tldraw/src/lib/tools/EraserTool/childStates/Pointing.ts @@ -1,5 +1,4 @@ import { - HIT_TEST_MARGIN, StateNode, TLEventHandlers, TLFrameShape, @@ -34,7 +33,7 @@ export class Pointing extends StateNode { if ( this.editor.isPointInShape(shape, currentPagePoint, { hitInside: false, - margin: HIT_TEST_MARGIN / zoomLevel, + margin: this.editor.options.hitTestMargin / zoomLevel, }) ) { const hitShape = this.editor.getOutermostSelectableShape(shape) diff --git a/packages/tldraw/src/lib/tools/HandTool/childStates/Dragging.ts b/packages/tldraw/src/lib/tools/HandTool/childStates/Dragging.ts index a0138ef0e..316b255db 100644 --- a/packages/tldraw/src/lib/tools/HandTool/childStates/Dragging.ts +++ b/packages/tldraw/src/lib/tools/HandTool/childStates/Dragging.ts @@ -1,4 +1,4 @@ -import { CAMERA_SLIDE_FRICTION, StateNode, TLEventHandlers, Vec } from '@tldraw/editor' +import { StateNode, TLEventHandlers, Vec } from '@tldraw/editor' export class Dragging extends StateNode { static override id = 'dragging' @@ -42,11 +42,7 @@ export class Dragging extends StateNode { const velocityAtPointerUp = Math.min(pointerVelocity.len(), 2) if (velocityAtPointerUp > 0.1) { - this.editor.slideCamera({ - speed: velocityAtPointerUp, - direction: pointerVelocity, - friction: CAMERA_SLIDE_FRICTION, - }) + this.editor.slideCamera({ speed: velocityAtPointerUp, direction: pointerVelocity }) } this.parent.transition('idle') diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/Idle.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/Idle.ts index 489557156..fc7d42235 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/Idle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/Idle.ts @@ -1,7 +1,6 @@ import { Editor, Group2d, - HIT_TEST_MARGIN, StateNode, TLArrowShape, TLClickEventInfo, @@ -199,7 +198,7 @@ export class Idle extends StateNode { ? hoveredShape : this.editor.getSelectedShapeAtPoint(this.editor.inputs.currentPagePoint) ?? this.editor.getShapeAtPoint(this.editor.inputs.currentPagePoint, { - margin: HIT_TEST_MARGIN / this.editor.getZoomLevel(), + margin: this.editor.options.hitTestMargin / this.editor.getZoomLevel(), hitInside: false, }) @@ -353,7 +352,7 @@ export class Idle extends StateNode { hoveredShape && !this.editor.isShapeOfType(hoveredShape, 'group') ? hoveredShape : this.editor.getShapeAtPoint(this.editor.inputs.currentPagePoint, { - margin: HIT_TEST_MARGIN / this.editor.getZoomLevel(), + margin: this.editor.options.hitTestMargin / this.editor.getZoomLevel(), hitInside: false, hitLabels: true, hitLocked: true, diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts index 67efe39ab..dabd4de9d 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts @@ -1,10 +1,4 @@ -import { - HIT_TEST_MARGIN, - StateNode, - TLEventHandlers, - TLPointerEventInfo, - TLShape, -} from '@tldraw/editor' +import { StateNode, TLEventHandlers, TLPointerEventInfo, TLShape } from '@tldraw/editor' import { getTextLabels } from '../../../utils/shapes/shapes' export class PointingShape extends StateNode { @@ -73,7 +67,7 @@ export class PointingShape extends StateNode { const hitShape = this.editor.getShapeAtPoint(currentPagePoint, { - margin: HIT_TEST_MARGIN / zoomLevel, + margin: this.editor.options.hitTestMargin / zoomLevel, hitInside: true, renderingOnly: true, }) ?? this.hitShape diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts index b55fbb7aa..17bf72e3b 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts @@ -98,7 +98,7 @@ export class ScribbleBrushing extends StateNode { const shapes = currentPageShapes let shape: TLShape, geometry: Geometry2d, A: Vec, B: Vec - const minDist = 0 // HIT_TEST_MARGIN / zoomLevel + const minDist = 0 // this.editor.options.hitTestMargin / zoomLevel for (let i = 0, n = shapes.length; i < n; i++) { shape = shapes[i] diff --git a/packages/tldraw/src/lib/tools/SelectTool/selectHelpers.ts b/packages/tldraw/src/lib/tools/SelectTool/selectHelpers.ts index 14aed1ac6..6199c11d1 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/selectHelpers.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/selectHelpers.ts @@ -1,5 +1,4 @@ import { - ANIMATION_MEDIUM_MS, Editor, Geometry2d, Mat, @@ -149,7 +148,7 @@ export function zoomToShapeIfOffscreen(editor: Editor) { }) editor.zoomToBounds(nextBounds, { animation: { - duration: ANIMATION_MEDIUM_MS, + duration: editor.options.animationMediumMs, }, inset: 0, }) diff --git a/packages/tldraw/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts b/packages/tldraw/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts index 6e47d8322..582c7e92b 100644 --- a/packages/tldraw/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts +++ b/packages/tldraw/src/lib/tools/selection-logic/getHitShapeOnCanvasPointerDown.ts @@ -1,4 +1,4 @@ -import { Editor, HIT_TEST_MARGIN, TLShape } from '@tldraw/editor' +import { Editor, TLShape } from '@tldraw/editor' export function getHitShapeOnCanvasPointerDown( editor: Editor, @@ -14,7 +14,7 @@ export function getHitShapeOnCanvasPointerDown( editor.getShapeAtPoint(currentPagePoint, { hitInside: false, hitLabels, - margin: HIT_TEST_MARGIN / zoomLevel, + margin: editor.options.hitTestMargin / zoomLevel, renderingOnly: true, }) ?? // selected shape at point diff --git a/packages/tldraw/src/lib/tools/selection-logic/selectOnCanvasPointerUp.ts b/packages/tldraw/src/lib/tools/selection-logic/selectOnCanvasPointerUp.ts index a77e92066..471c509e2 100644 --- a/packages/tldraw/src/lib/tools/selection-logic/selectOnCanvasPointerUp.ts +++ b/packages/tldraw/src/lib/tools/selection-logic/selectOnCanvasPointerUp.ts @@ -1,4 +1,4 @@ -import { Editor, HIT_TEST_MARGIN, TLShape, isShapeId } from '@tldraw/editor' +import { Editor, TLShape, isShapeId } from '@tldraw/editor' export function selectOnCanvasPointerUp(editor: Editor) { const selectedShapeIds = editor.getSelectedShapeIds() @@ -6,7 +6,7 @@ export function selectOnCanvasPointerUp(editor: Editor) { const hitShape = editor.getShapeAtPoint(currentPagePoint, { hitInside: false, - margin: HIT_TEST_MARGIN / editor.getZoomLevel(), + margin: editor.options.hitTestMargin / editor.getZoomLevel(), hitLabels: true, renderingOnly: true, filter: (shape) => !shape.isLocked, diff --git a/packages/tldraw/src/lib/tools/selection-logic/updateHoveredShapeId.ts b/packages/tldraw/src/lib/tools/selection-logic/updateHoveredShapeId.ts index 665ae6ff2..e044782c7 100644 --- a/packages/tldraw/src/lib/tools/selection-logic/updateHoveredShapeId.ts +++ b/packages/tldraw/src/lib/tools/selection-logic/updateHoveredShapeId.ts @@ -1,11 +1,11 @@ -import { Editor, HIT_TEST_MARGIN, TLShape, throttle } from '@tldraw/editor' +import { Editor, TLShape, throttle } from '@tldraw/editor' function _updateHoveredShapeId(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(), + margin: editor.options.hitTestMargin / editor.getZoomLevel(), renderingOnly: true, }) diff --git a/packages/tldraw/src/lib/ui/components/Minimap/DefaultMinimap.tsx b/packages/tldraw/src/lib/ui/components/Minimap/DefaultMinimap.tsx index e033d551f..db0e44326 100644 --- a/packages/tldraw/src/lib/ui/components/Minimap/DefaultMinimap.tsx +++ b/packages/tldraw/src/lib/ui/components/Minimap/DefaultMinimap.tsx @@ -1,5 +1,4 @@ import { - ANIMATION_MEDIUM_MS, Box, TLPointerEventInfo, Vec, @@ -62,7 +61,7 @@ export function DefaultMinimap() { minimapRef.current.originPagePoint.setTo(clampedPoint) minimapRef.current.originPageCenter.setTo(editor.getViewportPageBounds().center) - editor.centerOnPoint(point, { animation: { duration: ANIMATION_MEDIUM_MS } }) + editor.centerOnPoint(point, { animation: { duration: editor.options.animationMediumMs } }) }, [editor] ) @@ -101,7 +100,7 @@ export function DefaultMinimap() { const pagePoint = Vec.Add(point, delta) minimapRef.current.originPagePoint.setTo(pagePoint) minimapRef.current.originPageCenter.setTo(point) - editor.centerOnPoint(point, { animation: { duration: ANIMATION_MEDIUM_MS } }) + editor.centerOnPoint(point, { animation: { duration: editor.options.animationMediumMs } }) } else { const clampedPoint = minimapRef.current.minimapScreenPointToPagePoint( e.clientX, diff --git a/packages/tldraw/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx b/packages/tldraw/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx index 9a6569566..ca4abaf41 100644 --- a/packages/tldraw/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx +++ b/packages/tldraw/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx @@ -1,5 +1,4 @@ import { - MAX_PAGES, PageRecordType, TLPageId, releasePointerCapture, @@ -50,7 +49,7 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() { // If the user has reached the max page count, we disable the "add page" button const maxPageCountReached = useValue( 'maxPageCountReached', - () => editor.getPages().length >= MAX_PAGES, + () => editor.getPages().length >= editor.options.maxPages, [editor] ) diff --git a/packages/tldraw/src/lib/ui/components/PageMenu/PageItemSubmenu.tsx b/packages/tldraw/src/lib/ui/components/PageMenu/PageItemSubmenu.tsx index ef0926af8..07d0a6b3d 100644 --- a/packages/tldraw/src/lib/ui/components/PageMenu/PageItemSubmenu.tsx +++ b/packages/tldraw/src/lib/ui/components/PageMenu/PageItemSubmenu.tsx @@ -1,4 +1,4 @@ -import { MAX_PAGES, PageRecordType, TLPageId, track, useEditor } from '@tldraw/editor' +import { PageRecordType, TLPageId, track, useEditor } from '@tldraw/editor' import { useCallback } from 'react' import { useTranslation } from '../../hooks/useTranslation/useTranslation' import { TldrawUiButton } from '../primitives/Button/TldrawUiButton' @@ -66,7 +66,7 @@ export const PageItemSubmenu = track(function PageItemSubmenu({ id="duplicate" label="page-menu.submenu.duplicate-page" onSelect={onDuplicate} - disabled={pages.length >= MAX_PAGES} + disabled={pages.length >= editor.options.maxPages} /> {index > 0 && ( ( const handleDoubleClick = useCallback(() => { editor.resetZoom(editor.getViewportScreenCenter(), { - animation: { duration: ANIMATION_MEDIUM_MS }, + animation: { duration: editor.options.animationMediumMs }, }) }, [editor]) diff --git a/packages/tldraw/src/lib/ui/constants.ts b/packages/tldraw/src/lib/ui/constants.ts index 9602b4507..f39bbafbf 100644 --- a/packages/tldraw/src/lib/ui/constants.ts +++ b/packages/tldraw/src/lib/ui/constants.ts @@ -13,5 +13,3 @@ export enum PORTRAIT_BREAKPOINT { TABLET = 6, DESKTOP = 7, } - -export const ADJACENT_SHAPE_MARGIN = 20 diff --git a/packages/tldraw/src/lib/ui/context/actions.tsx b/packages/tldraw/src/lib/ui/context/actions.tsx index ef1be46e6..9c03d8a01 100644 --- a/packages/tldraw/src/lib/ui/context/actions.tsx +++ b/packages/tldraw/src/lib/ui/context/actions.tsx @@ -1,5 +1,4 @@ import { - ANIMATION_MEDIUM_MS, Box, DefaultColorStyle, Editor, @@ -25,7 +24,6 @@ import { getEmbedInfo } from '../../utils/embeds/embeds' import { fitFrameToContent, removeFrame } from '../../utils/frames/frames' import { EditLinkDialog } from '../components/EditLinkDialog' import { EmbedDialog } from '../components/EmbedDialog' -import { ADJACENT_SHAPE_MARGIN } from '../constants' import { useMenuClipboardEvents } from '../hooks/useClipboardEvents' import { useCopyAs } from '../hooks/useCopyAs' import { useExportAs } from '../hooks/useExportAs' @@ -819,7 +817,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { trackEvent('pack-shapes', { source }) editor.mark('pack') const selectedShapeIds = editor.getSelectedShapeIds() - editor.packShapes(selectedShapeIds, ADJACENT_SHAPE_MARGIN) + editor.packShapes(selectedShapeIds, editor.options.adjacentShapeMargin) kickoutOccludedShapes(editor, selectedShapeIds) }, }, @@ -1038,7 +1036,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { onSelect(source) { trackEvent('zoom-in', { source }) editor.zoomIn(undefined, { - animation: { duration: ANIMATION_MEDIUM_MS }, + animation: { duration: editor.options.animationMediumMs }, }) }, }, @@ -1050,7 +1048,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { onSelect(source) { trackEvent('zoom-out', { source }) editor.zoomOut(undefined, { - animation: { duration: ANIMATION_MEDIUM_MS }, + animation: { duration: editor.options.animationMediumMs }, }) }, }, @@ -1063,7 +1061,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { onSelect(source) { trackEvent('reset-zoom', { source }) editor.resetZoom(undefined, { - animation: { duration: ANIMATION_MEDIUM_MS }, + animation: { duration: editor.options.animationMediumMs }, }) }, }, @@ -1074,7 +1072,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { readonlyOk: true, onSelect(source) { trackEvent('zoom-to-fit', { source }) - editor.zoomToFit({ animation: { duration: ANIMATION_MEDIUM_MS } }) + editor.zoomToFit({ animation: { duration: editor.options.animationMediumMs } }) }, }, { @@ -1087,7 +1085,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { if (mustGoBackToSelectToolFirst()) return trackEvent('zoom-to-selection', { source }) - editor.zoomToSelection({ animation: { duration: ANIMATION_MEDIUM_MS } }) + editor.zoomToSelection({ animation: { duration: editor.options.animationMediumMs } }) }, }, { diff --git a/packages/tldraw/src/lib/utils/tldr/buildFromV1Document.ts b/packages/tldraw/src/lib/utils/tldr/buildFromV1Document.ts index 2ff0f7f9b..9d5cacd08 100644 --- a/packages/tldraw/src/lib/utils/tldr/buildFromV1Document.ts +++ b/packages/tldraw/src/lib/utils/tldr/buildFromV1Document.ts @@ -1,7 +1,6 @@ import { AssetRecordType, Editor, - MAX_SHAPES_PER_PAGE, PageRecordType, TLArrowShape, TLArrowShapeArrowheadStyle, @@ -130,7 +129,7 @@ export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocume const v1Shapes = Object.values(v1Page.shapes ?? {}) .sort((a, b) => (a.childIndex < b.childIndex ? -1 : 1)) - .slice(0, MAX_SHAPES_PER_PAGE) + .slice(0, editor.options.maxShapesPerPage) // Groups only v1Shapes.forEach((v1Shape) => { diff --git a/packages/tldraw/src/test/commands/createPage.test.ts b/packages/tldraw/src/test/commands/createPage.test.ts index 73e461618..d3fe32f3a 100644 --- a/packages/tldraw/src/test/commands/createPage.test.ts +++ b/packages/tldraw/src/test/commands/createPage.test.ts @@ -1,4 +1,4 @@ -import { MAX_PAGES, PageRecordType } from '@tldraw/editor' +import { PageRecordType } from '@tldraw/editor' import { TestEditor } from '../TestEditor' let editor: TestEditor @@ -31,10 +31,10 @@ it('Creates a page', () => { }) it("Doesn't create a page if max pages is reached", () => { - for (let i = 0; i < MAX_PAGES + 1; i++) { + for (let i = 0; i < editor.options.maxPages + 1; i++) { editor.createPage({ name: `Test Page ${i}` }) } - expect(editor.getPages().length).toBe(MAX_PAGES) + expect(editor.getPages().length).toBe(editor.options.maxPages) }) it('[regression] does not die if every page has the same index', () => { diff --git a/packages/tldraw/src/test/commands/duplicatePage.test.ts b/packages/tldraw/src/test/commands/duplicatePage.test.ts index d247437ec..91fe7afd4 100644 --- a/packages/tldraw/src/test/commands/duplicatePage.test.ts +++ b/packages/tldraw/src/test/commands/duplicatePage.test.ts @@ -1,4 +1,4 @@ -import { MAX_PAGES, createShapeId } from '@tldraw/editor' +import { createShapeId } from '@tldraw/editor' import { TestEditor } from '../TestEditor' let editor: TestEditor @@ -48,8 +48,8 @@ it('Duplicates a page', () => { }) it("Doesn't duplicate the page if max pages is reached", () => { - for (let i = 0; i < MAX_PAGES; i++) { + for (let i = 0; i < editor.options.maxPages; i++) { editor.duplicatePage(editor.getCurrentPageId()) } - expect(editor.getPages().length).toBe(MAX_PAGES) + expect(editor.getPages().length).toBe(editor.options.maxPages) }) diff --git a/packages/tldraw/src/test/commands/getSvgString.test.ts b/packages/tldraw/src/test/commands/getSvgString.test.ts index 6ce16f0c9..d0d430f98 100644 --- a/packages/tldraw/src/test/commands/getSvgString.test.ts +++ b/packages/tldraw/src/test/commands/getSvgString.test.ts @@ -1,4 +1,4 @@ -import { DefaultDashStyle, SVG_PADDING, createShapeId } from '@tldraw/editor' +import { DefaultDashStyle, createShapeId } from '@tldraw/editor' import { TestEditor } from '../TestEditor' let editor: TestEditor @@ -73,7 +73,7 @@ it('Gets the bounding box at the correct size', async () => { const svg = await editor.getSvgString(editor.getSelectedShapeIds()) const parsed = parseSvg(svg!) const bbox = editor.getSelectionRotatedPageBounds()! - const expanded = bbox.expandBy(SVG_PADDING) // adds 32px padding + const expanded = bbox.expandBy(editor.options.defaultSvgPadding) // adds 32px padding expect(parsed.getAttribute('width')).toMatch(expanded.width + '') expect(parsed.getAttribute('height')).toMatch(expanded.height + '')