Move constants to options prop (#3799)

Another go at #3628 & #3783. This moves (most) constants into
`editor.options`, configurable by the `options` prop on the tldraw
component.

### Change Type

- [x] `sdk` — Changes the tldraw SDK
- [x] `feature` — New feature

### Release Notes

You can now override many options which were previously hard-coded
constants. Pass an `options` prop into the tldraw component to change
the maximum number of pages, grid steps, or other previously hard-coded
values. See `TldrawOptions` for more
This commit is contained in:
alex 2024-05-28 15:22:03 +01:00 committed by GitHub
parent 19f8d4248c
commit a457a39081
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 347 additions and 267 deletions

View file

@ -0,0 +1,15 @@
import { Tldraw, TldrawOptions } from 'tldraw'
import 'tldraw/tldraw.css'
const options: Partial<TldrawOptions> = {
maxPages: 3,
animationMediumMs: 5000,
}
export default function CustomOptionsExample() {
return (
<div className="tldraw__editor">
<Tldraw persistenceKey="example" options={options} />
</div>
)
}

View file

@ -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.
---

View file

@ -89,12 +89,6 @@ import { whyAmIRunning } from '@tldraw/state';
// @public // @public
export function angleDistance(fromAngle: number, toAngle: number, direction: number): number; 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) // @internal (undocumented)
export function applyRotationToSnapshotShapes({ delta, editor, snapshot, stage, }: { export function applyRotationToSnapshotShapes({ delta, editor, snapshot, stage, }: {
delta: number; delta: number;
@ -391,9 +385,6 @@ export class Box {
// @public (undocumented) // @public (undocumented)
export type BoxLike = Box | BoxModel; export type BoxLike = Box | BoxModel;
// @internal (undocumented)
export const CAMERA_SLIDE_FRICTION = 0.09;
// @public (undocumented) // @public (undocumented)
export function canonicalizeRotation(a: number): number; export function canonicalizeRotation(a: number): number;
@ -607,6 +598,50 @@ export function DefaultSpinner(): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultSvgDefs: () => null; 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) // @public (undocumented)
export const defaultUserPreferences: Readonly<{ export const defaultUserPreferences: Readonly<{
animationSpeed: 0 | 1; animationSpeed: 0 | 1;
@ -622,12 +657,6 @@ export const defaultUserPreferences: Readonly<{
// @public // @public
export function degreesToRadians(d: number): number; export function degreesToRadians(d: number): number;
// @internal (undocumented)
export const DOUBLE_CLICK_DURATION = 450;
// @internal (undocumented)
export const DRAG_DISTANCE = 16;
// @public (undocumented) // @public (undocumented)
export const EASINGS: { export const EASINGS: {
readonly easeInCubic: (t: number) => number; readonly easeInCubic: (t: number) => number;
@ -683,7 +712,7 @@ export class Edge2d extends Geometry2d {
// @public (undocumented) // @public (undocumented)
export class Editor extends EventEmitter<TLEventMap> { export class Editor extends EventEmitter<TLEventMap> {
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; addOpenMenu(id: string): this;
alignShapes(shapes: TLShape[] | TLShapeId[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this; alignShapes(shapes: TLShape[] | TLShapeId[], operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top'): this;
animateShape(partial: null | TLShapePartial | undefined, opts?: Partial<{ animateShape(partial: null | TLShapePartial | undefined, opts?: Partial<{
@ -998,6 +1027,8 @@ export class Editor extends EventEmitter<TLEventMap> {
mark(markId?: string): this; mark(markId?: string): this;
moveShapesToPage(shapes: TLShape[] | TLShapeId[], pageId: TLPageId): this; moveShapesToPage(shapes: TLShape[] | TLShapeId[], pageId: TLPageId): this;
nudgeShapes(shapes: TLShape[] | TLShapeId[], offset: VecLike): this; nudgeShapes(shapes: TLShape[] | TLShapeId[], offset: VecLike): this;
// (undocumented)
readonly options: TldrawOptions;
packShapes(shapes: TLShape[] | TLShapeId[], gap: number): this; packShapes(shapes: TLShape[] | TLShapeId[], gap: number): this;
pageToScreen(point: VecLike): Vec; pageToScreen(point: VecLike): Vec;
pageToViewport(point: VecLike): Vec; pageToViewport(point: VecLike): Vec;
@ -1051,7 +1082,7 @@ export class Editor extends EventEmitter<TLEventMap> {
readonly sideEffects: StoreSideEffects<TLRecord>; readonly sideEffects: StoreSideEffects<TLRecord>;
slideCamera(opts?: { slideCamera(opts?: {
direction: VecLike; direction: VecLike;
friction: number; friction?: number | undefined;
speed: number; speed: number;
speedThreshold?: number | undefined; speedThreshold?: number | undefined;
}): this; }): this;
@ -1281,13 +1312,6 @@ export function getSvgPathFromPoints(points: VecLike[], closed?: boolean): strin
// @public (undocumented) // @public (undocumented)
export function getUserPreferences(): TLUserPreferences; export function getUserPreferences(): TLUserPreferences;
// @public (undocumented)
export const GRID_STEPS: {
mid: number;
min: number;
step: number;
}[];
// @public (undocumented) // @public (undocumented)
export class Group2d extends Geometry2d { export class Group2d extends Geometry2d {
constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {
@ -1409,9 +1433,6 @@ export class HistoryManager<R extends UnknownRecord> {
undo: () => this; undo: () => this;
} }
// @public (undocumented)
export const HIT_TEST_MARGIN = 8;
// @public (undocumented) // @public (undocumented)
export function HTMLContainer({ children, className, ...rest }: HTMLContainerProps): JSX_2.Element; export function HTMLContainer({ children, className, ...rest }: HTMLContainerProps): JSX_2.Element;
@ -1589,18 +1610,9 @@ export interface MatModel {
f: number; f: number;
} }
// @internal (undocumented)
export const MAX_PAGES = 40;
// @internal (undocumented)
export const MAX_SHAPES_PER_PAGE = 4000;
// @public // @public
export function moveCameraWhenCloseToEdge(editor: Editor): void; export function moveCameraWhenCloseToEdge(editor: Editor): void;
// @internal (undocumented)
export const MULTI_CLICK_DURATION = 200;
// @internal (undocumented) // @internal (undocumented)
export function normalizeWheel(event: React.WheelEvent<HTMLElement> | WheelEvent): { export function normalizeWheel(event: React.WheelEvent<HTMLElement> | WheelEvent): {
x: number; x: number;
@ -2075,9 +2087,6 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// @public (undocumented) // @public (undocumented)
export const stopEventPropagation: (e: any) => any; export const stopEventPropagation: (e: any) => any;
// @internal (undocumented)
export const SVG_PADDING = 32;
// @public (undocumented) // @public (undocumented)
export function SVGContainer({ children, className, ...rest }: SVGContainerProps): JSX_2.Element; export function SVGContainer({ children, className, ...rest }: SVGContainerProps): JSX_2.Element;
@ -2281,6 +2290,7 @@ export interface TldrawEditorBaseProps {
inferDarkMode?: boolean; inferDarkMode?: boolean;
initialState?: string; initialState?: string;
onMount?: TLOnMountHandler; onMount?: TLOnMountHandler;
options?: Partial<TldrawOptions>;
shapeUtils?: readonly TLAnyShapeUtilConstructor[]; shapeUtils?: readonly TLAnyShapeUtilConstructor[];
tools?: readonly TLStateNodeConstructor[]; tools?: readonly TLStateNodeConstructor[];
user?: TLUser; user?: TLUser;
@ -2299,6 +2309,62 @@ export type TldrawEditorProps = Expand<TldrawEditorBaseProps & ({
store: TLStore | TLStoreWithStatus; store: TLStore | TLStoreWithStatus;
})>; })>;
// @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) // @public (undocumented)
export type TLEditorComponents = Partial<{ export type TLEditorComponents = Partial<{
[K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null; [K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null;
@ -2312,6 +2378,8 @@ export interface TLEditorOptions {
getContainer: () => HTMLElement; getContainer: () => HTMLElement;
inferDarkMode?: boolean; inferDarkMode?: boolean;
initialState?: string; initialState?: string;
// (undocumented)
options?: Partial<TldrawOptions>;
shapeUtils: readonly TLShapeUtilConstructor<TLUnknownShape>[]; shapeUtils: readonly TLShapeUtilConstructor<TLUnknownShape>[];
store: TLStore; store: TLStore;
tools: readonly TLStateNodeConstructor[]; tools: readonly TLStateNodeConstructor[];

View file

@ -106,22 +106,7 @@ export {
export { createTLUser } from './lib/config/createTLUser' export { createTLUser } from './lib/config/createTLUser'
export { type TLAnyBindingUtilConstructor } from './lib/config/defaultBindings' export { type TLAnyBindingUtilConstructor } from './lib/config/defaultBindings'
export { coreShapes, type TLAnyShapeUtilConstructor } from './lib/config/defaultShapes' export { coreShapes, type TLAnyShapeUtilConstructor } from './lib/config/defaultShapes'
export { export { DEFAULT_ANIMATION_OPTIONS, DEFAULT_CAMERA_OPTIONS, SIDES } from './lib/constants'
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 { Editor, type TLEditorOptions, type TLResizeShapeOptions } from './lib/editor/Editor' export { Editor, type TLEditorOptions, type TLResizeShapeOptions } from './lib/editor/Editor'
export { export {
BindingUnbindReason, BindingUnbindReason,
@ -247,6 +232,7 @@ export { useSafeId } from './lib/hooks/useSafeId'
export { useSelectionEvents } from './lib/hooks/useSelectionEvents' export { useSelectionEvents } from './lib/hooks/useSelectionEvents'
export { useTLStore } from './lib/hooks/useTLStore' export { useTLStore } from './lib/hooks/useTLStore'
export { useTransform } from './lib/hooks/useTransform' export { useTransform } from './lib/hooks/useTransform'
export { defaultTldrawOptions, type TldrawOptions } from './lib/options'
export { export {
Box, Box,
ROTATE_CORNER_TO_SELECTION_CORNER, ROTATE_CORNER_TO_SELECTION_CORNER,

View file

@ -33,6 +33,7 @@ import { useEvent } from './hooks/useEvent'
import { useForceUpdate } from './hooks/useForceUpdate' import { useForceUpdate } from './hooks/useForceUpdate'
import { useLocalStore } from './hooks/useLocalStore' import { useLocalStore } from './hooks/useLocalStore'
import { useZoomCss } from './hooks/useZoomCss' import { useZoomCss } from './hooks/useZoomCss'
import { TldrawOptions } from './options'
import { stopEventPropagation } from './utils/dom' import { stopEventPropagation } from './utils/dom'
import { TLStoreWithStatus } from './utils/sync/StoreWithStatus' import { TLStoreWithStatus } from './utils/sync/StoreWithStatus'
@ -124,6 +125,11 @@ export interface TldrawEditorBaseProps {
* Camera options for the editor. * Camera options for the editor.
*/ */
cameraOptions?: Partial<TLCameraOptions> cameraOptions?: Partial<TLCameraOptions>
/**
* Options for the editor.
*/
options?: Partial<TldrawOptions>
} }
/** /**
@ -293,6 +299,7 @@ function TldrawEditorWithReadyStore({
autoFocus = true, autoFocus = true,
inferDarkMode, inferDarkMode,
cameraOptions, cameraOptions,
options,
}: Required< }: Required<
TldrawEditorProps & { TldrawEditorProps & {
store: TLStore store: TLStore
@ -317,6 +324,7 @@ function TldrawEditorWithReadyStore({
autoFocus: initialAutoFocus, autoFocus: initialAutoFocus,
inferDarkMode, inferDarkMode,
cameraOptions, cameraOptions,
options,
}) })
setEditor(editor) setEditor(editor)
@ -334,6 +342,7 @@ function TldrawEditorWithReadyStore({
initialAutoFocus, initialAutoFocus,
inferDarkMode, inferDarkMode,
cameraOptions, cameraOptions,
options,
]) ])
const crashingError = useSyncExternalStore( const crashingError = useSyncExternalStore(

View file

@ -1,11 +1,7 @@
import { track } from '@tldraw/state' import { track } from '@tldraw/state'
import { TLInstancePresence } from '@tldraw/tlschema' import { TLInstancePresence } from '@tldraw/tlschema'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { import { Editor } from '../editor/Editor'
COLLABORATOR_CHECK_INTERVAL,
COLLABORATOR_IDLE_TIMEOUT,
COLLABORATOR_INACTIVE_TIMEOUT,
} from '../constants'
import { useEditor } from '../hooks/useEditor' import { useEditor } from '../hooks/useEditor'
import { useEditorComponents } from '../hooks/useEditorComponents' import { useEditorComponents } from '../hooks/useEditorComponents'
import { usePeerIds } from '../hooks/usePeerIds' import { usePeerIds } from '../hooks/usePeerIds'
@ -29,7 +25,7 @@ const CollaboratorGuard = track(function CollaboratorGuard({
}) { }) {
const editor = useEditor() const editor = useEditor()
const presence = usePresence(collaboratorId) const presence = usePresence(collaboratorId)
const collaboratorState = useCollaboratorState(presence) const collaboratorState = useCollaboratorState(editor, presence)
if (!(presence && presence.currentPageId === editor.getCurrentPageId())) { 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 // 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) { function getStateFromElapsedTime(editor: Editor, elapsed: number) {
return elapsed > COLLABORATOR_INACTIVE_TIMEOUT return elapsed > editor.options.collaboratorInactiveTimeoutMs
? 'inactive' ? 'inactive'
: elapsed > COLLABORATOR_IDLE_TIMEOUT : elapsed > editor.options.collaboratorIdleTimeoutMs
? 'idle' ? 'idle'
: 'active' : 'active'
} }
function useCollaboratorState(latestPresence: TLInstancePresence | null) { function useCollaboratorState(editor: Editor, latestPresence: TLInstancePresence | null) {
const rLastActivityTimestamp = useRef(latestPresence?.lastActivityTimestamp ?? -1) const rLastActivityTimestamp = useRef(latestPresence?.lastActivityTimestamp ?? -1)
const [state, setState] = useState<'active' | 'idle' | 'inactive'>(() => const [state, setState] = useState<'active' | 'idle' | 'inactive'>(() =>
getStateFromElapsedTime(Date.now() - rLastActivityTimestamp.current) getStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current)
) )
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
setState(getStateFromElapsedTime(Date.now() - rLastActivityTimestamp.current)) setState(getStateFromElapsedTime(editor, Date.now() - rLastActivityTimestamp.current))
}, COLLABORATOR_CHECK_INTERVAL) }, editor.options.collaboratorCheckIntervalMs)
return () => clearInterval(interval) return () => clearInterval(interval)
}, []) }, [editor])
if (latestPresence) { if (latestPresence) {
// We can do this on every render, it's free and cheaper than an effect // We can do this on every render, it's free and cheaper than an effect

View file

@ -3,7 +3,6 @@ import { TLHandle, TLShapeId } from '@tldraw/tlschema'
import { dedupe, modulate, objectMapValues } from '@tldraw/utils' import { dedupe, modulate, objectMapValues } from '@tldraw/utils'
import classNames from 'classnames' import classNames from 'classnames'
import { Fragment, JSX, useEffect, useRef, useState } from 'react' 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 { useCanvasEvents } from '../../hooks/useCanvasEvents'
import { useCoarsePointer } from '../../hooks/useCoarsePointer' import { useCoarsePointer } from '../../hooks/useCoarsePointer'
import { useContainer } from '../../hooks/useContainer' 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 we're below the lod distance for text shadows, turn them off
if ( if (
rMemoizedStuff.current.allowTextOutline && 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( container.style.setProperty(
'--tl-text-outline', '--tl-text-outline',
lodDisableTextOutline lodDisableTextOutline
@ -297,7 +296,8 @@ function HandlesWrapperInner({ shapeId }: { shapeId: TLShapeId }) {
if (!handles) return null if (!handles) return null
const minDistBetweenVirtualHandlesAndRegularHandles = const minDistBetweenVirtualHandlesAndRegularHandles =
((isCoarse ? COARSE_HANDLE_RADIUS : HANDLE_RADIUS) / zoomLevel) * 2 ((isCoarse ? editor.options.coarseHandleRadius : editor.options.handleRadius) / zoomLevel) *
2
return ( return (
handles handles

View file

@ -1,5 +1,5 @@
import { modulate } from '@tldraw/utils' import { modulate } from '@tldraw/utils'
import { GRID_STEPS } from '../../constants' import { useEditor } from '../../hooks/useEditor'
/** @public */ /** @public */
export interface TLGridProps { export interface TLGridProps {
@ -11,10 +11,12 @@ export interface TLGridProps {
/** @public */ /** @public */
export function DefaultGrid({ x, y, z, size }: TLGridProps) { export function DefaultGrid({ x, y, z, size }: TLGridProps) {
const editor = useEditor()
const { gridSteps } = editor.options
return ( return (
<svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg"> <svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
{GRID_STEPS.map(({ min, mid, step }, i) => { {gridSteps.map(({ min, mid, step }, i) => {
const s = step * size * z const s = step * size * z
const xo = 0.5 + x * z const xo = 0.5 + x * z
const yo = 0.5 + y * z const yo = 0.5 + y * z
@ -35,7 +37,7 @@ export function DefaultGrid({ x, y, z, size }: TLGridProps) {
) )
})} })}
</defs> </defs>
{GRID_STEPS.map(({ step }, i) => ( {gridSteps.map(({ step }, i) => (
<rect key={`grid-rect-${i}`} width="100%" height="100%" fill={`url(#grid-${step})`} /> <rect key={`grid-rect-${i}`} width="100%" height="100%" fill={`url(#grid-${step})`} />
))} ))}
</svg> </svg>

View file

@ -1,6 +1,7 @@
import { TLHandle, TLShapeId } from '@tldraw/tlschema' import { TLHandle, TLShapeId } from '@tldraw/tlschema'
import classNames from 'classnames' import classNames from 'classnames'
import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS, SIDES } from '../../constants' import { SIDES } from '../../constants'
import { useEditor } from '../../hooks/useEditor'
/** @public */ /** @public */
export interface TLHandleProps { export interface TLHandleProps {
@ -13,7 +14,8 @@ export interface TLHandleProps {
/** @public */ /** @public */
export function DefaultHandle({ handle, isCoarse, className, zoom }: TLHandleProps) { 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') { if (handle.type === 'clone') {
// bouba // bouba

View file

@ -1,16 +1,6 @@
import { TLCameraOptions } from './editor/types/misc-types' import { TLCameraOptions } from './editor/types/misc-types'
import { EASINGS } from './primitives/easings' 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 */ /** @internal */
export const DEFAULT_CAMERA_OPTIONS: TLCameraOptions = { export const DEFAULT_CAMERA_OPTIONS: TLCameraOptions = {
isLocked: false, isLocked: false,
@ -20,49 +10,12 @@ export const DEFAULT_CAMERA_OPTIONS: TLCameraOptions = {
zoomSteps: [0.1, 0.25, 0.5, 1, 2, 4, 8], 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 */ /** @internal */
export const DEFAULT_ANIMATION_OPTIONS = { export const DEFAULT_ANIMATION_OPTIONS = {
duration: 0, duration: 0,
easing: EASINGS.easeInOutCubic, 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. * Negative pointer ids are reserved for internal use.
* *
@ -71,36 +24,9 @@ export const INTERNAL_POINTER_IDS = {
CAMERA_MOVE: -10, CAMERA_MOVE: -10,
} as const } 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 */ /** @public */
export const SIDES = ['top', 'right', 'bottom', 'left'] as const 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 LEFT_MOUSE_BUTTON = 0
export const RIGHT_MOUSE_BUTTON = 2 export const RIGHT_MOUSE_BUTTON = 2
export const MIDDLE_MOUSE_BUTTON = 1 export const MIDDLE_MOUSE_BUTTON = 1

View file

@ -83,26 +83,16 @@ import { TLUser, createTLUser } from '../config/createTLUser'
import { checkBindings } from '../config/defaultBindings' import { checkBindings } from '../config/defaultBindings'
import { checkShapesAndAddCore } from '../config/defaultShapes' import { checkShapesAndAddCore } from '../config/defaultShapes'
import { import {
ANIMATION_MEDIUM_MS,
CAMERA_MOVING_TIMEOUT,
CAMERA_SLIDE_FRICTION,
COARSE_DRAG_DISTANCE,
COLLABORATOR_IDLE_TIMEOUT,
DEFAULT_ANIMATION_OPTIONS, DEFAULT_ANIMATION_OPTIONS,
DEFAULT_CAMERA_OPTIONS, DEFAULT_CAMERA_OPTIONS,
DRAG_DISTANCE,
FOLLOW_CHASE_VIEWPORT_SNAP,
HIT_TEST_MARGIN,
INTERNAL_POINTER_IDS, INTERNAL_POINTER_IDS,
LEFT_MOUSE_BUTTON, LEFT_MOUSE_BUTTON,
LONG_PRESS_DURATION,
MAX_PAGES,
MAX_SHAPES_PER_PAGE,
MIDDLE_MOUSE_BUTTON, MIDDLE_MOUSE_BUTTON,
RIGHT_MOUSE_BUTTON, RIGHT_MOUSE_BUTTON,
STYLUS_ERASER_BUTTON, STYLUS_ERASER_BUTTON,
ZOOM_TO_FIT_PADDING, ZOOM_TO_FIT_PADDING,
} from '../constants' } from '../constants'
import { TldrawOptions, defaultTldrawOptions } from '../options'
import { Box, BoxLike } from '../primitives/Box' import { Box, BoxLike } from '../primitives/Box'
import { Mat, MatLike } from '../primitives/Mat' import { Mat, MatLike } from '../primitives/Mat'
import { Vec, VecLike } from '../primitives/Vec' import { Vec, VecLike } from '../primitives/Vec'
@ -217,6 +207,8 @@ export interface TLEditorOptions {
* Options for the editor's camera. * Options for the editor's camera.
*/ */
cameraOptions?: Partial<TLCameraOptions> cameraOptions?: Partial<TLCameraOptions>
options?: Partial<TldrawOptions>
} }
/** @public */ /** @public */
@ -232,9 +224,11 @@ export class Editor extends EventEmitter<TLEventMap> {
initialState, initialState,
autoFocus, autoFocus,
inferDarkMode, inferDarkMode,
options,
}: TLEditorOptions) { }: TLEditorOptions) {
super() super()
this.options = { ...defaultTldrawOptions, ...options }
this.store = store this.store = store
this.history = new HistoryManager<TLRecord>({ this.history = new HistoryManager<TLRecord>({
store, store,
@ -700,6 +694,8 @@ export class Editor extends EventEmitter<TLEventMap> {
this.performanceTracker = new PerformanceTracker() this.performanceTracker = new PerformanceTracker()
} }
readonly options: TldrawOptions
/** /**
* The editor's store * The editor's store
* *
@ -2876,7 +2872,7 @@ export class Editor extends EventEmitter<TLEventMap> {
opts = {} as { opts = {} as {
speed: number speed: number
direction: VecLike direction: VecLike
friction: number friction?: number
speedThreshold?: number speedThreshold?: number
} }
): this { ): this {
@ -2887,7 +2883,12 @@ export class Editor extends EventEmitter<TLEventMap> {
this.stopCameraAnimation() 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) let currentSpeed = Math.min(speed, 1)
const cancel = () => { const cancel = () => {
@ -2963,7 +2964,7 @@ export class Editor extends EventEmitter<TLEventMap> {
if (index < 0) return if (index < 0) return
highlightedUserIds.splice(index, 1) highlightedUserIds.splice(index, 1)
this.updateInstanceState({ highlightedUserIds }) this.updateInstanceState({ highlightedUserIds })
}, COLLABORATOR_IDLE_TIMEOUT) }, this.options.collaboratorIdleTimeoutMs)
}) })
return this return this
@ -3279,7 +3280,10 @@ export class Editor extends EventEmitter<TLEventMap> {
Math.abs(targetViewport.maxY - currentViewport.maxY) Math.abs(targetViewport.maxY - currentViewport.maxY)
// Stop chasing if we're close enough! // 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) this._isLockedOnFollowingUser.set(true)
return return
} }
@ -3364,8 +3368,8 @@ export class Editor extends EventEmitter<TLEventMap> {
opacity: number opacity: number
}[] = [] }[] = []
let nextIndex = MAX_SHAPES_PER_PAGE * 2 let nextIndex = this.options.maxShapesPerPage * 2
let nextBackgroundIndex = MAX_SHAPES_PER_PAGE let nextBackgroundIndex = this.options.maxShapesPerPage
const erasingShapeIds = this.getErasingShapeIds() const erasingShapeIds = this.getErasingShapeIds()
@ -3403,7 +3407,7 @@ export class Editor extends EventEmitter<TLEventMap> {
if (util.providesBackgroundForChildren(shape)) { if (util.providesBackgroundForChildren(shape)) {
backgroundIndexToRestore = nextBackgroundIndex backgroundIndexToRestore = nextBackgroundIndex
nextBackgroundIndex = nextIndex nextBackgroundIndex = nextIndex
nextIndex += MAX_SHAPES_PER_PAGE nextIndex += this.options.maxShapesPerPage
} }
for (const childId of childIds) { for (const childId of childIds) {
@ -3444,7 +3448,7 @@ export class Editor extends EventEmitter<TLEventMap> {
} }
private _tickCameraState = () => { private _tickCameraState = () => {
// always reset the timeout // always reset the timeout
this._cameraStateTimeoutRemaining = CAMERA_MOVING_TIMEOUT this._cameraStateTimeoutRemaining = this.options.cameraMovingTimoutMs
// If the state is idle, then start the tick // If the state is idle, then start the tick
if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return
this._cameraState.set('moving') this._cameraState.set('moving')
@ -3667,7 +3671,7 @@ export class Editor extends EventEmitter<TLEventMap> {
createPage(page: Partial<TLPage>): this { createPage(page: Partial<TLPage>): this {
this.history.batch(() => { this.history.batch(() => {
if (this.getInstanceState().isReadonly) return 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 pages = this.getPages()
const name = getIncrementedName( const name = getIncrementedName(
@ -3734,7 +3738,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public * @public
*/ */
duplicatePage(page: TLPageId | TLPage, createId: TLPageId = PageRecordType.createId()): this { 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 id = typeof page === 'string' ? page : page.id
const freshPage = this.getPage(id) // get the most recent version of the page anyway const freshPage = this.getPage(id) // get the most recent version of the page anyway
if (!freshPage) return this if (!freshPage) return this
@ -4543,7 +4547,7 @@ export class Editor extends EventEmitter<TLEventMap> {
} else { } else {
// For open shapes (e.g. lines or draw shapes) always use the margin. // 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 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 return shape
} }
} }
@ -5445,7 +5449,7 @@ export class Editor extends EventEmitter<TLEventMap> {
) )
const maxShapesReached = const maxShapesReached =
shapesToCreate.length + this.getCurrentPageShapeIds().size > MAX_SHAPES_PER_PAGE shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage
if (maxShapesReached) { if (maxShapesReached) {
alertMaxShapes(this) alertMaxShapes(this)
@ -5464,7 +5468,7 @@ export class Editor extends EventEmitter<TLEventMap> {
const viewportPageBounds = this.getViewportPageBounds() const viewportPageBounds = this.getViewportPageBounds()
if (selectionPageBounds && !viewportPageBounds.contains(selectionPageBounds)) { if (selectionPageBounds && !viewportPageBounds.contains(selectionPageBounds)) {
this.centerOnPoint(selectionPageBounds.center, { this.centerOnPoint(selectionPageBounds.center, {
animation: { duration: ANIMATION_MEDIUM_MS }, animation: { duration: this.options.animationMediumMs },
}) })
} }
} }
@ -5508,7 +5512,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// If there is no space on pageId, or if the selected shapes // 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 // 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) alertMaxShapes(this, pageId)
return this return this
} }
@ -6611,7 +6615,8 @@ export class Editor extends EventEmitter<TLEventMap> {
const currentPageShapeIds = this.getCurrentPageShapeIds() 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) { if (maxShapesReached) {
// can't create more shapes than fit on the page // can't create more shapes than fit on the page
@ -7855,7 +7860,7 @@ export class Editor extends EventEmitter<TLEventMap> {
return newShape 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 // There's some complexity here involving children
// that might be created without their parents, so // that might be created without their parents, so
// if we're going over the limit then just don't paste. // if we're going over the limit then just don't paste.
@ -8613,7 +8618,7 @@ export class Editor extends EventEmitter<TLEventMap> {
point: this.inputs.currentScreenPoint, point: this.inputs.currentScreenPoint,
name: 'long_press', name: 'long_press',
}) })
}, LONG_PRESS_DURATION) }, this.options.longPressDurationMs)
} }
// Save the selected ids at pointer down // Save the selected ids at pointer down
@ -8681,7 +8686,10 @@ export class Editor extends EventEmitter<TLEventMap> {
inputs.isPointing && inputs.isPointing &&
!inputs.isDragging && !inputs.isDragging &&
Vec.Dist2(originPagePoint, currentPagePoint) > Vec.Dist2(originPagePoint, currentPagePoint) >
(instanceState.isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) / cz (instanceState.isCoarsePointer
? this.options.coarseDragDistanceSquared
: this.options.dragDistanceSquared) /
cz
) { ) {
// Start dragging // Start dragging
inputs.isDragging = true inputs.isDragging = true
@ -8734,11 +8742,7 @@ export class Editor extends EventEmitter<TLEventMap> {
} }
if (slideSpeed > 0) { if (slideSpeed > 0) {
this.slideCamera({ this.slideCamera({ speed: slideSpeed, direction: slideDirection })
speed: slideSpeed,
direction: slideDirection,
friction: CAMERA_SLIDE_FRICTION,
})
} }
} else { } else {
if (info.button === STYLUS_ERASER_BUTTON) { if (info.button === STYLUS_ERASER_BUTTON) {
@ -8851,7 +8855,7 @@ export class Editor extends EventEmitter<TLEventMap> {
function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) { function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) {
const name = editor.getPage(pageId)!.name 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< function applyPartialToRecordWithProps<

View file

@ -6,7 +6,6 @@ import {
getDefaultColorTheme, getDefaultColorTheme,
} from '@tldraw/tlschema' } from '@tldraw/tlschema'
import { Fragment, ReactElement } from 'react' import { Fragment, ReactElement } from 'react'
import { SVG_PADDING } from '../constants'
import { Editor } from './Editor' import { Editor } from './Editor'
import { SvgExportContext, SvgExportContextProvider, SvgExportDef } from './types/SvgExportContext' import { SvgExportContext, SvgExportContextProvider, SvgExportDef } from './types/SvgExportContext'
import { TLSvgOptions } from './types/misc-types' import { TLSvgOptions } from './types/misc-types'
@ -22,7 +21,12 @@ export async function getSvgJsx(
if (ids.length === 0) return if (ids.length === 0) return
if (!window.document) throw Error('No document') 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 isDarkMode = opts.darkMode ?? editor.user.getIsDarkMode()
const theme = getDefaultColorTheme({ isDarkMode }) const theme = getDefaultColorTheme({ isDarkMode })

View file

@ -1,9 +1,3 @@
import {
COARSE_DRAG_DISTANCE,
DOUBLE_CLICK_DURATION,
DRAG_DISTANCE,
MULTI_CLICK_DURATION,
} from '../../constants'
import { Vec } from '../../primitives/Vec' import { Vec } from '../../primitives/Vec'
import { uniqueId } from '../../utils/uniqueId' import { uniqueId } from '../../utils/uniqueId'
import type { Editor } from '../Editor' import type { Editor } from '../Editor'
@ -72,7 +66,9 @@ export class ClickManager {
this._clickState = 'idle' 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._clickState !== 'idle' &&
this._clickScreenPoint && this._clickScreenPoint &&
Vec.Dist2(this._clickScreenPoint, this.editor.inputs.currentScreenPoint) > 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() this.cancelDoubleClickTimeout()
} }

View file

@ -0,0 +1,80 @@
/**
* Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}.
*
* @example
* ```tsx
* const options: Partial<TldrawOptions> = {
* maxPages: 3,
* maxShapesPerPage: 1000,
* }
*
* function MyTldrawComponent() {
* return <Tldraw options={options} />
* }
* ```
*
* @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

View file

@ -1,4 +1,3 @@
import { COARSE_POINTER_WIDTH, EDGE_SCROLL_DISTANCE, EDGE_SCROLL_SPEED } from '../constants'
import { Editor } from '../editor/Editor' import { Editor } from '../editor/Editor'
import { Vec } from '../primitives/Vec' import { Vec } from '../primitives/Vec'
@ -9,14 +8,15 @@ import { Vec } from '../primitives/Vec'
* @internal * @internal
*/ */
function getEdgeProximityFactor( function getEdgeProximityFactor(
editor: Editor,
position: number, position: number,
dimension: number, dimension: number,
isCoarse: boolean, isCoarse: boolean,
insetStart: boolean, insetStart: boolean,
insetEnd: boolean insetEnd: boolean
) { ) {
const dist = EDGE_SCROLL_DISTANCE const dist = editor.options.edgeScrollDistance
const pw = isCoarse ? COARSE_POINTER_WIDTH : 0 // pointer width const pw = isCoarse ? editor.options.coarsePointerWidth : 0 // pointer width
const pMin = position - pw const pMin = position - pw
const pMax = position + pw const pMax = position + pw
const min = insetStart ? 0 : dist const min = insetStart ? 0 : dist
@ -53,13 +53,13 @@ export function moveCameraWhenCloseToEdge(editor: Editor) {
isCoarsePointer, isCoarsePointer,
insets: [t, r, b, l], insets: [t, r, b, l],
} = editor.getInstanceState() } = editor.getInstanceState()
const proximityFactorX = getEdgeProximityFactor(x, screenBounds.w, isCoarsePointer, l, r) const proximityFactorX = getEdgeProximityFactor(editor, x, screenBounds.w, isCoarsePointer, l, r)
const proximityFactorY = getEdgeProximityFactor(y, screenBounds.h, isCoarsePointer, t, b) const proximityFactorY = getEdgeProximityFactor(editor, y, screenBounds.h, isCoarsePointer, t, b)
if (proximityFactorX === 0 && proximityFactorY === 0) return if (proximityFactorX === 0 && proximityFactorY === 0) return
// Determines the base speed of the scroll // 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 scrollDeltaX = (pxSpeed * proximityFactorX * screenSizeFactorX) / zoomLevel
const scrollDeltaY = (pxSpeed * proximityFactorY * screenSizeFactorY) / zoomLevel const scrollDeltaY = (pxSpeed * proximityFactorY * screenSizeFactorY) / zoomLevel

View file

@ -1,5 +1,4 @@
import { import {
DRAG_DISTANCE,
Mat, Mat,
StateNode, StateNode,
TLDefaultSizeStyle, TLDefaultSizeStyle,
@ -317,7 +316,8 @@ export class Drawing extends StateNode {
} }
const hasMovedFarEnough = 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 // 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 // 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 = 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 // 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 // where it is now; if it's far enough away, then update the page point where

View file

@ -36,7 +36,6 @@ import {
import { getFontDefForExport } from '../shared/defaultStyleDefs' import { getFontDefForExport } from '../shared/defaultStyleDefs'
import { startEditingShapeWithLabel } from '../../tools/SelectTool/selectHelpers' import { startEditingShapeWithLabel } from '../../tools/SelectTool/selectHelpers'
import { ADJACENT_SHAPE_MARGIN } from '../../ui/constants'
import { useForceSolid } from '../shared/useForceSolid' import { useForceSolid } from '../shared/useForceSolid'
import { import {
CLONE_HANDLE_MARGIN, CLONE_HANDLE_MARGIN,
@ -402,7 +401,7 @@ function useNoteKeydownHandler(id: TLShapeId) {
const offsetLength = const offsetLength =
NOTE_SIZE + NOTE_SIZE +
ADJACENT_SHAPE_MARGIN + editor.options.adjacentShapeMargin +
// If we're growing down, we need to account for the current shape's growY // If we're growing down, we need to account for the current shape's growY
(isCmdEnter && !e.shiftKey ? shape.props.growY : 0) (isCmdEnter && !e.shiftKey ? shape.props.growY : 0)

View file

@ -1,5 +1,4 @@
import { import {
HIT_TEST_MARGIN,
StateNode, StateNode,
TLEventHandlers, TLEventHandlers,
TLFrameShape, TLFrameShape,
@ -90,7 +89,7 @@ export class Erasing extends StateNode {
this.pushPointToScribble() this.pushPointToScribble()
const erasing = new Set<TLShapeId>(erasingShapeIds) const erasing = new Set<TLShapeId>(erasingShapeIds)
const minDist = HIT_TEST_MARGIN / zoomLevel const minDist = this.editor.options.hitTestMargin / zoomLevel
for (const shape of currentPageShapes) { for (const shape of currentPageShapes) {
if (editor.isShapeOfType<TLGroupShape>(shape, 'group')) continue if (editor.isShapeOfType<TLGroupShape>(shape, 'group')) continue

View file

@ -1,5 +1,4 @@
import { import {
HIT_TEST_MARGIN,
StateNode, StateNode,
TLEventHandlers, TLEventHandlers,
TLFrameShape, TLFrameShape,
@ -34,7 +33,7 @@ export class Pointing extends StateNode {
if ( if (
this.editor.isPointInShape(shape, currentPagePoint, { this.editor.isPointInShape(shape, currentPagePoint, {
hitInside: false, hitInside: false,
margin: HIT_TEST_MARGIN / zoomLevel, margin: this.editor.options.hitTestMargin / zoomLevel,
}) })
) { ) {
const hitShape = this.editor.getOutermostSelectableShape(shape) const hitShape = this.editor.getOutermostSelectableShape(shape)

View file

@ -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 { export class Dragging extends StateNode {
static override id = 'dragging' static override id = 'dragging'
@ -42,11 +42,7 @@ export class Dragging extends StateNode {
const velocityAtPointerUp = Math.min(pointerVelocity.len(), 2) const velocityAtPointerUp = Math.min(pointerVelocity.len(), 2)
if (velocityAtPointerUp > 0.1) { if (velocityAtPointerUp > 0.1) {
this.editor.slideCamera({ this.editor.slideCamera({ speed: velocityAtPointerUp, direction: pointerVelocity })
speed: velocityAtPointerUp,
direction: pointerVelocity,
friction: CAMERA_SLIDE_FRICTION,
})
} }
this.parent.transition('idle') this.parent.transition('idle')

View file

@ -1,7 +1,6 @@
import { import {
Editor, Editor,
Group2d, Group2d,
HIT_TEST_MARGIN,
StateNode, StateNode,
TLArrowShape, TLArrowShape,
TLClickEventInfo, TLClickEventInfo,
@ -199,7 +198,7 @@ export class Idle extends StateNode {
? hoveredShape ? hoveredShape
: this.editor.getSelectedShapeAtPoint(this.editor.inputs.currentPagePoint) ?? : this.editor.getSelectedShapeAtPoint(this.editor.inputs.currentPagePoint) ??
this.editor.getShapeAtPoint(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, hitInside: false,
}) })
@ -353,7 +352,7 @@ export class Idle extends StateNode {
hoveredShape && !this.editor.isShapeOfType<TLGroupShape>(hoveredShape, 'group') hoveredShape && !this.editor.isShapeOfType<TLGroupShape>(hoveredShape, 'group')
? hoveredShape ? hoveredShape
: this.editor.getShapeAtPoint(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, hitInside: false,
hitLabels: true, hitLabels: true,
hitLocked: true, hitLocked: true,

View file

@ -1,10 +1,4 @@
import { import { StateNode, TLEventHandlers, TLPointerEventInfo, TLShape } from '@tldraw/editor'
HIT_TEST_MARGIN,
StateNode,
TLEventHandlers,
TLPointerEventInfo,
TLShape,
} from '@tldraw/editor'
import { getTextLabels } from '../../../utils/shapes/shapes' import { getTextLabels } from '../../../utils/shapes/shapes'
export class PointingShape extends StateNode { export class PointingShape extends StateNode {
@ -73,7 +67,7 @@ export class PointingShape extends StateNode {
const hitShape = const hitShape =
this.editor.getShapeAtPoint(currentPagePoint, { this.editor.getShapeAtPoint(currentPagePoint, {
margin: HIT_TEST_MARGIN / zoomLevel, margin: this.editor.options.hitTestMargin / zoomLevel,
hitInside: true, hitInside: true,
renderingOnly: true, renderingOnly: true,
}) ?? this.hitShape }) ?? this.hitShape

View file

@ -98,7 +98,7 @@ export class ScribbleBrushing extends StateNode {
const shapes = currentPageShapes const shapes = currentPageShapes
let shape: TLShape, geometry: Geometry2d, A: Vec, B: Vec 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++) { for (let i = 0, n = shapes.length; i < n; i++) {
shape = shapes[i] shape = shapes[i]

View file

@ -1,5 +1,4 @@
import { import {
ANIMATION_MEDIUM_MS,
Editor, Editor,
Geometry2d, Geometry2d,
Mat, Mat,
@ -149,7 +148,7 @@ export function zoomToShapeIfOffscreen(editor: Editor) {
}) })
editor.zoomToBounds(nextBounds, { editor.zoomToBounds(nextBounds, {
animation: { animation: {
duration: ANIMATION_MEDIUM_MS, duration: editor.options.animationMediumMs,
}, },
inset: 0, inset: 0,
}) })

View file

@ -1,4 +1,4 @@
import { Editor, HIT_TEST_MARGIN, TLShape } from '@tldraw/editor' import { Editor, TLShape } from '@tldraw/editor'
export function getHitShapeOnCanvasPointerDown( export function getHitShapeOnCanvasPointerDown(
editor: Editor, editor: Editor,
@ -14,7 +14,7 @@ export function getHitShapeOnCanvasPointerDown(
editor.getShapeAtPoint(currentPagePoint, { editor.getShapeAtPoint(currentPagePoint, {
hitInside: false, hitInside: false,
hitLabels, hitLabels,
margin: HIT_TEST_MARGIN / zoomLevel, margin: editor.options.hitTestMargin / zoomLevel,
renderingOnly: true, renderingOnly: true,
}) ?? }) ??
// selected shape at point // selected shape at point

View file

@ -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) { export function selectOnCanvasPointerUp(editor: Editor) {
const selectedShapeIds = editor.getSelectedShapeIds() const selectedShapeIds = editor.getSelectedShapeIds()
@ -6,7 +6,7 @@ export function selectOnCanvasPointerUp(editor: Editor) {
const hitShape = editor.getShapeAtPoint(currentPagePoint, { const hitShape = editor.getShapeAtPoint(currentPagePoint, {
hitInside: false, hitInside: false,
margin: HIT_TEST_MARGIN / editor.getZoomLevel(), margin: editor.options.hitTestMargin / editor.getZoomLevel(),
hitLabels: true, hitLabels: true,
renderingOnly: true, renderingOnly: true,
filter: (shape) => !shape.isLocked, filter: (shape) => !shape.isLocked,

View file

@ -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) { 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 // 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, { const hitShape = editor.getShapeAtPoint(editor.inputs.currentPagePoint, {
hitInside: false, hitInside: false,
hitLabels: false, hitLabels: false,
margin: HIT_TEST_MARGIN / editor.getZoomLevel(), margin: editor.options.hitTestMargin / editor.getZoomLevel(),
renderingOnly: true, renderingOnly: true,
}) })

View file

@ -1,5 +1,4 @@
import { import {
ANIMATION_MEDIUM_MS,
Box, Box,
TLPointerEventInfo, TLPointerEventInfo,
Vec, Vec,
@ -62,7 +61,7 @@ export function DefaultMinimap() {
minimapRef.current.originPagePoint.setTo(clampedPoint) minimapRef.current.originPagePoint.setTo(clampedPoint)
minimapRef.current.originPageCenter.setTo(editor.getViewportPageBounds().center) minimapRef.current.originPageCenter.setTo(editor.getViewportPageBounds().center)
editor.centerOnPoint(point, { animation: { duration: ANIMATION_MEDIUM_MS } }) editor.centerOnPoint(point, { animation: { duration: editor.options.animationMediumMs } })
}, },
[editor] [editor]
) )
@ -101,7 +100,7 @@ export function DefaultMinimap() {
const pagePoint = Vec.Add(point, delta) const pagePoint = Vec.Add(point, delta)
minimapRef.current.originPagePoint.setTo(pagePoint) minimapRef.current.originPagePoint.setTo(pagePoint)
minimapRef.current.originPageCenter.setTo(point) minimapRef.current.originPageCenter.setTo(point)
editor.centerOnPoint(point, { animation: { duration: ANIMATION_MEDIUM_MS } }) editor.centerOnPoint(point, { animation: { duration: editor.options.animationMediumMs } })
} else { } else {
const clampedPoint = minimapRef.current.minimapScreenPointToPagePoint( const clampedPoint = minimapRef.current.minimapScreenPointToPagePoint(
e.clientX, e.clientX,

View file

@ -1,5 +1,4 @@
import { import {
MAX_PAGES,
PageRecordType, PageRecordType,
TLPageId, TLPageId,
releasePointerCapture, 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 // If the user has reached the max page count, we disable the "add page" button
const maxPageCountReached = useValue( const maxPageCountReached = useValue(
'maxPageCountReached', 'maxPageCountReached',
() => editor.getPages().length >= MAX_PAGES, () => editor.getPages().length >= editor.options.maxPages,
[editor] [editor]
) )

View file

@ -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 { useCallback } from 'react'
import { useTranslation } from '../../hooks/useTranslation/useTranslation' import { useTranslation } from '../../hooks/useTranslation/useTranslation'
import { TldrawUiButton } from '../primitives/Button/TldrawUiButton' import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
@ -66,7 +66,7 @@ export const PageItemSubmenu = track(function PageItemSubmenu({
id="duplicate" id="duplicate"
label="page-menu.submenu.duplicate-page" label="page-menu.submenu.duplicate-page"
onSelect={onDuplicate} onSelect={onDuplicate}
disabled={pages.length >= MAX_PAGES} disabled={pages.length >= editor.options.maxPages}
/> />
{index > 0 && ( {index > 0 && (
<TldrawUiMenuItem <TldrawUiMenuItem

View file

@ -1,5 +1,5 @@
import * as _Dropdown from '@radix-ui/react-dropdown-menu' import * as _Dropdown from '@radix-ui/react-dropdown-menu'
import { ANIMATION_MEDIUM_MS, useContainer, useEditor, useValue } from '@tldraw/editor' import { useContainer, useEditor, useValue } from '@tldraw/editor'
import { ReactNode, forwardRef, memo, useCallback } from 'react' import { ReactNode, forwardRef, memo, useCallback } from 'react'
import { PORTRAIT_BREAKPOINT } from '../../constants' import { PORTRAIT_BREAKPOINT } from '../../constants'
import { useBreakpoint } from '../../context/breakpoints' import { useBreakpoint } from '../../context/breakpoints'
@ -56,7 +56,7 @@ const ZoomTriggerButton = forwardRef<HTMLButtonElement, any>(
const handleDoubleClick = useCallback(() => { const handleDoubleClick = useCallback(() => {
editor.resetZoom(editor.getViewportScreenCenter(), { editor.resetZoom(editor.getViewportScreenCenter(), {
animation: { duration: ANIMATION_MEDIUM_MS }, animation: { duration: editor.options.animationMediumMs },
}) })
}, [editor]) }, [editor])

View file

@ -13,5 +13,3 @@ export enum PORTRAIT_BREAKPOINT {
TABLET = 6, TABLET = 6,
DESKTOP = 7, DESKTOP = 7,
} }
export const ADJACENT_SHAPE_MARGIN = 20

View file

@ -1,5 +1,4 @@
import { import {
ANIMATION_MEDIUM_MS,
Box, Box,
DefaultColorStyle, DefaultColorStyle,
Editor, Editor,
@ -25,7 +24,6 @@ import { getEmbedInfo } from '../../utils/embeds/embeds'
import { fitFrameToContent, removeFrame } from '../../utils/frames/frames' import { fitFrameToContent, removeFrame } from '../../utils/frames/frames'
import { EditLinkDialog } from '../components/EditLinkDialog' import { EditLinkDialog } from '../components/EditLinkDialog'
import { EmbedDialog } from '../components/EmbedDialog' import { EmbedDialog } from '../components/EmbedDialog'
import { ADJACENT_SHAPE_MARGIN } from '../constants'
import { useMenuClipboardEvents } from '../hooks/useClipboardEvents' import { useMenuClipboardEvents } from '../hooks/useClipboardEvents'
import { useCopyAs } from '../hooks/useCopyAs' import { useCopyAs } from '../hooks/useCopyAs'
import { useExportAs } from '../hooks/useExportAs' import { useExportAs } from '../hooks/useExportAs'
@ -819,7 +817,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
trackEvent('pack-shapes', { source }) trackEvent('pack-shapes', { source })
editor.mark('pack') editor.mark('pack')
const selectedShapeIds = editor.getSelectedShapeIds() const selectedShapeIds = editor.getSelectedShapeIds()
editor.packShapes(selectedShapeIds, ADJACENT_SHAPE_MARGIN) editor.packShapes(selectedShapeIds, editor.options.adjacentShapeMargin)
kickoutOccludedShapes(editor, selectedShapeIds) kickoutOccludedShapes(editor, selectedShapeIds)
}, },
}, },
@ -1038,7 +1036,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
onSelect(source) { onSelect(source) {
trackEvent('zoom-in', { source }) trackEvent('zoom-in', { source })
editor.zoomIn(undefined, { 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) { onSelect(source) {
trackEvent('zoom-out', { source }) trackEvent('zoom-out', { source })
editor.zoomOut(undefined, { 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) { onSelect(source) {
trackEvent('reset-zoom', { source }) trackEvent('reset-zoom', { source })
editor.resetZoom(undefined, { 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, readonlyOk: true,
onSelect(source) { onSelect(source) {
trackEvent('zoom-to-fit', { 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 if (mustGoBackToSelectToolFirst()) return
trackEvent('zoom-to-selection', { source }) trackEvent('zoom-to-selection', { source })
editor.zoomToSelection({ animation: { duration: ANIMATION_MEDIUM_MS } }) editor.zoomToSelection({ animation: { duration: editor.options.animationMediumMs } })
}, },
}, },
{ {

View file

@ -1,7 +1,6 @@
import { import {
AssetRecordType, AssetRecordType,
Editor, Editor,
MAX_SHAPES_PER_PAGE,
PageRecordType, PageRecordType,
TLArrowShape, TLArrowShape,
TLArrowShapeArrowheadStyle, TLArrowShapeArrowheadStyle,
@ -130,7 +129,7 @@ export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocume
const v1Shapes = Object.values(v1Page.shapes ?? {}) const v1Shapes = Object.values(v1Page.shapes ?? {})
.sort((a, b) => (a.childIndex < b.childIndex ? -1 : 1)) .sort((a, b) => (a.childIndex < b.childIndex ? -1 : 1))
.slice(0, MAX_SHAPES_PER_PAGE) .slice(0, editor.options.maxShapesPerPage)
// Groups only // Groups only
v1Shapes.forEach((v1Shape) => { v1Shapes.forEach((v1Shape) => {

View file

@ -1,4 +1,4 @@
import { MAX_PAGES, PageRecordType } from '@tldraw/editor' import { PageRecordType } from '@tldraw/editor'
import { TestEditor } from '../TestEditor' import { TestEditor } from '../TestEditor'
let editor: TestEditor let editor: TestEditor
@ -31,10 +31,10 @@ it('Creates a page', () => {
}) })
it("Doesn't create a page if max pages is reached", () => { 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}` }) 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', () => { it('[regression] does not die if every page has the same index', () => {

View file

@ -1,4 +1,4 @@
import { MAX_PAGES, createShapeId } from '@tldraw/editor' import { createShapeId } from '@tldraw/editor'
import { TestEditor } from '../TestEditor' import { TestEditor } from '../TestEditor'
let editor: TestEditor let editor: TestEditor
@ -48,8 +48,8 @@ it('Duplicates a page', () => {
}) })
it("Doesn't duplicate the page if max pages is reached", () => { 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()) editor.duplicatePage(editor.getCurrentPageId())
} }
expect(editor.getPages().length).toBe(MAX_PAGES) expect(editor.getPages().length).toBe(editor.options.maxPages)
}) })

View file

@ -1,4 +1,4 @@
import { DefaultDashStyle, SVG_PADDING, createShapeId } from '@tldraw/editor' import { DefaultDashStyle, createShapeId } from '@tldraw/editor'
import { TestEditor } from '../TestEditor' import { TestEditor } from '../TestEditor'
let editor: 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 svg = await editor.getSvgString(editor.getSelectedShapeIds())
const parsed = parseSvg(svg!) const parsed = parseSvg(svg!)
const bbox = editor.getSelectionRotatedPageBounds()! 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('width')).toMatch(expanded.width + '')
expect(parsed.getAttribute('height')).toMatch(expanded.height + '') expect(parsed.getAttribute('height')).toMatch(expanded.height + '')