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
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<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;
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<TLEventMap> {
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<TLEventMap> {
readonly sideEffects: StoreSideEffects<TLRecord>;
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<Geometry2dOptions, 'isClosed' | 'isFilled'> & {
@ -1409,9 +1433,6 @@ export class HistoryManager<R extends UnknownRecord> {
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<HTMLElement> | WheelEvent): {
x: number;
@ -2075,9 +2087,6 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// @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<TldrawOptions>;
shapeUtils?: readonly TLAnyShapeUtilConstructor[];
tools?: readonly TLStateNodeConstructor[];
user?: TLUser;
@ -2299,6 +2309,62 @@ export type TldrawEditorProps = Expand<TldrawEditorBaseProps & ({
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)
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<TldrawOptions>;
shapeUtils: readonly TLShapeUtilConstructor<TLUnknownShape>[];
store: TLStore;
tools: readonly TLStateNodeConstructor[];

View file

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

View file

@ -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<TLCameraOptions>
/**
* Options for the editor.
*/
options?: Partial<TldrawOptions>
}
/**
@ -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(

View file

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

View file

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

View file

@ -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 (
<svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
{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) {
)
})}
</defs>
{GRID_STEPS.map(({ step }, i) => (
{gridSteps.map(({ step }, i) => (
<rect key={`grid-rect-${i}`} width="100%" height="100%" fill={`url(#grid-${step})`} />
))}
</svg>

View file

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

View file

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

View file

@ -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<TLCameraOptions>
options?: Partial<TldrawOptions>
}
/** @public */
@ -232,9 +224,11 @@ export class Editor extends EventEmitter<TLEventMap> {
initialState,
autoFocus,
inferDarkMode,
options,
}: TLEditorOptions) {
super()
this.options = { ...defaultTldrawOptions, ...options }
this.store = store
this.history = new HistoryManager<TLRecord>({
store,
@ -700,6 +694,8 @@ export class Editor extends EventEmitter<TLEventMap> {
this.performanceTracker = new PerformanceTracker()
}
readonly options: TldrawOptions
/**
* The editor's store
*
@ -2876,7 +2872,7 @@ export class Editor extends EventEmitter<TLEventMap> {
opts = {} as {
speed: number
direction: VecLike
friction: number
friction?: number
speedThreshold?: number
}
): this {
@ -2887,7 +2883,12 @@ export class Editor extends EventEmitter<TLEventMap> {
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<TLEventMap> {
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<TLEventMap> {
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<TLEventMap> {
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<TLEventMap> {
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<TLEventMap> {
}
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<TLEventMap> {
createPage(page: Partial<TLPage>): 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<TLEventMap> {
* @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<TLEventMap> {
} 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<TLEventMap> {
)
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<TLEventMap> {
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<TLEventMap> {
// 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<TLEventMap> {
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<TLEventMap> {
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<TLEventMap> {
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<TLEventMap> {
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<TLEventMap> {
}
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<TLEventMap> {
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<

View file

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

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 { 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()
}

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 { 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

View file

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

View file

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

View file

@ -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<TLShapeId>(erasingShapeIds)
const minDist = HIT_TEST_MARGIN / zoomLevel
const minDist = this.editor.options.hitTestMargin / zoomLevel
for (const shape of currentPageShapes) {
if (editor.isShapeOfType<TLGroupShape>(shape, 'group')) continue

View file

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

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 {
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')

View file

@ -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<TLGroupShape>(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,

View file

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

View file

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

View file

@ -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,
})

View file

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

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) {
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,

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) {
// 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,
})

View file

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

View file

@ -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]
)

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 { 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 && (
<TldrawUiMenuItem

View file

@ -1,5 +1,5 @@
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 { PORTRAIT_BREAKPOINT } from '../../constants'
import { useBreakpoint } from '../../context/breakpoints'
@ -56,7 +56,7 @@ const ZoomTriggerButton = forwardRef<HTMLButtonElement, any>(
const handleDoubleClick = useCallback(() => {
editor.resetZoom(editor.getViewportScreenCenter(), {
animation: { duration: ANIMATION_MEDIUM_MS },
animation: { duration: editor.options.animationMediumMs },
})
}, [editor])

View file

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

View file

@ -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 } })
},
},
{

View file

@ -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) => {

View file

@ -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', () => {

View file

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

View file

@ -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 + '')