Roundup fixes (#2862)

This one is a roundup of superficial changes, apologies for having them
in a single PR.

This PR:
- does some chair re-arranging for one of our hotter paths related to
updating shapes
- changes our type exports for editor components
- adds shape indicator to editor components
- moves canvas to be an editor component
- fixes a CSS bug with hinted buttons
- fixes CSS bugs with the menus
- fixes bad imports in examples

### Change Type

- [x] `major`
This commit is contained in:
Steve Ruiz 2024-02-19 14:52:43 +00:00 committed by GitHub
parent b3d6af4454
commit 9fc5f4459f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 2191 additions and 1825 deletions

View file

@ -8,6 +8,7 @@ import {
TldrawUiButton, TldrawUiButton,
TldrawUiButtonLabel, TldrawUiButtonLabel,
useEditor, useEditor,
useRelevantStyles,
} from '@tldraw/tldraw' } from '@tldraw/tldraw'
import '@tldraw/tldraw/tldraw.css' import '@tldraw/tldraw/tldraw.css'
@ -16,6 +17,8 @@ function CustomStylePanel(props: TLUiStylePanelProps) {
// Styles are complex, sorry. Check our DefaultStylePanel for an example. // Styles are complex, sorry. Check our DefaultStylePanel for an example.
const styles = useRelevantStyles()
return ( return (
<DefaultStylePanel {...props}> <DefaultStylePanel {...props}>
<TldrawUiButton <TldrawUiButton
@ -34,7 +37,7 @@ function CustomStylePanel(props: TLUiStylePanelProps) {
> >
<TldrawUiButtonLabel>Green</TldrawUiButtonLabel> <TldrawUiButtonLabel>Green</TldrawUiButtonLabel>
</TldrawUiButton> </TldrawUiButton>
<DefaultStylePanelContent relevantStyles={props.relevantStyles} /> <DefaultStylePanelContent styles={styles} />
</DefaultStylePanel> </DefaultStylePanel>
) )
} }

View file

@ -1,5 +1,4 @@
import { TLComponents, Tldraw } from '@tldraw/tldraw' import { DefaultToolbar, TLComponents, Tldraw } from '@tldraw/tldraw'
import { DefaultToolbar } from '@tldraw/tldraw/src/lib/ui/components/Toolbar/DefaultToolbar'
import '@tldraw/tldraw/tldraw.css' import '@tldraw/tldraw/tldraw.css'
function CustomToolbar() { function CustomToolbar() {

View file

@ -1,5 +1,4 @@
import { import {
Canvas,
ContextMenu, ContextMenu,
DefaultContextMenuContent, DefaultContextMenuContent,
TldrawEditor, TldrawEditor,
@ -39,7 +38,7 @@ export default function ExplodedExample() {
persistenceKey="exploded-example" persistenceKey="exploded-example"
> >
<TldrawUi> <TldrawUi>
<ContextMenu canvas={<Canvas />}> <ContextMenu>
<DefaultContextMenuContent /> <DefaultContextMenuContent />
</ContextMenu> </ContextMenu>
</TldrawUi> </TldrawUi>

View file

@ -21,7 +21,7 @@ setDefaultEditorAssetUrls(assetUrls)
setDefaultUiAssetUrls(assetUrls) setDefaultUiAssetUrls(assetUrls)
const gettingStartedExamples = examples.find((e) => e.id === 'Getting Started') const gettingStartedExamples = examples.find((e) => e.id === 'Getting Started')
if (!gettingStartedExamples) throw new Error('Could not find getting started exmaples') if (!gettingStartedExamples) throw new Error('Could not find getting started exmaples')
const basicExample = gettingStartedExamples.value.find((e) => e.priority === 1) const basicExample = gettingStartedExamples.value.find((e) => e.title === 'Persistence key')
if (!basicExample) throw new Error('Could not find initial example') if (!basicExample) throw new Error('Could not find initial example')
const router = createBrowserRouter([ const router = createBrowserRouter([

View file

@ -306,11 +306,6 @@ export const CAMERA_SLIDE_FRICTION = 0.09;
// @public (undocumented) // @public (undocumented)
export function canonicalizeRotation(a: number): number; export function canonicalizeRotation(a: number): number;
// @public (undocumented)
export function Canvas({ className }: {
className?: string;
}): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export class Circle2d extends Geometry2d { export class Circle2d extends Geometry2d {
constructor(config: Omit<Geometry2dOptions, 'isClosed'> & { constructor(config: Omit<Geometry2dOptions, 'isClosed'> & {
@ -449,50 +444,49 @@ export const DEFAULT_ANIMATION_OPTIONS: {
export function DefaultBackground(): JSX_2.Element; export function DefaultBackground(): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultBrush: TLBrushComponent; export const DefaultBrush: ({ brush, color, opacity, className }: TLBrushProps) => JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultCollaboratorHint: TLCollaboratorHintComponent; export function DefaultCanvas({ className }: TLCanvasComponentProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultCursor: NamedExoticComponent< { export function DefaultCollaboratorHint({ className, zoom, point, color, viewport, opacity, }: TLCollaboratorHintProps): JSX_2.Element;
className?: string | undefined;
point: null | VecModel; // @public (undocumented)
zoom: number; export const DefaultCursor: NamedExoticComponent<TLCursorProps>;
color?: string | undefined;
name: null | string;
chatMessage: string;
}>;
// @public (undocumented) // @public (undocumented)
export const DefaultErrorFallback: TLErrorFallbackComponent; export const DefaultErrorFallback: TLErrorFallbackComponent;
// @public (undocumented) // @public (undocumented)
export const DefaultGrid: TLGridComponent; export function DefaultGrid({ x, y, z, size }: TLGridProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultHandle: TLHandleComponent; export function DefaultHandle({ handle, isCoarse, className, zoom }: TLHandleProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultHandles: TLHandlesComponent; export const DefaultHandles: ({ children }: TLHandlesProps) => JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultHoveredShapeIndicator: TLHoveredShapeIndicatorComponent; export function DefaultHoveredShapeIndicator({ shapeId }: TLHoveredShapeIndicatorProps): JSX_2.Element | null;
// @public (undocumented) // @public (undocumented)
export const DefaultScribble: TLScribbleComponent; export function DefaultScribble({ scribble, zoom, color, opacity, className }: TLScribbleProps): JSX_2.Element | null;
// @public (undocumented) // @public (undocumented)
export const DefaultSelectionBackground: TLSelectionBackgroundComponent; export function DefaultSelectionBackground({ bounds, rotation }: TLSelectionBackgroundProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultSelectionForeground: TLSelectionForegroundComponent; export function DefaultSelectionForeground({ bounds, rotation }: TLSelectionForegroundProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultSnapIndicator: TLSnapIndicatorComponent; export const DefaultShapeIndicator: NamedExoticComponent<TLShapeIndicatorProps>;
// @public (undocumented) // @public (undocumented)
export const DefaultSpinner: TLSpinnerComponent; export function DefaultSnapIndicator({ className, line, zoom }: TLSnapIndicatorProps): JSX_2.Element;
// @public (undocumented)
export function DefaultSpinner(): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const DefaultSvgDefs: () => null; export const DefaultSvgDefs: () => null;
@ -1587,14 +1581,6 @@ export function setRuntimeOverrides(input: Partial<typeof runtime>): void;
// @public (undocumented) // @public (undocumented)
export function setUserPreferences(user: TLUserPreferences): void; export function setUserPreferences(user: TLUserPreferences): void;
// @public (undocumented)
export const ShapeIndicator: React_3.NamedExoticComponent<{
id: TLShapeId;
color?: string | undefined;
opacity?: number | undefined;
className?: string | undefined;
}>;
// @public (undocumented) // @public (undocumented)
export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> { export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
constructor(editor: Editor); constructor(editor: Editor);
@ -1898,9 +1884,6 @@ export type TLArrowPoint = {
arrowhead: TLArrowShapeArrowheadStyle; arrowhead: TLArrowShapeArrowheadStyle;
}; };
// @public (undocumented)
export type TLBackgroundComponent = ComponentType;
// @public (undocumented) // @public (undocumented)
export type TLBaseBoxShape = TLBaseShape<string, { export type TLBaseBoxShape = TLBaseShape<string, {
w: number; w: number;
@ -1932,12 +1915,12 @@ export type TLBeforeCreateHandler<R extends TLRecord> = (record: R, source: 'rem
export type TLBeforeDeleteHandler<R extends TLRecord> = (record: R, source: 'remote' | 'user') => false | void; export type TLBeforeDeleteHandler<R extends TLRecord> = (record: R, source: 'remote' | 'user') => false | void;
// @public (undocumented) // @public (undocumented)
export type TLBrushComponent = ComponentType<{ export type TLBrushProps = {
brush: BoxModel; brush: BoxModel;
color?: string; color?: string;
opacity?: number; opacity?: number;
className?: string; className?: string;
}>; };
// @public (undocumented) // @public (undocumented)
export type TLCancelEvent = (info: TLCancelEventInfo) => void; export type TLCancelEvent = (info: TLCancelEventInfo) => void;
@ -1965,14 +1948,14 @@ export type TLClickEventInfo = TLBaseEventInfo & {
export type TLCLickEventName = 'double_click' | 'quadruple_click' | 'triple_click'; export type TLCLickEventName = 'double_click' | 'quadruple_click' | 'triple_click';
// @public (undocumented) // @public (undocumented)
export type TLCollaboratorHintComponent = ComponentType<{ export type TLCollaboratorHintProps = {
className?: string; className?: string;
point: VecModel; point: VecModel;
viewport: Box; viewport: Box;
zoom: number; zoom: number;
opacity?: number; opacity?: number;
color: string; color: string;
}>; };
// @public (undocumented) // @public (undocumented)
export type TLCommand<Name extends string = any, Data = any> = { export type TLCommand<Name extends string = any, Data = any> = {
@ -2020,14 +2003,14 @@ export interface TLContent {
} }
// @public (undocumented) // @public (undocumented)
export type TLCursorComponent = ComponentType<{ export type TLCursorProps = {
className?: string; className?: string;
point: null | VecModel; point: null | VecModel;
zoom: number; zoom: number;
color?: string; color?: string;
name: null | string; name: null | string;
chatMessage: string; chatMessage: string;
}>; };
// @public (undocumented) // @public (undocumented)
export const TldrawEditor: React_2.NamedExoticComponent<TldrawEditorProps>; export const TldrawEditor: React_2.NamedExoticComponent<TldrawEditorProps>;
@ -2224,27 +2207,26 @@ export type TLExternalContentSource = {
}; };
// @public (undocumented) // @public (undocumented)
export type TLGridComponent = ComponentType<{ export type TLGridProps = {
x: number; x: number;
y: number; y: number;
z: number; z: number;
size: number; size: number;
}>; };
// @public (undocumented) // @public (undocumented)
export type TLHandleComponent = ComponentType<{ export type TLHandleProps = {
shapeId: TLShapeId; shapeId: TLShapeId;
handle: TLHandle; handle: TLHandle;
zoom: number; zoom: number;
isCoarse: boolean; isCoarse: boolean;
className?: string; className?: string;
}>; };
// @public (undocumented) // @public (undocumented)
export type TLHandlesComponent = ComponentType<{ export type TLHandlesProps = {
className?: string;
children: any; children: any;
}>; };
// @public (undocumented) // @public (undocumented)
export type TLHistoryEntry = TLCommand | TLHistoryMark; export type TLHistoryEntry = TLCommand | TLHistoryMark;
@ -2258,12 +2240,9 @@ export type TLHistoryMark = {
}; };
// @public (undocumented) // @public (undocumented)
export type TLHoveredShapeIndicatorComponent = ComponentType<{ export type TLHoveredShapeIndicatorProps = {
shapeId: TLShapeId; shapeId: TLShapeId;
}>; };
// @public (undocumented)
export type TLInFrontOfTheCanvas = ComponentType<object>;
// @public (undocumented) // @public (undocumented)
export type TLInterruptEvent = (info: TLInterruptEventInfo) => void; export type TLInterruptEvent = (info: TLInterruptEventInfo) => void;
@ -2343,9 +2322,6 @@ export type TLOnRotateHandler<T extends TLShape> = TLEventChangeHandler<T>;
// @public (undocumented) // @public (undocumented)
export type TLOnRotateStartHandler<T extends TLShape> = TLEventStartHandler<T>; export type TLOnRotateStartHandler<T extends TLShape> = TLEventStartHandler<T>;
// @public (undocumented)
export type TLOnTheCanvas = ComponentType<object>;
// @public (undocumented) // @public (undocumented)
export type TLOnTranslateEndHandler<T extends TLShape> = TLEventChangeHandler<T>; export type TLOnTranslateEndHandler<T extends TLShape> = TLEventChangeHandler<T>;
@ -2442,25 +2418,25 @@ export type TLRotationSnapshot = {
}; };
// @public (undocumented) // @public (undocumented)
export type TLScribbleComponent = ComponentType<{ export type TLScribbleProps = {
scribble: TLScribble; scribble: TLScribble;
zoom: number; zoom: number;
color?: string; color?: string;
opacity?: number; opacity?: number;
className?: string; className?: string;
}>; };
// @public (undocumented) // @public (undocumented)
export type TLSelectionBackgroundComponent = React_3.ComponentType<{ export type TLSelectionBackgroundProps = {
bounds: Box; bounds: Box;
rotation: number; rotation: number;
}>; };
// @public (undocumented) // @public (undocumented)
export type TLSelectionForegroundComponent = ComponentType<{ export type TLSelectionForegroundProps = {
bounds: Box; bounds: Box;
rotation: number; rotation: number;
}>; };
// @public (undocumented) // @public (undocumented)
export type TLSelectionHandle = RotateCorner | SelectionCorner | SelectionEdge; export type TLSelectionHandle = RotateCorner | SelectionCorner | SelectionEdge;
@ -2495,12 +2471,12 @@ export interface TLSessionStateSnapshot {
} }
// @public (undocumented) // @public (undocumented)
export type TLShapeIndicatorComponent = React_3.ComponentType<{ export type TLShapeIndicatorProps = {
id: TLShapeId; shapeId: TLShapeId;
color?: string | undefined; color?: string | undefined;
opacity?: number; opacity?: number;
className?: string; className?: string;
}>; };
// @public (undocumented) // @public (undocumented)
export interface TLShapeUtilCanvasSvgDef { export interface TLShapeUtilCanvasSvgDef {
@ -2526,14 +2502,11 @@ export interface TLShapeUtilConstructor<T extends TLUnknownShape, U extends Shap
export type TLShapeUtilFlag<T> = (shape: T) => boolean; export type TLShapeUtilFlag<T> = (shape: T) => boolean;
// @public (undocumented) // @public (undocumented)
export type TLSnapIndicatorComponent = React_3.ComponentType<{ export type TLSnapIndicatorProps = {
className?: string; className?: string;
line: SnapIndicator; line: SnapIndicator;
zoom: number; zoom: number;
}>; };
// @public (undocumented)
export type TLSpinnerComponent = ComponentType<object>;
// @public (undocumented) // @public (undocumented)
export interface TLStateNodeConstructor { export interface TLStateNodeConstructor {
@ -2584,9 +2557,6 @@ export type TLStoreWithStatus = {
readonly error?: undefined; readonly error?: undefined;
}; };
// @public (undocumented)
export type TLSvgDefsComponent = React.ComponentType;
// @public (undocumented) // @public (undocumented)
export type TLSvgOptions = { export type TLSvgOptions = {
bounds: Box; bounds: Box;
@ -2672,6 +2642,34 @@ export function useContainer(): HTMLDivElement;
// @public (undocumented) // @public (undocumented)
export function useEditor(): Editor; export function useEditor(): Editor;
// @public (undocumented)
export function useEditorComponents(): Partial<{
Background: ComponentType | null;
SvgDefs: ComponentType | null;
Brush: ComponentType<TLBrushProps> | null;
ZoomBrush: ComponentType<TLBrushProps> | null;
ShapeIndicator: ComponentType<TLShapeIndicatorProps> | null;
Cursor: ComponentType<TLCursorProps> | null;
Canvas: ComponentType<TLCanvasComponentProps> | null;
CollaboratorBrush: ComponentType<TLBrushProps> | null;
CollaboratorCursor: ComponentType<TLCursorProps> | null;
CollaboratorHint: ComponentType<TLCollaboratorHintProps> | null;
CollaboratorShapeIndicator: ComponentType<TLShapeIndicatorProps> | null;
Grid: ComponentType<TLGridProps> | null;
Scribble: ComponentType<TLScribbleProps> | null;
CollaboratorScribble: ComponentType<TLScribbleProps> | null;
SnapIndicator: ComponentType<TLSnapIndicatorProps> | null;
Handles: ComponentType<TLHandlesProps> | null;
Handle: ComponentType<TLHandleProps> | null;
Spinner: ComponentType | null;
SelectionForeground: ComponentType<TLSelectionForegroundProps> | null;
SelectionBackground: ComponentType<TLSelectionBackgroundProps> | null;
HoveredShapeIndicator: ComponentType<TLHoveredShapeIndicatorProps> | null;
OnTheCanvas: ComponentType | null;
InFrontOfTheCanvas: ComponentType | null;
LoadingScreen: ComponentType | null;
} & ErrorComponents> & ErrorComponents;
// @public (undocumented) // @public (undocumented)
export function useIsCropping(shapeId: TLShapeId): boolean; export function useIsCropping(shapeId: TLShapeId): boolean;

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,6 @@ export {
type TldrawEditorBaseProps, type TldrawEditorBaseProps,
type TldrawEditorProps, type TldrawEditorProps,
} from './lib/TldrawEditor' } from './lib/TldrawEditor'
export { Canvas } from './lib/components/Canvas'
export { export {
ErrorBoundary, ErrorBoundary,
OptionalErrorBoundary, OptionalErrorBoundary,
@ -42,63 +41,53 @@ export {
} from './lib/components/ErrorBoundary' } from './lib/components/ErrorBoundary'
export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer' export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer'
export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer' export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer'
export { ShapeIndicator, type TLShapeIndicatorComponent } from './lib/components/ShapeIndicator' export { DefaultBackground } from './lib/components/default-components/DefaultBackground'
export { export { DefaultBrush, type TLBrushProps } from './lib/components/default-components/DefaultBrush'
DefaultBackground, export { DefaultCanvas } from './lib/components/default-components/DefaultCanvas'
type TLBackgroundComponent,
} from './lib/components/default-components/DefaultBackground'
export {
DefaultBrush,
type TLBrushComponent,
} from './lib/components/default-components/DefaultBrush'
export { export {
DefaultCollaboratorHint, DefaultCollaboratorHint,
type TLCollaboratorHintComponent, type TLCollaboratorHintProps,
} from './lib/components/default-components/DefaultCollaboratorHint' } from './lib/components/default-components/DefaultCollaboratorHint'
export { export {
DefaultCursor, DefaultCursor,
type TLCursorComponent, type TLCursorProps,
} from './lib/components/default-components/DefaultCursor' } from './lib/components/default-components/DefaultCursor'
export { DefaultErrorFallback } from './lib/components/default-components/DefaultErrorFallback' export { DefaultErrorFallback } from './lib/components/default-components/DefaultErrorFallback'
export { DefaultGrid, type TLGridComponent } from './lib/components/default-components/DefaultGrid' export { DefaultGrid, type TLGridProps } from './lib/components/default-components/DefaultGrid'
export { export {
DefaultHandle, DefaultHandle,
type TLHandleComponent, type TLHandleProps,
} from './lib/components/default-components/DefaultHandle' } from './lib/components/default-components/DefaultHandle'
export { export {
DefaultHandles, DefaultHandles,
type TLHandlesComponent, type TLHandlesProps,
} from './lib/components/default-components/DefaultHandles' } from './lib/components/default-components/DefaultHandles'
export { export {
DefaultHoveredShapeIndicator, DefaultHoveredShapeIndicator,
type TLHoveredShapeIndicatorComponent, type TLHoveredShapeIndicatorProps,
} from './lib/components/default-components/DefaultHoveredShapeIndicator' } from './lib/components/default-components/DefaultHoveredShapeIndicator'
export { type TLInFrontOfTheCanvas } from './lib/components/default-components/DefaultInFrontOfTheCanvas'
export { type TLOnTheCanvas } from './lib/components/default-components/DefaultOnTheCanvas'
export { export {
DefaultScribble, DefaultScribble,
type TLScribbleComponent, type TLScribbleProps,
} from './lib/components/default-components/DefaultScribble' } from './lib/components/default-components/DefaultScribble'
export { export {
DefaultSelectionBackground, DefaultSelectionBackground,
type TLSelectionBackgroundComponent, type TLSelectionBackgroundProps,
} from './lib/components/default-components/DefaultSelectionBackground' } from './lib/components/default-components/DefaultSelectionBackground'
export { export {
DefaultSelectionForeground, DefaultSelectionForeground,
type TLSelectionForegroundComponent, type TLSelectionForegroundProps,
} from './lib/components/default-components/DefaultSelectionForeground' } from './lib/components/default-components/DefaultSelectionForeground'
export {
DefaultShapeIndicator,
type TLShapeIndicatorProps,
} from './lib/components/default-components/DefaultShapeIndicator'
export { export {
DefaultSnapIndicator, DefaultSnapIndicator,
type TLSnapIndicatorComponent, type TLSnapIndicatorProps,
} from './lib/components/default-components/DefaultSnapIndictor' } from './lib/components/default-components/DefaultSnapIndictor'
export { export { DefaultSpinner } from './lib/components/default-components/DefaultSpinner'
DefaultSpinner, export { DefaultSvgDefs } from './lib/components/default-components/DefaultSvgDefs'
type TLSpinnerComponent,
} from './lib/components/default-components/DefaultSpinner'
export {
DefaultSvgDefs,
type TLSvgDefsComponent,
} from './lib/components/default-components/DefaultSvgDefs'
export { export {
TAB_ID, TAB_ID,
createSessionStateSnapshotSignal, createSessionStateSnapshotSignal,
@ -256,6 +245,7 @@ export { type TLResizeHandle, type TLSelectionHandle } from './lib/editor/types/
export { ContainerProvider, useContainer } from './lib/hooks/useContainer' export { ContainerProvider, useContainer } from './lib/hooks/useContainer'
export { getCursor } from './lib/hooks/useCursor' export { getCursor } from './lib/hooks/useCursor'
export { EditorContext, useEditor } from './lib/hooks/useEditor' export { EditorContext, useEditor } from './lib/hooks/useEditor'
export { useEditorComponents } from './lib/hooks/useEditorComponents'
export type { TLEditorComponents } from './lib/hooks/useEditorComponents' export type { TLEditorComponents } from './lib/hooks/useEditorComponents'
export { useShallowArrayIdentity, useShallowObjectIdentity } from './lib/hooks/useIdentity' export { useShallowArrayIdentity, useShallowObjectIdentity } from './lib/hooks/useIdentity'
export { useIsCropping } from './lib/hooks/useIsCropping' export { useIsCropping } from './lib/hooks/useIsCropping'

View file

@ -11,7 +11,6 @@ import React, {
} from 'react' } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { Canvas } from './components/Canvas'
import { OptionalErrorBoundary } from './components/ErrorBoundary' import { OptionalErrorBoundary } from './components/ErrorBoundary'
import { DefaultErrorFallback } from './components/default-components/DefaultErrorFallback' import { DefaultErrorFallback } from './components/default-components/DefaultErrorFallback'
import { DefaultLoadingScreen } from './components/default-components/DefaultLoadingScreen' import { DefaultLoadingScreen } from './components/default-components/DefaultLoadingScreen'
@ -309,6 +308,8 @@ function TldrawEditorWithReadyStore({
() => editor?.getCrashingError() ?? null () => editor?.getCrashingError() ?? null
) )
const { Canvas } = useEditorComponents()
if (!editor) { if (!editor) {
return null return null
} }
@ -331,7 +332,7 @@ function TldrawEditorWithReadyStore({
) : ( ) : (
<EditorContext.Provider value={editor}> <EditorContext.Provider value={editor}>
<Layout autoFocus={autoFocus} onMount={onMount}> <Layout autoFocus={autoFocus} onMount={onMount}>
{children} {children ?? (Canvas ? <Canvas /> : null)}
</Layout> </Layout>
</EditorContext.Provider> </EditorContext.Provider>
)} )}
@ -356,7 +357,7 @@ function Layout({
useFocusEvents(autoFocus) useFocusEvents(autoFocus)
useOnMount(onMount) useOnMount(onMount)
return children ?? <Canvas /> return <>{children}</>
} }
function Crash({ crashingError }: { crashingError: unknown }): null { function Crash({ crashingError }: { crashingError: unknown }): null {

View file

@ -144,7 +144,7 @@ const Collaborator = track(function Collaborator({
<CollaboratorShapeIndicator <CollaboratorShapeIndicator
className="tl-collaborator__shape-indicator" className="tl-collaborator__shape-indicator"
key={userId + '_' + shapeId} key={userId + '_' + shapeId}
id={shapeId} shapeId={shapeId}
color={color} color={color}
opacity={0.5} opacity={0.5}
/> />

View file

@ -1,8 +1,3 @@
import { ComponentType } from 'react'
/** @public */
export type TLBackgroundComponent = ComponentType
/** @public */ /** @public */
export function DefaultBackground() { export function DefaultBackground() {
return <div className="tl-background" /> return <div className="tl-background" />

View file

@ -1,18 +1,18 @@
import { BoxModel } from '@tldraw/tlschema' import { BoxModel } from '@tldraw/tlschema'
import { ComponentType, useRef } from 'react' import { useRef } from 'react'
import { useTransform } from '../../hooks/useTransform' import { useTransform } from '../../hooks/useTransform'
import { toDomPrecision } from '../../primitives/utils' import { toDomPrecision } from '../../primitives/utils'
/** @public */ /** @public */
export type TLBrushComponent = ComponentType<{ export type TLBrushProps = {
brush: BoxModel brush: BoxModel
color?: string color?: string
opacity?: number opacity?: number
className?: string className?: string
}> }
/** @public */ /** @public */
export const DefaultBrush: TLBrushComponent = ({ brush, color, opacity, className }) => { export const DefaultBrush = ({ brush, color, opacity, className }: TLBrushProps) => {
const rSvg = useRef<SVGSVGElement>(null) const rSvg = useRef<SVGSVGElement>(null)
useTransform(rSvg, brush.x, brush.y) useTransform(rSvg, brush.x, brush.y)

View file

@ -3,27 +3,29 @@ 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 React from 'react' import React from 'react'
import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS } from '../constants' import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS } from '../../constants'
import { useCanvasEvents } from '../hooks/useCanvasEvents' import { useCanvasEvents } from '../../hooks/useCanvasEvents'
import { useCoarsePointer } from '../hooks/useCoarsePointer' import { useCoarsePointer } from '../../hooks/useCoarsePointer'
import { useDocumentEvents } from '../hooks/useDocumentEvents' import { useDocumentEvents } from '../../hooks/useDocumentEvents'
import { useEditor } from '../hooks/useEditor' import { useEditor } from '../../hooks/useEditor'
import { useEditorComponents } from '../hooks/useEditorComponents' import { useEditorComponents } from '../../hooks/useEditorComponents'
import { useFixSafariDoubleTapZoomPencilEvents } from '../hooks/useFixSafariDoubleTapZoomPencilEvents' import { useFixSafariDoubleTapZoomPencilEvents } from '../../hooks/useFixSafariDoubleTapZoomPencilEvents'
import { useGestureEvents } from '../hooks/useGestureEvents' import { useGestureEvents } from '../../hooks/useGestureEvents'
import { useHandleEvents } from '../hooks/useHandleEvents' import { useHandleEvents } from '../../hooks/useHandleEvents'
import { useScreenBounds } from '../hooks/useScreenBounds' import { useScreenBounds } from '../../hooks/useScreenBounds'
import { Mat } from '../primitives/Mat' import { Mat } from '../../primitives/Mat'
import { Vec } from '../primitives/Vec' import { Vec } from '../../primitives/Vec'
import { toDomPrecision } from '../primitives/utils' import { toDomPrecision } from '../../primitives/utils'
import { debugFlags } from '../utils/debug-flags' import { debugFlags } from '../../utils/debug-flags'
import { GeometryDebuggingView } from './GeometryDebuggingView' import { GeometryDebuggingView } from '../GeometryDebuggingView'
import { LiveCollaborators } from './LiveCollaborators' import { LiveCollaborators } from '../LiveCollaborators'
import { Shape } from './Shape' import { Shape } from '../Shape'
import { ShapeIndicator } from './ShapeIndicator'
/** @public */ /** @public */
export function Canvas({ className }: { className?: string }) { export type TLCanvasComponentProps = { className?: string }
/** @public */
export function DefaultCanvas({ className }: TLCanvasComponentProps) {
const editor = useEditor() const editor = useEditor()
const { Background, SvgDefs } = useEditorComponents() const { Background, SvgDefs } = useEditorComponents()
@ -378,12 +380,19 @@ function SelectedIdIndicators() {
[editor] [editor]
) )
const { ShapeIndicator } = useEditorComponents()
if (!ShapeIndicator) return null
if (!shouldDisplay) return null if (!shouldDisplay) return null
return ( return (
<> <>
{selectedShapeIds.map((id) => ( {selectedShapeIds.map((id) => (
<ShapeIndicator key={id + '_indicator'} className="tl-user-indicator__selected" id={id} /> <ShapeIndicator
key={id + '_indicator'}
className="tl-user-indicator__selected"
shapeId={id}
/>
))} ))}
</> </>
) )
@ -413,15 +422,17 @@ const HoveredShapeIndicator = function HoveredShapeIndicator() {
const HintedShapeIndicator = track(function HintedShapeIndicator() { const HintedShapeIndicator = track(function HintedShapeIndicator() {
const editor = useEditor() const editor = useEditor()
const { ShapeIndicator } = useEditorComponents()
const ids = dedupe(editor.getHintingShapeIds()) const ids = dedupe(editor.getHintingShapeIds())
if (!ids.length) return null if (!ids.length) return null
if (!ShapeIndicator) return null
return ( return (
<> <>
{ids.map((id) => ( {ids.map((id) => (
<ShapeIndicator className="tl-user-indicator__hint" id={id} key={id + '_hinting'} /> <ShapeIndicator className="tl-user-indicator__hint" shapeId={id} key={id + '_hinting'} />
))} ))}
</> </>
) )

View file

@ -1,30 +1,30 @@
import { VecModel } from '@tldraw/tlschema' import { VecModel } from '@tldraw/tlschema'
import classNames from 'classnames' import classNames from 'classnames'
import { ComponentType, useRef } from 'react' import { useRef } from 'react'
import { useTransform } from '../../hooks/useTransform' import { useTransform } from '../../hooks/useTransform'
import { Box } from '../../primitives/Box' import { Box } from '../../primitives/Box'
import { Vec } from '../../primitives/Vec' import { Vec } from '../../primitives/Vec'
import { clamp } from '../../primitives/utils' import { clamp } from '../../primitives/utils'
/** @public */ /** @public */
export type TLCollaboratorHintComponent = ComponentType<{ export type TLCollaboratorHintProps = {
className?: string className?: string
point: VecModel point: VecModel
viewport: Box viewport: Box
zoom: number zoom: number
opacity?: number opacity?: number
color: string color: string
}> }
/** @public */ /** @public */
export const DefaultCollaboratorHint: TLCollaboratorHintComponent = ({ export function DefaultCollaboratorHint({
className, className,
zoom, zoom,
point, point,
color, color,
viewport, viewport,
opacity = 1, opacity = 1,
}) => { }: TLCollaboratorHintProps) {
const rSvg = useRef<SVGSVGElement>(null) const rSvg = useRef<SVGSVGElement>(null)
useTransform( useTransform(

View file

@ -1,19 +1,27 @@
import { VecModel } from '@tldraw/tlschema' import { VecModel } from '@tldraw/tlschema'
import classNames from 'classnames' import classNames from 'classnames'
import { ComponentType, memo, useRef } from 'react' import { memo, useRef } from 'react'
import { useTransform } from '../../hooks/useTransform' import { useTransform } from '../../hooks/useTransform'
/** @public */ /** @public */
export type TLCursorComponent = ComponentType<{ export type TLCursorProps = {
className?: string className?: string
point: VecModel | null point: VecModel | null
zoom: number zoom: number
color?: string color?: string
name: string | null name: string | null
chatMessage: string chatMessage: string
}> }
const _Cursor: TLCursorComponent = ({ className, zoom, point, color, name, chatMessage }) => { /** @public */
export const DefaultCursor = memo(function DefaultCursor({
className,
zoom,
point,
color,
name,
chatMessage,
}: TLCursorProps) {
const rCursor = useRef<HTMLDivElement>(null) const rCursor = useRef<HTMLDivElement>(null)
useTransform(rCursor, point?.x, point?.y, 1 / zoom) useTransform(rCursor, point?.x, point?.y, 1 / zoom)
@ -44,7 +52,4 @@ const _Cursor: TLCursorComponent = ({ className, zoom, point, color, name, chatM
)} )}
</div> </div>
) )
} })
/** @public */
export const DefaultCursor = memo(_Cursor)

View file

@ -3,9 +3,9 @@ import classNames from 'classnames'
import { ComponentType, useEffect, useLayoutEffect, useRef, useState } from 'react' import { ComponentType, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { Editor } from '../../editor/Editor' import { Editor } from '../../editor/Editor'
import { EditorContext } from '../../hooks/useEditor' import { EditorContext } from '../../hooks/useEditor'
import { useEditorComponents } from '../../hooks/useEditorComponents'
import { hardResetEditor } from '../../utils/hardResetEditor' import { hardResetEditor } from '../../utils/hardResetEditor'
import { refreshPage } from '../../utils/refreshPage' import { refreshPage } from '../../utils/refreshPage'
import { Canvas } from '../Canvas'
import { ErrorBoundary } from '../ErrorBoundary' import { ErrorBoundary } from '../ErrorBoundary'
const BASE_ERROR_URL = 'https://github.com/tldraw/tldraw/issues/new' const BASE_ERROR_URL = 'https://github.com/tldraw/tldraw/issues/new'
@ -23,6 +23,8 @@ export const DefaultErrorFallback: TLErrorFallbackComponent = ({ error, editor }
const [didCopy, setDidCopy] = useState(false) const [didCopy, setDidCopy] = useState(false)
const [shouldShowResetConfirmation, setShouldShowResetConfirmation] = useState(false) const [shouldShowResetConfirmation, setShouldShowResetConfirmation] = useState(false)
const { Canvas } = useEditorComponents()
const errorMessage = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
const errorStack = error instanceof Error ? error.stack : null const errorStack = error instanceof Error ? error.stack : null
@ -135,9 +137,7 @@ My browser: ${navigator.userAgent}`
// a plain grey background. // a plain grey background.
<ErrorBoundary onError={noop} fallback={() => null}> <ErrorBoundary onError={noop} fallback={() => null}>
<EditorContext.Provider value={editor}> <EditorContext.Provider value={editor}>
<div className="tl-overlay tl-error-boundary__canvas"> <div className="tl-overlay tl-error-boundary__canvas">{Canvas ? <Canvas /> : null}</div>
<Canvas />
</div>
</EditorContext.Provider> </EditorContext.Provider>
</ErrorBoundary> </ErrorBoundary>
)} )}

View file

@ -1,17 +1,16 @@
import { modulate } from '@tldraw/utils' import { modulate } from '@tldraw/utils'
import { ComponentType } from 'react'
import { GRID_STEPS } from '../../constants' import { GRID_STEPS } from '../../constants'
/** @public */ /** @public */
export type TLGridComponent = ComponentType<{ export type TLGridProps = {
x: number x: number
y: number y: number
z: number z: number
size: number size: number
}> }
/** @public */ /** @public */
export const DefaultGrid: TLGridComponent = ({ x, y, z, size }) => { export function DefaultGrid({ x, y, z, size }: TLGridProps) {
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>

View file

@ -1,19 +1,18 @@
import { TLHandle, TLShapeId } from '@tldraw/tlschema' import { TLHandle, TLShapeId } from '@tldraw/tlschema'
import classNames from 'classnames' import classNames from 'classnames'
import { ComponentType } from 'react'
import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS } from '../../constants' import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS } from '../../constants'
/** @public */ /** @public */
export type TLHandleComponent = ComponentType<{ export type TLHandleProps = {
shapeId: TLShapeId shapeId: TLShapeId
handle: TLHandle handle: TLHandle
zoom: number zoom: number
isCoarse: boolean isCoarse: boolean
className?: string className?: string
}> }
/** @public */ /** @public */
export const DefaultHandle: TLHandleComponent = ({ handle, isCoarse, className, zoom }) => { export function DefaultHandle({ handle, isCoarse, className, zoom }: TLHandleProps) {
const bgRadius = (isCoarse ? COARSE_HANDLE_RADIUS : HANDLE_RADIUS) / zoom const bgRadius = (isCoarse ? COARSE_HANDLE_RADIUS : HANDLE_RADIUS) / zoom
const fgRadius = (handle.type === 'create' && isCoarse ? 3 : 4) / zoom const fgRadius = (handle.type === 'create' && isCoarse ? 3 : 4) / zoom

View file

@ -1,12 +1,9 @@
import { ComponentType } from 'react'
/** @public */ /** @public */
export type TLHandlesComponent = ComponentType<{ export type TLHandlesProps = {
className?: string
children: any children: any
}> }
/** @public */ /** @public */
export const DefaultHandles: TLHandlesComponent = ({ children }) => { export const DefaultHandles = ({ children }: TLHandlesProps) => {
return <svg className="tl-user-handles tl-overlays__item">{children}</svg> return <svg className="tl-user-handles tl-overlays__item">{children}</svg>
} }

View file

@ -1,13 +1,14 @@
import { TLShapeId } from '@tldraw/tlschema' import { TLShapeId } from '@tldraw/tlschema'
import { ComponentType } from 'react' import { useEditorComponents } from '../../hooks/useEditorComponents'
import { ShapeIndicator } from '../ShapeIndicator'
/** @public */ /** @public */
export type TLHoveredShapeIndicatorComponent = ComponentType<{ export type TLHoveredShapeIndicatorProps = {
shapeId: TLShapeId shapeId: TLShapeId
}> }
/** @public */ /** @public */
export const DefaultHoveredShapeIndicator: TLHoveredShapeIndicatorComponent = ({ shapeId }) => { export function DefaultHoveredShapeIndicator({ shapeId }: TLHoveredShapeIndicatorProps) {
return <ShapeIndicator className="tl-user-indicator__hovered" id={shapeId} /> const { ShapeIndicator } = useEditorComponents()
if (!ShapeIndicator) return null
return <ShapeIndicator className="tl-user-indicator__hovered" shapeId={shapeId} />
} }

View file

@ -1,4 +0,0 @@
import { ComponentType } from 'react'
/** @public */
export type TLInFrontOfTheCanvas = ComponentType<object>

View file

@ -1,10 +1,6 @@
import { ComponentType } from 'react'
import { LoadingScreen } from '../../TldrawEditor' import { LoadingScreen } from '../../TldrawEditor'
/** @public */ /** @public */
export type TLLoadingScreenComponent = ComponentType<object> export const DefaultLoadingScreen = () => {
/** @public */
export const DefaultLoadingScreen: TLLoadingScreenComponent = () => {
return <LoadingScreen>Connecting...</LoadingScreen> return <LoadingScreen>Connecting...</LoadingScreen>
} }

View file

@ -1,4 +0,0 @@
import { ComponentType } from 'react'
/** @public */
export type TLOnTheCanvas = ComponentType<object>

View file

@ -1,25 +1,18 @@
import { TLScribble } from '@tldraw/tlschema' import { TLScribble } from '@tldraw/tlschema'
import classNames from 'classnames' import classNames from 'classnames'
import { ComponentType } from 'react'
import { getSvgPathFromPoints } from '../../utils/getSvgPathFromPoints' import { getSvgPathFromPoints } from '../../utils/getSvgPathFromPoints'
/** @public */ /** @public */
export type TLScribbleComponent = ComponentType<{ export type TLScribbleProps = {
scribble: TLScribble scribble: TLScribble
zoom: number zoom: number
color?: string color?: string
opacity?: number opacity?: number
className?: string className?: string
}> }
/** @public */ /** @public */
export const DefaultScribble: TLScribbleComponent = ({ export function DefaultScribble({ scribble, zoom, color, opacity, className }: TLScribbleProps) {
scribble,
zoom,
color,
opacity,
className,
}) => {
if (!scribble.points.length) return null if (!scribble.points.length) return null
return ( return (

View file

@ -4,16 +4,13 @@ import { Box } from '../../primitives/Box'
import { toDomPrecision } from '../../primitives/utils' import { toDomPrecision } from '../../primitives/utils'
/** @public */ /** @public */
export type TLSelectionBackgroundComponent = React.ComponentType<{ export type TLSelectionBackgroundProps = {
bounds: Box bounds: Box
rotation: number rotation: number
}> }
/** @public */ /** @public */
export const DefaultSelectionBackground: TLSelectionBackgroundComponent = ({ export function DefaultSelectionBackground({ bounds, rotation }: TLSelectionBackgroundProps) {
bounds,
rotation,
}) => {
const rDiv = React.useRef<HTMLDivElement>(null) const rDiv = React.useRef<HTMLDivElement>(null)
useTransform(rDiv, bounds.x, bounds.y, 1, rotation) useTransform(rDiv, bounds.x, bounds.y, 1, rotation)

View file

@ -1,22 +1,19 @@
import { useValue } from '@tldraw/state' import { useValue } from '@tldraw/state'
import classNames from 'classnames' import classNames from 'classnames'
import { ComponentType, useRef } from 'react' import { useRef } from 'react'
import { useEditor } from '../../hooks/useEditor' import { useEditor } from '../../hooks/useEditor'
import { useTransform } from '../../hooks/useTransform' import { useTransform } from '../../hooks/useTransform'
import { Box } from '../../primitives/Box' import { Box } from '../../primitives/Box'
import { toDomPrecision } from '../../primitives/utils' import { toDomPrecision } from '../../primitives/utils'
/** @public */ /** @public */
export type TLSelectionForegroundComponent = ComponentType<{ export type TLSelectionForegroundProps = {
bounds: Box bounds: Box
rotation: number rotation: number
}> }
/** @public */ /** @public */
export const DefaultSelectionForeground: TLSelectionForegroundComponent = ({ export function DefaultSelectionForeground({ bounds, rotation }: TLSelectionForegroundProps) {
bounds,
rotation,
}) => {
const editor = useEditor() const editor = useEditor()
const rSvg = useRef<SVGSVGElement>(null) const rSvg = useRef<SVGSVGElement>(null)

View file

@ -1,12 +1,12 @@
import { useStateTracking, useValue } from '@tldraw/state' import { useStateTracking, useValue } from '@tldraw/state'
import { TLShape, TLShapeId } from '@tldraw/tlschema' import { TLShape, TLShapeId } from '@tldraw/tlschema'
import classNames from 'classnames' import classNames from 'classnames'
import * as React from 'react' import { memo } from 'react'
import type { Editor } from '../editor/Editor' import type { Editor } from '../../editor/Editor'
import { ShapeUtil } from '../editor/shapes/ShapeUtil' import { ShapeUtil } from '../../editor/shapes/ShapeUtil'
import { useEditor } from '../hooks/useEditor' import { useEditor } from '../../hooks/useEditor'
import { useEditorComponents } from '../hooks/useEditorComponents' import { useEditorComponents } from '../../hooks/useEditorComponents'
import { OptionalErrorBoundary } from './ErrorBoundary' import { OptionalErrorBoundary } from '../ErrorBoundary'
class ShapeWithPropsEquality { class ShapeWithPropsEquality {
constructor(public shape: TLShape | undefined) {} constructor(public shape: TLShape | undefined) {}
@ -51,24 +51,30 @@ const InnerIndicator = ({ editor, id }: { editor: Editor; id: TLShapeId }) => {
} }
/** @public */ /** @public */
export type TLShapeIndicatorComponent = React.ComponentType<{ export type TLShapeIndicatorProps = {
id: TLShapeId shapeId: TLShapeId
color?: string | undefined color?: string | undefined
opacity?: number opacity?: number
className?: string className?: string
}> }
const _ShapeIndicator: TLShapeIndicatorComponent = ({ id, className, color, opacity }) => { /** @public */
export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
shapeId,
className,
color,
opacity,
}: TLShapeIndicatorProps) {
const editor = useEditor() const editor = useEditor()
const transform = useValue( const transform = useValue(
'transform', 'transform',
() => { () => {
const pageTransform = editor.getShapePageTransform(id) const pageTransform = editor.getShapePageTransform(shapeId)
if (!pageTransform) return '' if (!pageTransform) return ''
return pageTransform.toCssString() return pageTransform.toCssString()
}, },
[editor, id] [editor, shapeId]
) )
return ( return (
@ -79,11 +85,8 @@ const _ShapeIndicator: TLShapeIndicatorComponent = ({ id, className, color, opac
stroke={color ?? 'var(--color-selected)'} stroke={color ?? 'var(--color-selected)'}
opacity={opacity} opacity={opacity}
> >
<InnerIndicator editor={editor} id={id} /> <InnerIndicator editor={editor} id={shapeId} />
</g> </g>
</svg> </svg>
) )
} })
/** @public */
export const ShapeIndicator = React.memo(_ShapeIndicator)

View file

@ -154,14 +154,14 @@ function GapsSnapIndicator({ gaps, direction, zoom }: { zoom: number } & GapsSna
} }
/** @public */ /** @public */
export type TLSnapIndicatorComponent = React.ComponentType<{ export type TLSnapIndicatorProps = {
className?: string className?: string
line: SnapIndicator line: SnapIndicator
zoom: number zoom: number
}> }
/** @public */ /** @public */
export const DefaultSnapIndicator: TLSnapIndicatorComponent = ({ className, line, zoom }) => { export function DefaultSnapIndicator({ className, line, zoom }: TLSnapIndicatorProps) {
return ( return (
<svg className={classNames('tl-overlays__item', className)}> <svg className={classNames('tl-overlays__item', className)}>
{line.type === 'points' ? ( {line.type === 'points' ? (

View file

@ -1,10 +1,5 @@
import { ComponentType } from 'react'
/** @public */ /** @public */
export type TLSpinnerComponent = ComponentType<object> export function DefaultSpinner() {
/** @public */
export const DefaultSpinner: TLSpinnerComponent = () => {
return ( return (
<svg width={16} height={16} viewBox="0 0 16 16"> <svg width={16} height={16} viewBox="0 0 16 16">
<g strokeWidth={2} fill="none" fillRule="evenodd"> <g strokeWidth={2} fill="none" fillRule="evenodd">

View file

@ -1,6 +1,3 @@
/** @public */
export type TLSvgDefsComponent = React.ComponentType
/** @public */ /** @public */
export const DefaultSvgDefs = () => { export const DefaultSvgDefs = () => {
return null return null

View file

@ -53,6 +53,7 @@ import {
getIndicesBetween, getIndicesBetween,
getOwnProperty, getOwnProperty,
hasOwnProperty, hasOwnProperty,
objectMapValues,
sortById, sortById,
sortByIndex, sortByIndex,
structuredClone, structuredClone,
@ -6723,23 +6724,27 @@ export class Editor extends EventEmitter<TLEventMap> {
let remaining = duration let remaining = duration
let t: number let t: number
type FromTo = { prop: string; from: number; to: number } type ShapeAnimation = {
type ShapeAnimation = { partial: TLShapePartial; values: FromTo[] } partial: TLShapePartial
values: { prop: string; from: number; to: number }[]
}
const animations: ShapeAnimation[] = [] const animations: ShapeAnimation[] = []
partials.forEach((partial) => { let partial: TLShapePartial | null | undefined, result: ShapeAnimation
if (!partial) return for (let i = 0, n = partials.length; i < n; i++) {
partial = partials[i]
if (!partial) continue
const result: ShapeAnimation = { result = {
partial, partial,
values: [], values: [],
} }
const shape = this.getShape(partial.id)! const shape = this.getShape(partial.id)!
if (!shape) continue
if (!shape) return // We only support animations for certain props
for (const key of ['x', 'y', 'rotation'] as const) { for (const key of ['x', 'y', 'rotation'] as const) {
if (partial[key] !== undefined && shape[key] !== partial[key]) { if (partial[key] !== undefined && shape[key] !== partial[key]) {
result.values.push({ prop: key, from: shape[key], to: partial[key] as number }) result.values.push({ prop: key, from: shape[key], to: partial[key] as number })
@ -6748,7 +6753,7 @@ export class Editor extends EventEmitter<TLEventMap> {
animations.push(result) animations.push(result)
this.animatingShapes.set(shape.id, animationId) this.animatingShapes.set(shape.id, animationId)
}) }
let value: ShapeAnimation let value: ShapeAnimation
@ -6773,28 +6778,27 @@ export class Editor extends EventEmitter<TLEventMap> {
const { animatingShapes } = this const { animatingShapes } = this
try { const updates: TLShapePartial[] = []
const tPartials: TLShapePartial[] = []
for (let i = 0; i < animations.length; i++) { let animationIdForShape: string | undefined
value = animations[i] for (let i = 0, n = animations.length; i < n; i++) {
value = animations[i]
// Is the animation for this shape still active?
animationIdForShape = animatingShapes.get(value.partial.id)
if (animationIdForShape !== animationId) continue
if (animatingShapes.get(value.partial.id) === animationId) { // Create the update
tPartials.push({ updates.push({
id: value.partial.id, id: value.partial.id,
type: value.partial.type, type: value.partial.type,
...value.values.reduce((acc, { prop, from, to }) => { ...value.values.reduce((acc, { prop, from, to }) => {
acc[prop] = from + (to - from) * t acc[prop] = from + (to - from) * t
return acc return acc
}, {} as any), }, {} as any),
}) })
}
}
this._updateShapes(tPartials, { squashing: true })
} catch (e) {
// noop
} }
this._updateShapes(updates, { squashing: true })
} }
this.addListener('tick', handleTick) this.addListener('tick', handleTick)
@ -6968,20 +6972,24 @@ export class Editor extends EventEmitter<TLEventMap> {
partials: (TLShapePartial<T> | null | undefined)[], partials: (TLShapePartial<T> | null | undefined)[],
historyOptions?: TLCommandHistoryOptions historyOptions?: TLCommandHistoryOptions
) { ) {
let compactedPartials = compact(partials) const compactedPartials: TLShapePartial<T>[] = Array(partials.length)
if (this.animatingShapes.size > 0) {
compactedPartials.forEach((p) => this.animatingShapes.delete(p.id)) for (let i = 0, n = partials.length; i < n; i++) {
const partial = partials[i]
if (!partial) continue
// Get the current shape referenced by the partial
const shape = this.getShape(partial.id)
if (!shape) continue
// If the shape is locked and we're not setting isLocked to true, continue
if (this.isShapeOrAncestorLocked(shape) && !Object.hasOwn(partial, 'isLocked')) continue
// Remove any animating shapes from the list of partials
this.animatingShapes.delete(partial.id)
compactedPartials.push(partial)
} }
compactedPartials = compactedPartials.filter((p) => {
const shape = this.getShape(p.id)
if (!shape) return false
// Only allow changes to unlocked shapes or changes to the isLocked property (otherwise we cannot unlock a shape)
if (this.isShapeOrAncestorLocked(shape) && !Object.hasOwn(p, 'isLocked')) return false
return true
})
this._updateShapes(compactedPartials, historyOptions) this._updateShapes(compactedPartials, historyOptions)
return this return this
} }
@ -6995,45 +7003,49 @@ export class Editor extends EventEmitter<TLEventMap> {
) => { ) => {
if (this.getInstanceState().isReadonly) return null if (this.getInstanceState().isReadonly) return null
const partials = compact(_partials) const snapshots: Record<string, TLShape> = {}
const updates: Record<string, TLShape> = {}
const snapshots = Object.fromEntries( let shape: TLShape | undefined
compact(partials.map(({ id }) => this.getShape(id))).map((shape) => { let updated: TLShape
return [shape.id, shape]
})
)
if (partials.length <= 0) return null for (let i = 0, n = _partials.length; i < n; i++) {
const partial = _partials[i]
// Skip nullish partials (sometimes created by map fns returning undefined)
if (!partial) continue
const updated = compact( // Get the current shape referenced by the partial
partials.map((partial) => { // If there is no current shape, we'll skip this update
const prev = snapshots[partial.id] shape = this.getShape(partial.id)
if (!prev) return null if (!shape) continue
return applyPartialToShape(prev, partial)
})
)
const updates = Object.fromEntries(updated.map((shape) => [shape.id, shape])) // Get the updated version of the shape
// If the update had no effect, we'll skip this update
updated = applyPartialToShape(shape, partial)
if (updated === shape) continue
snapshots[shape.id] = shape
updates[shape.id] = updated
}
return { data: { snapshots, updates }, ...historyOptions } return { data: { snapshots, updates }, ...historyOptions }
}, },
{ {
do: ({ updates }) => { do: ({ updates }) => {
// Iterate through array; if any shape has an onUpdate handler, call it // Iterate through array; if any shape has an onBeforeUpdate handler, call it
// and, if the handler returns a new shape, replace the old shape with // and, if the handler returns a new shape, replace the old shape with
// the new one. This is used for example when repositioning a text shape // the new one. This is used for example when repositioning a text shape
// based on its new text content. // based on its new text content.
const result = Object.values(updates) this.store.put(
for (let i = 0; i < result.length; i++) { objectMapValues(updates).map((shape) => {
const shape = result[i] const current = this.store.get(shape.id)
const current = this.store.get(shape.id) if (current) {
if (!current) continue const next = this.getShapeUtil(shape).onBeforeUpdate?.(current, shape)
const next = this.getShapeUtil(shape).onBeforeUpdate?.(current, shape) if (next) return next
if (next) { }
result[i] = next return shape
} })
} )
this.store.put(result)
}, },
undo: ({ snapshots }) => { undo: ({ snapshots }) => {
this.store.put(Object.values(snapshots)) this.store.put(Object.values(snapshots))
@ -8954,44 +8966,34 @@ function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) {
function applyPartialToShape<T extends TLShape>(prev: T, partial?: TLShapePartial<T>): T { function applyPartialToShape<T extends TLShape>(prev: T, partial?: TLShapePartial<T>): T {
if (!partial) return prev if (!partial) return prev
let next = null as null | T let next = null as null | T
for (const [k, v] of Object.entries(partial)) { const entries = Object.entries(partial)
for (let i = 0, n = entries.length; i < n; i++) {
const [k, v] = entries[i]
if (v === undefined) continue if (v === undefined) continue
switch (k) {
case 'id':
case 'type':
continue
default: {
if (v !== (prev as any)[k]) {
if (!next) {
next = { ...prev }
}
if (k === 'props') { // Is the key a special key? We don't update those
// props property if (k === 'id' || k === 'type' || k === 'typeName') continue
const nextProps = { ...prev.props } as JsonObject
for (const [propKey, propValue] of Object.entries(v as object)) { // Is the value the same as it was before?
if (propValue !== undefined) { if (v === (prev as any)[k]) continue
nextProps[propKey] = propValue
} // There's a new value, so create the new shape if we haven't already (should we be cloning this?)
} if (!next) next = { ...prev }
next!.props = nextProps
} else if (k === 'meta') { // for props / meta properties, we support updates with partials of this object
// meta property if (k === 'props' || k === 'meta') {
const nextMeta = { ...prev.meta } as JsonObject next[k] = { ...prev[k] } as JsonObject
for (const [metaKey, metaValue] of Object.entries(v as object)) { for (const [nextKey, nextValue] of Object.entries(v as object)) {
if (metaValue !== undefined) { if (nextValue !== undefined) {
nextMeta[metaKey] = metaValue ;(next[k] as JsonObject)[nextKey] = nextValue
}
}
next!.meta = nextMeta
} else {
// base property
;(next as any)[k] = v
}
} }
} }
continue
} }
}
return next ?? prev // base property
;(next as any)[k] = v
}
if (!next) return prev
return next
} }

View file

@ -1,80 +1,80 @@
import { createContext, useContext, useMemo } from 'react' import { ComponentType, createContext, useContext, useMemo } from 'react'
import { ShapeIndicator, TLShapeIndicatorComponent } from '../components/ShapeIndicator' import { DefaultBackground } from '../components/default-components/DefaultBackground'
import { DefaultBrush, TLBrushProps } from '../components/default-components/DefaultBrush'
import { import {
DefaultBackground, DefaultCanvas,
TLBackgroundComponent, TLCanvasComponentProps,
} from '../components/default-components/DefaultBackground' } from '../components/default-components/DefaultCanvas'
import { DefaultBrush, TLBrushComponent } from '../components/default-components/DefaultBrush'
import { import {
DefaultCollaboratorHint, DefaultCollaboratorHint,
TLCollaboratorHintComponent, TLCollaboratorHintProps,
} from '../components/default-components/DefaultCollaboratorHint' } from '../components/default-components/DefaultCollaboratorHint'
import { DefaultCursor, TLCursorComponent } from '../components/default-components/DefaultCursor' import { DefaultCursor, TLCursorProps } from '../components/default-components/DefaultCursor'
import { import {
DefaultErrorFallback, DefaultErrorFallback,
TLErrorFallbackComponent, TLErrorFallbackComponent,
} from '../components/default-components/DefaultErrorFallback' } from '../components/default-components/DefaultErrorFallback'
import { DefaultGrid, TLGridComponent } from '../components/default-components/DefaultGrid' import { DefaultGrid, TLGridProps } from '../components/default-components/DefaultGrid'
import { DefaultHandle, TLHandleComponent } from '../components/default-components/DefaultHandle' import { DefaultHandle, TLHandleProps } from '../components/default-components/DefaultHandle'
import { DefaultHandles, TLHandlesComponent } from '../components/default-components/DefaultHandles' import { DefaultHandles, TLHandlesProps } from '../components/default-components/DefaultHandles'
import { import {
DefaultHoveredShapeIndicator, DefaultHoveredShapeIndicator,
TLHoveredShapeIndicatorComponent, TLHoveredShapeIndicatorProps,
} from '../components/default-components/DefaultHoveredShapeIndicator' } from '../components/default-components/DefaultHoveredShapeIndicator'
import { TLInFrontOfTheCanvas } from '../components/default-components/DefaultInFrontOfTheCanvas' import { DefaultScribble, TLScribbleProps } from '../components/default-components/DefaultScribble'
import { TLLoadingScreenComponent } from '../components/default-components/DefaultLoadingScreen'
import { TLOnTheCanvas } from '../components/default-components/DefaultOnTheCanvas'
import {
DefaultScribble,
TLScribbleComponent,
} from '../components/default-components/DefaultScribble'
import { import {
DefaultSelectionBackground, DefaultSelectionBackground,
TLSelectionBackgroundComponent, TLSelectionBackgroundProps,
} from '../components/default-components/DefaultSelectionBackground' } from '../components/default-components/DefaultSelectionBackground'
import { import {
DefaultSelectionForeground, DefaultSelectionForeground,
TLSelectionForegroundComponent, TLSelectionForegroundProps,
} from '../components/default-components/DefaultSelectionForeground' } from '../components/default-components/DefaultSelectionForeground'
import { import {
DefaultShapeErrorFallback, DefaultShapeErrorFallback,
TLShapeErrorFallbackComponent, TLShapeErrorFallbackComponent,
} from '../components/default-components/DefaultShapeErrorFallback' } from '../components/default-components/DefaultShapeErrorFallback'
import {
DefaultShapeIndicator,
TLShapeIndicatorProps,
} from '../components/default-components/DefaultShapeIndicator'
import { import {
DefaultShapeIndicatorErrorFallback, DefaultShapeIndicatorErrorFallback,
TLShapeIndicatorErrorFallbackComponent, TLShapeIndicatorErrorFallbackComponent,
} from '../components/default-components/DefaultShapeIndicatorErrorFallback' } from '../components/default-components/DefaultShapeIndicatorErrorFallback'
import { import {
DefaultSnapIndicator, DefaultSnapIndicator,
TLSnapIndicatorComponent, TLSnapIndicatorProps,
} from '../components/default-components/DefaultSnapIndictor' } from '../components/default-components/DefaultSnapIndictor'
import { DefaultSpinner, TLSpinnerComponent } from '../components/default-components/DefaultSpinner' import { DefaultSpinner } from '../components/default-components/DefaultSpinner'
import { DefaultSvgDefs, TLSvgDefsComponent } from '../components/default-components/DefaultSvgDefs' import { DefaultSvgDefs } from '../components/default-components/DefaultSvgDefs'
import { useShallowObjectIdentity } from './useIdentity' import { useShallowObjectIdentity } from './useIdentity'
export interface BaseEditorComponents { export interface BaseEditorComponents {
Background: TLBackgroundComponent Background: ComponentType
SvgDefs: TLSvgDefsComponent SvgDefs: ComponentType
Brush: TLBrushComponent Brush: ComponentType<TLBrushProps>
ZoomBrush: TLBrushComponent ZoomBrush: ComponentType<TLBrushProps>
Cursor: TLCursorComponent ShapeIndicator: ComponentType<TLShapeIndicatorProps>
CollaboratorBrush: TLBrushComponent Cursor: ComponentType<TLCursorProps>
CollaboratorCursor: TLCursorComponent Canvas: ComponentType<TLCanvasComponentProps>
CollaboratorHint: TLCollaboratorHintComponent CollaboratorBrush: ComponentType<TLBrushProps>
CollaboratorShapeIndicator: TLShapeIndicatorComponent CollaboratorCursor: ComponentType<TLCursorProps>
Grid: TLGridComponent CollaboratorHint: ComponentType<TLCollaboratorHintProps>
Scribble: TLScribbleComponent CollaboratorShapeIndicator: ComponentType<TLShapeIndicatorProps>
CollaboratorScribble: TLScribbleComponent Grid: ComponentType<TLGridProps>
SnapIndicator: TLSnapIndicatorComponent Scribble: ComponentType<TLScribbleProps>
Handles: TLHandlesComponent CollaboratorScribble: ComponentType<TLScribbleProps>
Handle: TLHandleComponent SnapIndicator: ComponentType<TLSnapIndicatorProps>
Spinner: TLSpinnerComponent Handles: ComponentType<TLHandlesProps>
SelectionForeground: TLSelectionForegroundComponent Handle: ComponentType<TLHandleProps>
SelectionBackground: TLSelectionBackgroundComponent Spinner: ComponentType
HoveredShapeIndicator: TLHoveredShapeIndicatorComponent SelectionForeground: ComponentType<TLSelectionForegroundProps>
OnTheCanvas: TLOnTheCanvas SelectionBackground: ComponentType<TLSelectionBackgroundProps>
InFrontOfTheCanvas: TLInFrontOfTheCanvas HoveredShapeIndicator: ComponentType<TLHoveredShapeIndicatorProps>
LoadingScreen: TLLoadingScreenComponent OnTheCanvas: ComponentType
InFrontOfTheCanvas: ComponentType
LoadingScreen: ComponentType
} }
// These will always have defaults // These will always have defaults
@ -116,7 +116,7 @@ export function EditorComponentsProvider({
Cursor: DefaultCursor, Cursor: DefaultCursor,
CollaboratorCursor: DefaultCursor, CollaboratorCursor: DefaultCursor,
CollaboratorHint: DefaultCollaboratorHint, CollaboratorHint: DefaultCollaboratorHint,
CollaboratorShapeIndicator: ShapeIndicator, CollaboratorShapeIndicator: DefaultShapeIndicator,
Grid: DefaultGrid, Grid: DefaultGrid,
Scribble: DefaultScribble, Scribble: DefaultScribble,
SnapIndicator: DefaultSnapIndicator, SnapIndicator: DefaultSnapIndicator,
@ -130,8 +130,10 @@ export function EditorComponentsProvider({
SelectionBackground: DefaultSelectionBackground, SelectionBackground: DefaultSelectionBackground,
SelectionForeground: DefaultSelectionForeground, SelectionForeground: DefaultSelectionForeground,
HoveredShapeIndicator: DefaultHoveredShapeIndicator, HoveredShapeIndicator: DefaultHoveredShapeIndicator,
ShapeIndicator: DefaultShapeIndicator,
OnTheCanvas: null, OnTheCanvas: null,
InFrontOfTheCanvas: null, InFrontOfTheCanvas: null,
Canvas: DefaultCanvas,
..._overrides, ..._overrides,
}), }),
[_overrides] [_overrides]

View file

@ -74,9 +74,9 @@ import { TLExitEventHandler } from '@tldraw/editor';
import { TLFrameShape } from '@tldraw/editor'; import { TLFrameShape } from '@tldraw/editor';
import { TLGeoShape } from '@tldraw/editor'; import { TLGeoShape } from '@tldraw/editor';
import { TLHandle } from '@tldraw/editor'; import { TLHandle } from '@tldraw/editor';
import { TLHandlesComponent } from '@tldraw/editor'; import { TLHandlesProps } from '@tldraw/editor';
import { TLHighlightShape } from '@tldraw/editor'; import { TLHighlightShape } from '@tldraw/editor';
import { TLHoveredShapeIndicatorComponent } from '@tldraw/editor'; import { TLHoveredShapeIndicatorProps } from '@tldraw/editor';
import { TLImageShape } from '@tldraw/editor'; import { TLImageShape } from '@tldraw/editor';
import { TLInterruptEvent } from '@tldraw/editor'; import { TLInterruptEvent } from '@tldraw/editor';
import { TLKeyboardEvent } from '@tldraw/editor'; import { TLKeyboardEvent } from '@tldraw/editor';
@ -100,9 +100,9 @@ import { TLPointerEventName } from '@tldraw/editor';
import { TLRecord } from '@tldraw/editor'; import { TLRecord } from '@tldraw/editor';
import { TLRotationSnapshot } from '@tldraw/editor'; import { TLRotationSnapshot } from '@tldraw/editor';
import { TLSchema } from '@tldraw/editor'; import { TLSchema } from '@tldraw/editor';
import { TLScribbleComponent } from '@tldraw/editor'; import { TLScribbleProps } from '@tldraw/editor';
import { TLSelectionBackgroundComponent } from '@tldraw/editor'; import { TLSelectionBackgroundProps } from '@tldraw/editor';
import { TLSelectionForegroundComponent } from '@tldraw/editor'; import { TLSelectionForegroundProps } from '@tldraw/editor';
import { TLSelectionHandle } from '@tldraw/editor'; import { TLSelectionHandle } from '@tldraw/editor';
import { TLShape } from '@tldraw/editor'; import { TLShape } from '@tldraw/editor';
import { TLShapeId } from '@tldraw/editor'; import { TLShapeId } from '@tldraw/editor';
@ -347,7 +347,7 @@ export const defaultShapeUtils: TLAnyShapeUtilConstructor[];
export const DefaultStylePanel: NamedExoticComponent<TLUiStylePanelProps>; export const DefaultStylePanel: NamedExoticComponent<TLUiStylePanelProps>;
// @public (undocumented) // @public (undocumented)
export function DefaultStylePanelContent({ relevantStyles }: TLUiStylePanelContentProps): JSX_2.Element | null; export function DefaultStylePanelContent({ styles }: TLUiStylePanelContentProps): JSX_2.Element | null;
// @public (undocumented) // @public (undocumented)
export const DefaultToolbar: React_2.NamedExoticComponent<object>; export const DefaultToolbar: React_2.NamedExoticComponent<object>;
@ -1173,21 +1173,6 @@ export function Tldraw(props: TldrawProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export const TLDRAW_FILE_EXTENSION: ".tldr"; export const TLDRAW_FILE_EXTENSION: ".tldr";
// @public (undocumented)
export function TldrawCropHandles({ size, width, height, hideAlternateHandles, }: TldrawCropHandlesProps): JSX_2.Element;
// @public (undocumented)
export interface TldrawCropHandlesProps {
// (undocumented)
height: number;
// (undocumented)
hideAlternateHandles: boolean;
// (undocumented)
size: number;
// (undocumented)
width: number;
}
// @public (undocumented) // @public (undocumented)
export interface TldrawFile { export interface TldrawFile {
// (undocumented) // (undocumented)
@ -1199,10 +1184,10 @@ export interface TldrawFile {
} }
// @public (undocumented) // @public (undocumented)
export const TldrawHandles: TLHandlesComponent; export function TldrawHandles({ children }: TLHandlesProps): JSX_2.Element | null;
// @public (undocumented) // @public (undocumented)
export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent; export function TldrawHoveredShapeIndicator({ shapeId }: TLHoveredShapeIndicatorProps): JSX_2.Element | null;
// @public // @public
export const TldrawImage: NamedExoticComponent< { export const TldrawImage: NamedExoticComponent< {
@ -1240,13 +1225,13 @@ export type TldrawProps = (Omit<TldrawUiProps, 'components'> & Omit<TldrawEditor
}); });
// @public (undocumented) // @public (undocumented)
export const TldrawScribble: TLScribbleComponent; export function TldrawScribble({ scribble, zoom, color, opacity, className }: TLScribbleProps): JSX_2.Element | null;
// @public (undocumented) // @public (undocumented)
export const TldrawSelectionBackground: TLSelectionBackgroundComponent; export const TldrawSelectionBackground: ({ bounds, rotation }: TLSelectionBackgroundProps) => JSX_2.Element | null;
// @public (undocumented) // @public (undocumented)
export const TldrawSelectionForeground: TLSelectionForegroundComponent; export const TldrawSelectionForeground: MemoExoticComponent<({ bounds, rotation, }: TLSelectionForegroundProps) => JSX_2.Element | null>;
// @public (undocumented) // @public (undocumented)
export const TldrawUi: React_2.NamedExoticComponent<TldrawUiProps>; export const TldrawUi: React_2.NamedExoticComponent<TldrawUiProps>;
@ -1313,7 +1298,7 @@ export function TldrawUiDropdownMenuCheckboxItem({ children, onSelect, ...rest }
export function TldrawUiDropdownMenuContent({ side, align, sideOffset, alignOffset, children, }: TLUiDropdownMenuContentProps): JSX_2.Element; export function TldrawUiDropdownMenuContent({ side, align, sideOffset, alignOffset, children, }: TLUiDropdownMenuContentProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export function TldrawUiDropdownMenuGroup({ children, size, }: TLUiDropdownMenuGroupProps): JSX_2.Element; export function TldrawUiDropdownMenuGroup({ children }: TLUiDropdownMenuGroupProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export function TldrawUiDropdownMenuIndicator(): JSX_2.Element; export function TldrawUiDropdownMenuIndicator(): JSX_2.Element;
@ -1349,7 +1334,7 @@ export function TldrawUiMenuCheckboxItem<TranslationKey extends string = string,
export function TldrawUiMenuContextProvider({ type, sourceId, children, }: TLUiMenuContextProviderProps): JSX_2.Element; export function TldrawUiMenuContextProvider({ type, sourceId, children, }: TLUiMenuContextProviderProps): JSX_2.Element;
// @public (undocumented) // @public (undocumented)
export function TldrawUiMenuGroup({ id, label, small, children }: TLUiMenuGroupProps): any; export function TldrawUiMenuGroup({ id, label, children }: TLUiMenuGroupProps): any;
// @public (undocumented) // @public (undocumented)
export function TldrawUiMenuItem<TranslationKey extends string = string, IconType extends string = string>({ disabled, spinner, readonlyOk, id, kbd, label, icon, onSelect, noClose, }: TLUiMenuItemProps<TranslationKey, IconType>): JSX_2.Element | null; export function TldrawUiMenuItem<TranslationKey extends string = string, IconType extends string = string>({ disabled, spinner, readonlyOk, id, kbd, label, icon, onSelect, noClose, }: TLUiMenuItemProps<TranslationKey, IconType>): JSX_2.Element | null;
@ -1459,8 +1444,6 @@ export type TLUiComponentsProviderProps = {
// @public (undocumented) // @public (undocumented)
export interface TLUiContextMenuProps { export interface TLUiContextMenuProps {
// (undocumented)
canvas: any;
// (undocumented) // (undocumented)
children?: any; children?: any;
} }
@ -1549,7 +1532,6 @@ export type TLUiDropdownMenuContentProps = {
// @public (undocumented) // @public (undocumented)
export type TLUiDropdownMenuGroupProps = { export type TLUiDropdownMenuGroupProps = {
children: any; children: any;
size?: 'medium' | 'small' | 'tiny' | 'wide';
}; };
// @public (undocumented) // @public (undocumented)
@ -1866,7 +1848,6 @@ export type TLUiMenuGroupProps<TranslationKey extends string = string> = {
label?: { label?: {
[key: string]: TranslationKey; [key: string]: TranslationKey;
} | TranslationKey; } | TranslationKey;
small?: boolean;
children?: any; children?: any;
}; };
@ -1894,7 +1875,7 @@ export type TLUiMenuSubmenuProps<Translation extends string = string> = {
} | Translation; } | Translation;
disabled?: boolean; disabled?: boolean;
children: any; children: any;
size?: 'large' | 'medium' | 'small' | 'tiny'; size?: 'medium' | 'small' | 'tiny' | 'wide';
}; };
// @public (undocumented) // @public (undocumented)
@ -1951,7 +1932,7 @@ export interface TLUiSliderProps {
// @public (undocumented) // @public (undocumented)
export type TLUiStylePanelContentProps = { export type TLUiStylePanelContentProps = {
relevantStyles: ReturnType<typeof useRelevantStyles>; styles: ReturnType<typeof useRelevantStyles>;
}; };
// @public (undocumented) // @public (undocumented)
@ -1960,11 +1941,6 @@ export interface TLUiStylePanelProps {
children?: any; children?: any;
// (undocumented) // (undocumented)
isMobile?: boolean; isMobile?: boolean;
// (undocumented)
relevantStyles: {
styles: ReadonlySharedStyleMap;
opacity: SharedStyle<number>;
} | null;
} }
// @public (undocumented) // @public (undocumented)
@ -2136,6 +2112,9 @@ export function useNativeClipboardEvents(): void;
// @public (undocumented) // @public (undocumented)
export function useReadonly(): boolean; export function useReadonly(): boolean;
// @public (undocumented)
export function useRelevantStyles(stylesToCheck?: readonly (EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"> | EnumStyleProp<"dashed" | "dotted" | "draw" | "solid"> | EnumStyleProp<"l" | "m" | "s" | "xl"> | EnumStyleProp<"none" | "pattern" | "semi" | "solid">)[]): null | ReadonlySharedStyleMap;
// @public (undocumented) // @public (undocumented)
export function useTldrawUiComponents(): Partial<{ export function useTldrawUiComponents(): Partial<{
ContextMenu: ComponentType<TLUiContextMenuProps> | null; ContextMenu: ComponentType<TLUiContextMenuProps> | null;

View file

@ -3185,7 +3185,7 @@
"excerptTokens": [ "excerptTokens": [
{ {
"kind": "Content", "kind": "Content",
"text": "export declare function DefaultStylePanelContent({ relevantStyles }: " "text": "export declare function DefaultStylePanelContent({ styles }: "
}, },
{ {
"kind": "Reference", "kind": "Reference",
@ -3223,7 +3223,7 @@
"overloadIndex": 1, "overloadIndex": 1,
"parameters": [ "parameters": [
{ {
"parameterName": "{ relevantStyles }", "parameterName": "{ styles }",
"parameterTypeTokenRange": { "parameterTypeTokenRange": {
"startIndex": 1, "startIndex": 1,
"endIndex": 2 "endIndex": 2
@ -13722,183 +13722,6 @@
], ],
"name": "Tldraw" "name": "Tldraw"
}, },
{
"kind": "Function",
"canonicalReference": "@tldraw/tldraw!TldrawCropHandles:function(1)",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function TldrawCropHandles({ size, width, height, hideAlternateHandles, }: "
},
{
"kind": "Reference",
"text": "TldrawCropHandlesProps",
"canonicalReference": "@tldraw/tldraw!TldrawCropHandlesProps:interface"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Content",
"text": "import(\"react/jsx-runtime\")."
},
{
"kind": "Reference",
"text": "JSX.Element",
"canonicalReference": "@types/react!JSX.Element:interface"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawCropHandles.tsx",
"returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 5
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "{ size, width, height, hideAlternateHandles, }",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "TldrawCropHandles"
},
{
"kind": "Interface",
"canonicalReference": "@tldraw/tldraw!TldrawCropHandlesProps:interface",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export interface TldrawCropHandlesProps "
}
],
"fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawCropHandles.tsx",
"releaseTag": "Public",
"name": "TldrawCropHandlesProps",
"preserveMemberOrder": false,
"members": [
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tldraw!TldrawCropHandlesProps#height:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "height: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "height",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tldraw!TldrawCropHandlesProps#hideAlternateHandles:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "hideAlternateHandles: "
},
{
"kind": "Content",
"text": "boolean"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "hideAlternateHandles",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tldraw!TldrawCropHandlesProps#size:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "size: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "size",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tldraw!TldrawCropHandlesProps#width:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "width: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "width",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
}
],
"extendsTokenRanges": []
},
{ {
"kind": "Interface", "kind": "Interface",
"canonicalReference": "@tldraw/tldraw!TldrawFile:interface", "canonicalReference": "@tldraw/tldraw!TldrawFile:interface",
@ -14005,52 +13828,114 @@
"extendsTokenRanges": [] "extendsTokenRanges": []
}, },
{ {
"kind": "Variable", "kind": "Function",
"canonicalReference": "@tldraw/tldraw!TldrawHandles:var", "canonicalReference": "@tldraw/tldraw!TldrawHandles:function(1)",
"docComment": "/**\n * @public\n */\n", "docComment": "/**\n * @public\n */\n",
"excerptTokens": [ "excerptTokens": [
{ {
"kind": "Content", "kind": "Content",
"text": "TldrawHandles: " "text": "export declare function TldrawHandles({ children }: "
}, },
{ {
"kind": "Reference", "kind": "Reference",
"text": "TLHandlesComponent", "text": "TLHandlesProps",
"canonicalReference": "@tldraw/editor!TLHandlesComponent:type" "canonicalReference": "@tldraw/editor!TLHandlesProps:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Content",
"text": "import(\"react/jsx-runtime\")."
},
{
"kind": "Reference",
"text": "JSX.Element",
"canonicalReference": "@types/react!JSX.Element:interface"
},
{
"kind": "Content",
"text": " | null"
},
{
"kind": "Content",
"text": ";"
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawHandles.tsx", "fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawHandles.tsx",
"isReadonly": true, "returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 6
},
"releaseTag": "Public", "releaseTag": "Public",
"name": "TldrawHandles", "overloadIndex": 1,
"variableTypeTokenRange": { "parameters": [
"startIndex": 1, {
"endIndex": 2 "parameterName": "{ children }",
} "parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "TldrawHandles"
}, },
{ {
"kind": "Variable", "kind": "Function",
"canonicalReference": "@tldraw/tldraw!TldrawHoveredShapeIndicator:var", "canonicalReference": "@tldraw/tldraw!TldrawHoveredShapeIndicator:function(1)",
"docComment": "/**\n * @public\n */\n", "docComment": "/**\n * @public\n */\n",
"excerptTokens": [ "excerptTokens": [
{ {
"kind": "Content", "kind": "Content",
"text": "TldrawHoveredShapeIndicator: " "text": "export declare function TldrawHoveredShapeIndicator({ shapeId }: "
}, },
{ {
"kind": "Reference", "kind": "Reference",
"text": "TLHoveredShapeIndicatorComponent", "text": "TLHoveredShapeIndicatorProps",
"canonicalReference": "@tldraw/editor!TLHoveredShapeIndicatorComponent:type" "canonicalReference": "@tldraw/editor!TLHoveredShapeIndicatorProps:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Content",
"text": "import(\"react/jsx-runtime\")."
},
{
"kind": "Reference",
"text": "JSX.Element",
"canonicalReference": "@types/react!JSX.Element:interface"
},
{
"kind": "Content",
"text": " | null"
},
{
"kind": "Content",
"text": ";"
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawHoveredShapeIndicator.tsx", "fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawHoveredShapeIndicator.tsx",
"isReadonly": true, "returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 6
},
"releaseTag": "Public", "releaseTag": "Public",
"name": "TldrawHoveredShapeIndicator", "overloadIndex": 1,
"variableTypeTokenRange": { "parameters": [
"startIndex": 1, {
"endIndex": 2 "parameterName": "{ shapeId }",
} "parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "TldrawHoveredShapeIndicator"
}, },
{ {
"kind": "Variable", "kind": "Variable",
@ -14340,28 +14225,59 @@
} }
}, },
{ {
"kind": "Variable", "kind": "Function",
"canonicalReference": "@tldraw/tldraw!TldrawScribble:var", "canonicalReference": "@tldraw/tldraw!TldrawScribble:function(1)",
"docComment": "/**\n * @public\n */\n", "docComment": "/**\n * @public\n */\n",
"excerptTokens": [ "excerptTokens": [
{ {
"kind": "Content", "kind": "Content",
"text": "TldrawScribble: " "text": "export declare function TldrawScribble({ scribble, zoom, color, opacity, className }: "
}, },
{ {
"kind": "Reference", "kind": "Reference",
"text": "TLScribbleComponent", "text": "TLScribbleProps",
"canonicalReference": "@tldraw/editor!TLScribbleComponent:type" "canonicalReference": "@tldraw/editor!TLScribbleProps:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Content",
"text": "import(\"react/jsx-runtime\")."
},
{
"kind": "Reference",
"text": "JSX.Element",
"canonicalReference": "@types/react!JSX.Element:interface"
},
{
"kind": "Content",
"text": " | null"
},
{
"kind": "Content",
"text": ";"
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawScribble.tsx", "fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawScribble.tsx",
"isReadonly": true, "returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 6
},
"releaseTag": "Public", "releaseTag": "Public",
"name": "TldrawScribble", "overloadIndex": 1,
"variableTypeTokenRange": { "parameters": [
"startIndex": 1, {
"endIndex": 2 "parameterName": "{ scribble, zoom, color, opacity, className }",
} "parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "TldrawScribble"
}, },
{ {
"kind": "Variable", "kind": "Variable",
@ -14372,10 +14288,27 @@
"kind": "Content", "kind": "Content",
"text": "TldrawSelectionBackground: " "text": "TldrawSelectionBackground: "
}, },
{
"kind": "Content",
"text": "({ bounds, rotation }: "
},
{ {
"kind": "Reference", "kind": "Reference",
"text": "TLSelectionBackgroundComponent", "text": "TLSelectionBackgroundProps",
"canonicalReference": "@tldraw/editor!TLSelectionBackgroundComponent:type" "canonicalReference": "@tldraw/editor!TLSelectionBackgroundProps:type"
},
{
"kind": "Content",
"text": ") => import(\"react/jsx-runtime\")."
},
{
"kind": "Reference",
"text": "JSX.Element",
"canonicalReference": "@types/react!JSX.Element:interface"
},
{
"kind": "Content",
"text": " | null"
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawSelectionBackground.tsx", "fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawSelectionBackground.tsx",
@ -14384,7 +14317,7 @@
"name": "TldrawSelectionBackground", "name": "TldrawSelectionBackground",
"variableTypeTokenRange": { "variableTypeTokenRange": {
"startIndex": 1, "startIndex": 1,
"endIndex": 2 "endIndex": 6
} }
}, },
{ {
@ -14396,10 +14329,36 @@
"kind": "Content", "kind": "Content",
"text": "TldrawSelectionForeground: " "text": "TldrawSelectionForeground: "
}, },
{
"kind": "Content",
"text": "import(\"react\")."
},
{ {
"kind": "Reference", "kind": "Reference",
"text": "TLSelectionForegroundComponent", "text": "MemoExoticComponent",
"canonicalReference": "@tldraw/editor!TLSelectionForegroundComponent:type" "canonicalReference": "@types/react!React.MemoExoticComponent:type"
},
{
"kind": "Content",
"text": "<({ bounds, rotation, }: "
},
{
"kind": "Reference",
"text": "TLSelectionForegroundProps",
"canonicalReference": "@tldraw/editor!TLSelectionForegroundProps:type"
},
{
"kind": "Content",
"text": ") => import(\"react/jsx-runtime\")."
},
{
"kind": "Reference",
"text": "JSX.Element",
"canonicalReference": "@types/react!JSX.Element:interface"
},
{
"kind": "Content",
"text": " | null>"
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx", "fileUrlPath": "packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx",
@ -14408,7 +14367,7 @@
"name": "TldrawSelectionForeground", "name": "TldrawSelectionForeground",
"variableTypeTokenRange": { "variableTypeTokenRange": {
"startIndex": 1, "startIndex": 1,
"endIndex": 2 "endIndex": 8
} }
}, },
{ {
@ -15525,7 +15484,7 @@
"excerptTokens": [ "excerptTokens": [
{ {
"kind": "Content", "kind": "Content",
"text": "export declare function TldrawUiDropdownMenuGroup({ children, size, }: " "text": "export declare function TldrawUiDropdownMenuGroup({ children }: "
}, },
{ {
"kind": "Reference", "kind": "Reference",
@ -15559,7 +15518,7 @@
"overloadIndex": 1, "overloadIndex": 1,
"parameters": [ "parameters": [
{ {
"parameterName": "{ children, size, }", "parameterName": "{ children }",
"parameterTypeTokenRange": { "parameterTypeTokenRange": {
"startIndex": 1, "startIndex": 1,
"endIndex": 2 "endIndex": 2
@ -16181,7 +16140,7 @@
"excerptTokens": [ "excerptTokens": [
{ {
"kind": "Content", "kind": "Content",
"text": "export declare function TldrawUiMenuGroup({ id, label, small, children }: " "text": "export declare function TldrawUiMenuGroup({ id, label, children }: "
}, },
{ {
"kind": "Reference", "kind": "Reference",
@ -16210,7 +16169,7 @@
"overloadIndex": 1, "overloadIndex": 1,
"parameters": [ "parameters": [
{ {
"parameterName": "{ id, label, small, children }", "parameterName": "{ id, label, children }",
"parameterTypeTokenRange": { "parameterTypeTokenRange": {
"startIndex": 1, "startIndex": 1,
"endIndex": 2 "endIndex": 2
@ -17518,34 +17477,6 @@
"name": "TLUiContextMenuProps", "name": "TLUiContextMenuProps",
"preserveMemberOrder": false, "preserveMemberOrder": false,
"members": [ "members": [
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tldraw!TLUiContextMenuProps#canvas:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "canvas: "
},
{
"kind": "Content",
"text": "any"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/tldraw/src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx",
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "canvas",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{ {
"kind": "PropertySignature", "kind": "PropertySignature",
"canonicalReference": "@tldraw/tldraw!TLUiContextMenuProps#children:member", "canonicalReference": "@tldraw/tldraw!TLUiContextMenuProps#children:member",
@ -18144,7 +18075,7 @@
}, },
{ {
"kind": "Content", "kind": "Content",
"text": "{\n children: any;\n size?: 'medium' | 'small' | 'tiny' | 'wide';\n}" "text": "{\n children: any;\n}"
}, },
{ {
"kind": "Content", "kind": "Content",
@ -21351,7 +21282,7 @@
}, },
{ {
"kind": "Content", "kind": "Content",
"text": "{\n id: string;\n label?: {\n [key: string]: TranslationKey;\n } | TranslationKey;\n small?: boolean;\n children?: any;\n}" "text": "{\n id: string;\n label?: {\n [key: string]: TranslationKey;\n } | TranslationKey;\n children?: any;\n}"
}, },
{ {
"kind": "Content", "kind": "Content",
@ -21506,7 +21437,7 @@
}, },
{ {
"kind": "Content", "kind": "Content",
"text": "{\n id: string;\n label?: {\n [key: string]: Translation;\n } | Translation;\n disabled?: boolean;\n children: any;\n size?: 'large' | 'medium' | 'small' | 'tiny';\n}" "text": "{\n id: string;\n label?: {\n [key: string]: Translation;\n } | Translation;\n disabled?: boolean;\n children: any;\n size?: 'medium' | 'small' | 'tiny' | 'wide';\n}"
}, },
{ {
"kind": "Content", "kind": "Content",
@ -21808,7 +21739,7 @@
}, },
{ {
"kind": "Content", "kind": "Content",
"text": "{\n relevantStyles: " "text": "{\n styles: "
}, },
{ {
"kind": "Reference", "kind": "Reference",
@ -21822,7 +21753,7 @@
{ {
"kind": "Reference", "kind": "Reference",
"text": "useRelevantStyles", "text": "useRelevantStyles",
"canonicalReference": "@tldraw/tldraw!~useRelevantStyles:function" "canonicalReference": "@tldraw/tldraw!useRelevantStyles:function"
}, },
{ {
"kind": "Content", "kind": "Content",
@ -21851,7 +21782,7 @@
"text": "export interface TLUiStylePanelProps " "text": "export interface TLUiStylePanelProps "
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx", "fileUrlPath": "packages/tldraw/.tsbuild-api/lib/ui/components/StylePanel/DefaultStylePanel.d.ts",
"releaseTag": "Public", "releaseTag": "Public",
"name": "TLUiStylePanelProps", "name": "TLUiStylePanelProps",
"preserveMemberOrder": false, "preserveMemberOrder": false,
@ -21874,6 +21805,7 @@
"text": ";" "text": ";"
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx",
"isReadonly": false, "isReadonly": false,
"isOptional": true, "isOptional": true,
"releaseTag": "Public", "releaseTag": "Public",
@ -21901,6 +21833,7 @@
"text": ";" "text": ";"
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx",
"isReadonly": false, "isReadonly": false,
"isOptional": true, "isOptional": true,
"releaseTag": "Public", "releaseTag": "Public",
@ -21909,51 +21842,6 @@
"startIndex": 1, "startIndex": 1,
"endIndex": 2 "endIndex": 2
} }
},
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/tldraw!TLUiStylePanelProps#relevantStyles:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "relevantStyles: "
},
{
"kind": "Content",
"text": "{\n styles: "
},
{
"kind": "Reference",
"text": "ReadonlySharedStyleMap",
"canonicalReference": "@tldraw/editor!ReadonlySharedStyleMap:class"
},
{
"kind": "Content",
"text": ";\n opacity: "
},
{
"kind": "Reference",
"text": "SharedStyle",
"canonicalReference": "@tldraw/editor!SharedStyle:type"
},
{
"kind": "Content",
"text": "<number>;\n } | null"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "relevantStyles",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 6
}
} }
], ],
"extendsTokenRanges": [] "extendsTokenRanges": []
@ -23678,6 +23566,92 @@
"parameters": [], "parameters": [],
"name": "useReadonly" "name": "useReadonly"
}, },
{
"kind": "Function",
"canonicalReference": "@tldraw/tldraw!useRelevantStyles:function(1)",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function useRelevantStyles(stylesToCheck?: "
},
{
"kind": "Content",
"text": "readonly (import(\"@tldraw/editor\")."
},
{
"kind": "Reference",
"text": "EnumStyleProp",
"canonicalReference": "@tldraw/tlschema!EnumStyleProp:class"
},
{
"kind": "Content",
"text": "<\"black\" | \"blue\" | \"green\" | \"grey\" | \"light-blue\" | \"light-green\" | \"light-red\" | \"light-violet\" | \"orange\" | \"red\" | \"violet\" | \"yellow\"> | import(\"@tldraw/editor\")."
},
{
"kind": "Reference",
"text": "EnumStyleProp",
"canonicalReference": "@tldraw/tlschema!EnumStyleProp:class"
},
{
"kind": "Content",
"text": "<\"dashed\" | \"dotted\" | \"draw\" | \"solid\"> | import(\"@tldraw/editor\")."
},
{
"kind": "Reference",
"text": "EnumStyleProp",
"canonicalReference": "@tldraw/tlschema!EnumStyleProp:class"
},
{
"kind": "Content",
"text": "<\"l\" | \"m\" | \"s\" | \"xl\"> | import(\"@tldraw/editor\")."
},
{
"kind": "Reference",
"text": "EnumStyleProp",
"canonicalReference": "@tldraw/tlschema!EnumStyleProp:class"
},
{
"kind": "Content",
"text": "<\"none\" | \"pattern\" | \"semi\" | \"solid\">)[]"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Content",
"text": "null | "
},
{
"kind": "Reference",
"text": "ReadonlySharedStyleMap",
"canonicalReference": "@tldraw/editor!ReadonlySharedStyleMap:class"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/tldraw/src/lib/ui/hooks/useRevelantStyles.ts",
"returnTypeTokenRange": {
"startIndex": 11,
"endIndex": 13
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "stylesToCheck",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 10
},
"isOptional": true
}
],
"name": "useRelevantStyles"
},
{ {
"kind": "Function", "kind": "Function",
"canonicalReference": "@tldraw/tldraw!useTldrawUiComponents:function(1)", "canonicalReference": "@tldraw/tldraw!useTldrawUiComponents:function(1)",

View file

@ -4,7 +4,6 @@
export * from '@tldraw/editor' export * from '@tldraw/editor'
export { Tldraw, type TldrawProps } from './lib/Tldraw' export { Tldraw, type TldrawProps } from './lib/Tldraw'
export { TldrawImage, type TldrawImageProps } from './lib/TldrawImage' export { TldrawImage, type TldrawImageProps } from './lib/TldrawImage'
export { TldrawCropHandles, type TldrawCropHandlesProps } from './lib/canvas/TldrawCropHandles'
export { TldrawHandles } from './lib/canvas/TldrawHandles' export { TldrawHandles } from './lib/canvas/TldrawHandles'
export { TldrawHoveredShapeIndicator } from './lib/canvas/TldrawHoveredShapeIndicator' export { TldrawHoveredShapeIndicator } from './lib/canvas/TldrawHoveredShapeIndicator'
export { TldrawScribble } from './lib/canvas/TldrawScribble' export { TldrawScribble } from './lib/canvas/TldrawScribble'
@ -82,6 +81,7 @@ export { useKeyboardShortcuts } from './lib/ui/hooks/useKeyboardShortcuts'
export { useLocalStorageState } from './lib/ui/hooks/useLocalStorageState' export { useLocalStorageState } from './lib/ui/hooks/useLocalStorageState'
export { useMenuIsOpen } from './lib/ui/hooks/useMenuIsOpen' export { useMenuIsOpen } from './lib/ui/hooks/useMenuIsOpen'
export { useReadonly } from './lib/ui/hooks/useReadonly' export { useReadonly } from './lib/ui/hooks/useReadonly'
export { useRelevantStyles } from './lib/ui/hooks/useRevelantStyles'
export { export {
toolbarItem, toolbarItem,
useToolbarSchema, useToolbarSchema,

View file

@ -9,7 +9,8 @@ describe('<Tldraw />', () => {
await renderTldrawComponent( await renderTldrawComponent(
<Tldraw> <Tldraw>
<div data-testid="canvas-1" /> <div data-testid="canvas-1" />
</Tldraw> </Tldraw>,
{ waitForPatterns: false }
) )
await screen.findByTestId('canvas-1') await screen.findByTestId('canvas-1')
@ -26,7 +27,7 @@ describe('<Tldraw />', () => {
) )
} }
await renderTldrawComponent(<TestComponent />) await renderTldrawComponent(<TestComponent />, { waitForPatterns: false })
await screen.findByTestId('canvas-1') await screen.findByTestId('canvas-1')
}) })
@ -59,7 +60,8 @@ describe('<Tldraw />', () => {
const rendered = await renderTldrawComponent( const rendered = await renderTldrawComponent(
<Tldraw shapeUtils={[FakeShapeUtil1]}> <Tldraw shapeUtils={[FakeShapeUtil1]}>
<div data-testid="canvas-1" /> <div data-testid="canvas-1" />
</Tldraw> </Tldraw>,
{ waitForPatterns: false }
) )
await screen.findByTestId('canvas-1') await screen.findByTestId('canvas-1')

View file

@ -1,5 +1,4 @@
import { import {
Canvas,
Editor, Editor,
ErrorScreen, ErrorScreen,
LoadingScreen, LoadingScreen,
@ -13,6 +12,7 @@ import {
TldrawEditorBaseProps, TldrawEditorBaseProps,
assert, assert,
useEditor, useEditor,
useEditorComponents,
useShallowArrayIdentity, useShallowArrayIdentity,
useShallowObjectIdentity, useShallowObjectIdentity,
} from '@tldraw/editor' } from '@tldraw/editor'
@ -125,7 +125,7 @@ export function Tldraw(props: TldrawProps) {
tools={toolsWithDefaults} tools={toolsWithDefaults}
> >
<TldrawUi {...rest} components={componentsWithDefault}> <TldrawUi {...rest} components={componentsWithDefault}>
<InsideOfEditorContext <InsideOfEditorAndUiContext
maxImageDimension={maxImageDimension} maxImageDimension={maxImageDimension}
maxAssetSize={maxAssetSize} maxAssetSize={maxAssetSize}
acceptedImageMimeTypes={acceptedImageMimeTypes} acceptedImageMimeTypes={acceptedImageMimeTypes}
@ -147,8 +147,8 @@ const defaultAcceptedImageMimeTypes = Object.freeze([
const defaultAcceptedVideoMimeTypes = Object.freeze(['video/mp4', 'video/quicktime']) const defaultAcceptedVideoMimeTypes = Object.freeze(['video/mp4', 'video/quicktime'])
// We put these hooks into a component here so that they can run inside of the context provided by TldrawEditor. // We put these hooks into a component here so that they can run inside of the context provided by TldrawEditor and TldrawUi.
function InsideOfEditorContext({ function InsideOfEditorAndUiContext({
maxImageDimension = 1000, maxImageDimension = 1000,
maxAssetSize = 10 * 1024 * 1024, // 10mb maxAssetSize = 10 * 1024 * 1024, // 10mb
acceptedImageMimeTypes = defaultAcceptedImageMimeTypes, acceptedImageMimeTypes = defaultAcceptedImageMimeTypes,
@ -182,10 +182,19 @@ function InsideOfEditorContext({
if (editor) return onMountEvent?.(editor) if (editor) return onMountEvent?.(editor)
}, [editor, onMountEvent]) }, [editor, onMountEvent])
const { Canvas } = useEditorComponents()
const { ContextMenu } = useTldrawUiComponents() const { ContextMenu } = useTldrawUiComponents()
if (!ContextMenu) return <Canvas />
return <ContextMenu canvas={<Canvas />} /> if (ContextMenu) {
// should wrap canvas
return <ContextMenu />
}
if (Canvas) {
return <Canvas />
}
return null
} }
// duped from tldraw editor // duped from tldraw editor

View file

@ -1,7 +1,6 @@
import { toDomPrecision } from '@tldraw/editor' import { toDomPrecision } from '@tldraw/editor'
import classNames from 'classnames' import classNames from 'classnames'
/** @public */
export interface TldrawCropHandlesProps { export interface TldrawCropHandlesProps {
size: number size: number
width: number width: number
@ -9,7 +8,6 @@ export interface TldrawCropHandlesProps {
hideAlternateHandles: boolean hideAlternateHandles: boolean
} }
/** @public */
export function TldrawCropHandles({ export function TldrawCropHandles({
size, size,
width, width,

View file

@ -1,7 +1,7 @@
import { TLHandlesComponent, useEditor, useValue } from '@tldraw/editor' import { TLHandlesProps, useEditor, useValue } from '@tldraw/editor'
/** @public */ /** @public */
export const TldrawHandles: TLHandlesComponent = ({ children }) => { export function TldrawHandles({ children }: TLHandlesProps) {
const editor = useEditor() const editor = useEditor()
const shouldDisplayHandles = useValue( const shouldDisplayHandles = useValue(
'shouldDisplayHandles', 'shouldDisplayHandles',

View file

@ -1,13 +1,14 @@
import { import {
ShapeIndicator, TLHoveredShapeIndicatorProps,
TLHoveredShapeIndicatorComponent,
useEditor, useEditor,
useEditorComponents,
useValue, useValue,
} from '@tldraw/editor' } from '@tldraw/editor'
/** @public */ /** @public */
export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent = ({ shapeId }) => { export function TldrawHoveredShapeIndicator({ shapeId }: TLHoveredShapeIndicatorProps) {
const editor = useEditor() const editor = useEditor()
const { ShapeIndicator } = useEditorComponents()
const showHoveredShapeIndicator = useValue( const showHoveredShapeIndicator = useValue(
'show hovered', 'show hovered',
() => { () => {
@ -23,8 +24,9 @@ export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent = ({
}, },
[editor] [editor]
) )
if (!ShapeIndicator) return null
if (!showHoveredShapeIndicator) return null if (!showHoveredShapeIndicator) return null
return <ShapeIndicator className="tl-user-indicator__hovered" id={shapeId} /> return <ShapeIndicator className="tl-user-indicator__hovered" shapeId={shapeId} />
} }
// //

View file

@ -1,15 +1,9 @@
import { EASINGS, TLScribbleComponent, getSvgPathFromPoints } from '@tldraw/editor' import { EASINGS, TLScribbleProps, getSvgPathFromPoints } from '@tldraw/editor'
import classNames from 'classnames' import classNames from 'classnames'
import { getStroke } from '../shapes/shared/freehand/getStroke' import { getStroke } from '../shapes/shared/freehand/getStroke'
/** @public */ /** @public */
export const TldrawScribble: TLScribbleComponent = ({ export function TldrawScribble({ scribble, zoom, color, opacity, className }: TLScribbleProps) {
scribble,
zoom,
color,
opacity,
className,
}) => {
if (!scribble.points.length) return null if (!scribble.points.length) return null
const stroke = getStroke(scribble.points, { const stroke = getStroke(scribble.points, {

View file

@ -1,12 +1,12 @@
import { import {
DefaultSelectionBackground, DefaultSelectionBackground,
TLSelectionBackgroundComponent, TLSelectionBackgroundProps,
useEditor, useEditor,
useValue, useValue,
} from '@tldraw/editor' } from '@tldraw/editor'
/** @public */ /** @public */
export const TldrawSelectionBackground: TLSelectionBackgroundComponent = ({ bounds, rotation }) => { export const TldrawSelectionBackground = ({ bounds, rotation }: TLSelectionBackgroundProps) => {
const editor = useEditor() const editor = useEditor()
const shouldDisplay = useValue( const shouldDisplay = useValue(

View file

@ -1,8 +1,7 @@
import { import {
Box,
RotateCorner, RotateCorner,
TLEmbedShape, TLEmbedShape,
TLSelectionForegroundComponent, TLSelectionForegroundProps,
TLTextShape, TLTextShape,
getCursor, getCursor,
toDomPrecision, toDomPrecision,
@ -18,428 +17,428 @@ import { useReadonly } from '../ui/hooks/useReadonly'
import { TldrawCropHandles } from './TldrawCropHandles' import { TldrawCropHandles } from './TldrawCropHandles'
/** @public */ /** @public */
export const TldrawSelectionForeground: TLSelectionForegroundComponent = track( export const TldrawSelectionForeground = track(function TldrawSelectionForeground({
function TldrawSelectionForeground({ bounds, rotation }: { bounds: Box; rotation: number }) { bounds,
const editor = useEditor() rotation,
const rSvg = useRef<SVGSVGElement>(null) }: TLSelectionForegroundProps) {
const editor = useEditor()
const rSvg = useRef<SVGSVGElement>(null)
const isReadonlyMode = useReadonly() const isReadonlyMode = useReadonly()
const topEvents = useSelectionEvents('top') const topEvents = useSelectionEvents('top')
const rightEvents = useSelectionEvents('right') const rightEvents = useSelectionEvents('right')
const bottomEvents = useSelectionEvents('bottom') const bottomEvents = useSelectionEvents('bottom')
const leftEvents = useSelectionEvents('left') const leftEvents = useSelectionEvents('left')
const topLeftEvents = useSelectionEvents('top_left') const topLeftEvents = useSelectionEvents('top_left')
const topRightEvents = useSelectionEvents('top_right') const topRightEvents = useSelectionEvents('top_right')
const bottomRightEvents = useSelectionEvents('bottom_right') const bottomRightEvents = useSelectionEvents('bottom_right')
const bottomLeftEvents = useSelectionEvents('bottom_left') const bottomLeftEvents = useSelectionEvents('bottom_left')
const isDefaultCursor = const isDefaultCursor =
!editor.getIsMenuOpen() && editor.getInstanceState().cursor.type === 'default' !editor.getIsMenuOpen() && editor.getInstanceState().cursor.type === 'default'
const isCoarsePointer = editor.getInstanceState().isCoarsePointer const isCoarsePointer = editor.getInstanceState().isCoarsePointer
const onlyShape = editor.getOnlySelectedShape() const onlyShape = editor.getOnlySelectedShape()
const isLockedShape = onlyShape && editor.isShapeOrAncestorLocked(onlyShape) const isLockedShape = onlyShape && editor.isShapeOrAncestorLocked(onlyShape)
// if all shapes have an expandBy for the selection outline, we can expand by the l // if all shapes have an expandBy for the selection outline, we can expand by the l
const expandOutlineBy = onlyShape const expandOutlineBy = onlyShape
? editor.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape) ? editor.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape)
: 0 : 0
useTransform(rSvg, bounds?.x, bounds?.y, 1, editor.getSelectionRotation(), { useTransform(rSvg, bounds?.x, bounds?.y, 1, editor.getSelectionRotation(), {
x: -expandOutlineBy, x: -expandOutlineBy,
y: -expandOutlineBy, y: -expandOutlineBy,
}) })
if (!bounds) return null if (!bounds) return null
bounds = bounds.clone().expandBy(expandOutlineBy).zeroFix() bounds = bounds.clone().expandBy(expandOutlineBy).zeroFix()
const zoom = editor.getZoomLevel() const zoom = editor.getZoomLevel()
const isChangingStyle = editor.getInstanceState().isChangingStyle const isChangingStyle = editor.getInstanceState().isChangingStyle
const width = bounds.width const width = bounds.width
const height = bounds.height const height = bounds.height
const size = 8 / zoom const size = 8 / zoom
const isTinyX = width < size * 2 const isTinyX = width < size * 2
const isTinyY = height < size * 2 const isTinyY = height < size * 2
const isSmallX = width < size * 4 const isSmallX = width < size * 4
const isSmallY = height < size * 4 const isSmallY = height < size * 4
const isSmallCropX = width < size * 5 const isSmallCropX = width < size * 5
const isSmallCropY = height < size * 5 const isSmallCropY = height < size * 5
const mobileHandleMultiplier = isCoarsePointer ? 1.75 : 1 const mobileHandleMultiplier = isCoarsePointer ? 1.75 : 1
const targetSize = (6 / zoom) * mobileHandleMultiplier const targetSize = (6 / zoom) * mobileHandleMultiplier
const targetSizeX = (isSmallX ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75) const targetSizeX = (isSmallX ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
const targetSizeY = (isSmallY ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75) const targetSizeY = (isSmallY ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
const showSelectionBounds = const showSelectionBounds =
(onlyShape ? !editor.getShapeUtil(onlyShape).hideSelectionBoundsFg(onlyShape) : true) && (onlyShape ? !editor.getShapeUtil(onlyShape).hideSelectionBoundsFg(onlyShape) : true) &&
!isChangingStyle !isChangingStyle
let shouldDisplayBox = let shouldDisplayBox =
(showSelectionBounds && (showSelectionBounds &&
editor.isInAny(
'select.idle',
'select.brushing',
'select.scribble_brushing',
'select.pointing_canvas',
'select.pointing_selection',
'select.pointing_shape',
'select.crop.idle',
'select.crop.pointing_crop',
'select.pointing_resize_handle',
'select.pointing_crop_handle'
)) ||
(showSelectionBounds &&
editor.isIn('select.resizing') &&
onlyShape &&
editor.isShapeOfType<TLTextShape>(onlyShape, 'text'))
if (onlyShape && shouldDisplayBox) {
if (editor.environment.isFirefox && editor.isShapeOfType<TLEmbedShape>(onlyShape, 'embed')) {
shouldDisplayBox = false
}
}
const showCropHandles =
editor.isInAny(
'select.pointing_crop_handle',
'select.crop.idle',
'select.crop.pointing_crop'
) &&
!isChangingStyle &&
!isReadonlyMode
const shouldDisplayControls =
editor.isInAny( editor.isInAny(
'select.idle', 'select.idle',
'select.brushing',
'select.scribble_brushing',
'select.pointing_canvas',
'select.pointing_selection', 'select.pointing_selection',
'select.pointing_shape', 'select.pointing_shape',
'select.crop.idle' 'select.crop.idle',
) && 'select.crop.pointing_crop',
!isChangingStyle && 'select.pointing_resize_handle',
!isReadonlyMode 'select.pointing_crop_handle'
)) ||
const showCornerRotateHandles = (showSelectionBounds &&
!isCoarsePointer && editor.isIn('select.resizing') &&
!(isTinyX || isTinyY) &&
(shouldDisplayControls || showCropHandles) &&
(onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) &&
!isLockedShape
const showMobileRotateHandle =
isCoarsePointer &&
(!isSmallX || !isSmallY) &&
(shouldDisplayControls || showCropHandles) &&
(onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) &&
!isLockedShape
const showResizeHandles =
shouldDisplayControls &&
(onlyShape
? editor.getShapeUtil(onlyShape).canResize(onlyShape) &&
!editor.getShapeUtil(onlyShape).hideResizeHandles(onlyShape)
: true) &&
!showCropHandles &&
!isLockedShape
const hideAlternateCornerHandles = isTinyX || isTinyY
const showOnlyOneHandle = isTinyX && isTinyY
const hideAlternateCropHandles = isSmallCropX || isSmallCropY
const showHandles = showResizeHandles || showCropHandles
const hideRotateCornerHandles = !showCornerRotateHandles
const hideMobileRotateHandle = !shouldDisplayControls || !showMobileRotateHandle
const hideTopLeftCorner = !shouldDisplayControls || !showHandles
const hideTopRightCorner = !shouldDisplayControls || !showHandles || hideAlternateCornerHandles
const hideBottomLeftCorner =
!shouldDisplayControls || !showHandles || hideAlternateCornerHandles
const hideBottomRightCorner =
!shouldDisplayControls || !showHandles || (showOnlyOneHandle && !showCropHandles)
// If we're showing crop handles, then show the edges too.
// If we're showing resize handles, then show the edges only
// if we're not hiding them for some other reason.
let hideVerticalEdgeTargets = true
// The same logic above applies here, except another nuance is that
// we enable resizing for text on mobile (coarse).
let hideHorizontalEdgeTargets = true
if (showCropHandles) {
hideVerticalEdgeTargets = hideAlternateCropHandles
hideHorizontalEdgeTargets = hideAlternateCropHandles
} else if (showResizeHandles) {
hideVerticalEdgeTargets = hideAlternateCornerHandles || showOnlyOneHandle || isCoarsePointer
const isMobileAndTextShape = isCoarsePointer && onlyShape && onlyShape.type === 'text'
hideHorizontalEdgeTargets = hideVerticalEdgeTargets && !isMobileAndTextShape
}
const textHandleHeight = Math.min(24 / zoom, height - targetSizeY * 3)
const showTextResizeHandles =
shouldDisplayControls &&
isCoarsePointer &&
onlyShape && onlyShape &&
editor.isShapeOfType<TLTextShape>(onlyShape, 'text') && editor.isShapeOfType<TLTextShape>(onlyShape, 'text'))
textHandleHeight * zoom >= 4
return ( if (onlyShape && shouldDisplayBox) {
<svg className="tl-overlays__item tl-selection__fg" data-testid="selection-foreground"> if (editor.environment.isFirefox && editor.isShapeOfType<TLEmbedShape>(onlyShape, 'embed')) {
<g ref={rSvg}> shouldDisplayBox = false
{shouldDisplayBox && ( }
<rect
className="tl-selection__fg__outline"
width={toDomPrecision(width)}
height={toDomPrecision(height)}
/>
)}
<RotateCornerHandle
data-testid="selection.rotate.top-left"
cx={0}
cy={0}
targetSize={targetSize}
corner="top_left_rotate"
cursor={isDefaultCursor ? getCursor('nwse-rotate', rotation) : undefined}
isHidden={hideRotateCornerHandles}
/>
<RotateCornerHandle
data-testid="selection.rotate.top-right"
cx={width + targetSize * 3}
cy={0}
targetSize={targetSize}
corner="top_right_rotate"
cursor={isDefaultCursor ? getCursor('nesw-rotate', rotation) : undefined}
isHidden={hideRotateCornerHandles}
/>
<RotateCornerHandle
data-testid="selection.rotate.bottom-left"
cx={0}
cy={height + targetSize * 3}
targetSize={targetSize}
corner="bottom_left_rotate"
cursor={isDefaultCursor ? getCursor('swne-rotate', rotation) : undefined}
isHidden={hideRotateCornerHandles}
/>
<RotateCornerHandle
data-testid="selection.rotate.bottom-right"
cx={width + targetSize * 3}
cy={height + targetSize * 3}
targetSize={targetSize}
corner="bottom_right_rotate"
cursor={isDefaultCursor ? getCursor('senw-rotate', rotation) : undefined}
isHidden={hideRotateCornerHandles}
/>
<MobileRotateHandle
data-testid="selection.rotate.mobile"
cx={isSmallX ? -targetSize * 1.5 : width / 2}
cy={isSmallX ? height / 2 : -targetSize * 1.5}
size={size}
isHidden={hideMobileRotateHandle}
/>
{/* Targets */}
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideVerticalEdgeTargets,
})}
data-testid="selection.resize.top"
aria-label="top target"
pointerEvents="all"
x={0}
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY))}
width={toDomPrecision(width)}
height={toDomPrecision(Math.max(1, targetSizeY * 2))}
style={isDefaultCursor ? { cursor: getCursor('ns-resize', rotation) } : undefined}
{...topEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideHorizontalEdgeTargets,
})}
data-testid="selection.resize.right"
aria-label="right target"
pointerEvents="all"
x={toDomPrecision(width - (isSmallX ? 0 : targetSizeX))}
y={0}
height={toDomPrecision(height)}
width={toDomPrecision(Math.max(1, targetSizeX * 2))}
style={isDefaultCursor ? { cursor: getCursor('ew-resize', rotation) } : undefined}
{...rightEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideVerticalEdgeTargets,
})}
data-testid="selection.resize.bottom"
aria-label="bottom target"
pointerEvents="all"
x={0}
y={toDomPrecision(height - (isSmallY ? 0 : targetSizeY))}
width={toDomPrecision(width)}
height={toDomPrecision(Math.max(1, targetSizeY * 2))}
style={isDefaultCursor ? { cursor: getCursor('ns-resize', rotation) } : undefined}
{...bottomEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideHorizontalEdgeTargets,
})}
data-testid="selection.resize.left"
aria-label="left target"
pointerEvents="all"
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 2 : targetSizeX))}
y={0}
height={toDomPrecision(height)}
width={toDomPrecision(Math.max(1, targetSizeX * 2))}
style={isDefaultCursor ? { cursor: getCursor('ew-resize', rotation) } : undefined}
{...leftEvents}
/>
{/* Corner Targets */}
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideTopLeftCorner,
})}
data-testid="selection.target.top-left"
aria-label="top-left target"
pointerEvents="all"
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 2 : targetSizeX * 1.5))}
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY * 1.5))}
width={toDomPrecision(targetSizeX * 3)}
height={toDomPrecision(targetSizeY * 3)}
style={isDefaultCursor ? { cursor: getCursor('nwse-resize', rotation) } : undefined}
{...topLeftEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideTopRightCorner,
})}
data-testid="selection.target.top-right"
aria-label="top-right target"
pointerEvents="all"
x={toDomPrecision(width - (isSmallX ? 0 : targetSizeX * 1.5))}
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY * 1.5))}
width={toDomPrecision(targetSizeX * 3)}
height={toDomPrecision(targetSizeY * 3)}
style={isDefaultCursor ? { cursor: getCursor('nesw-resize', rotation) } : undefined}
{...topRightEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideBottomRightCorner,
})}
data-testid="selection.target.bottom-right"
aria-label="bottom-right target"
pointerEvents="all"
x={toDomPrecision(width - (isSmallX ? targetSizeX : targetSizeX * 1.5))}
y={toDomPrecision(height - (isSmallY ? targetSizeY : targetSizeY * 1.5))}
width={toDomPrecision(targetSizeX * 3)}
height={toDomPrecision(targetSizeY * 3)}
style={isDefaultCursor ? { cursor: getCursor('nwse-resize', rotation) } : undefined}
{...bottomRightEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideBottomLeftCorner,
})}
data-testid="selection.target.bottom-left"
aria-label="bottom-left target"
pointerEvents="all"
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 3 : targetSizeX * 1.5))}
y={toDomPrecision(height - (isSmallY ? 0 : targetSizeY * 1.5))}
width={toDomPrecision(targetSizeX * 3)}
height={toDomPrecision(targetSizeY * 3)}
style={isDefaultCursor ? { cursor: getCursor('nesw-resize', rotation) } : undefined}
{...bottomLeftEvents}
/>
{/* Resize Handles */}
{showResizeHandles && (
<>
<rect
data-testid="selection.resize.top-left"
className={classNames('tl-corner-handle', {
'tl-hidden': hideTopLeftCorner,
})}
aria-label="top_left handle"
x={toDomPrecision(0 - size / 2)}
y={toDomPrecision(0 - size / 2)}
width={toDomPrecision(size)}
height={toDomPrecision(size)}
/>
<rect
data-testid="selection.resize.top-right"
className={classNames('tl-corner-handle', {
'tl-hidden': hideTopRightCorner,
})}
aria-label="top_right handle"
x={toDomPrecision(width - size / 2)}
y={toDomPrecision(0 - size / 2)}
width={toDomPrecision(size)}
height={toDomPrecision(size)}
/>
<rect
data-testid="selection.resize.bottom-right"
className={classNames('tl-corner-handle', {
'tl-hidden': hideBottomRightCorner,
})}
aria-label="bottom_right handle"
x={toDomPrecision(width - size / 2)}
y={toDomPrecision(height - size / 2)}
width={toDomPrecision(size)}
height={toDomPrecision(size)}
/>
<rect
data-testid="selection.resize.bottom-left"
className={classNames('tl-corner-handle', {
'tl-hidden': hideBottomLeftCorner,
})}
aria-label="bottom_left handle"
x={toDomPrecision(0 - size / 2)}
y={toDomPrecision(height - size / 2)}
width={toDomPrecision(size)}
height={toDomPrecision(size)}
/>
</>
)}
{showTextResizeHandles && (
<>
<rect
data-testid="selection.text-resize.left.handle"
className="tl-text-handle"
aria-label="bottom_left handle"
x={toDomPrecision(0 - size / 4)}
y={toDomPrecision(height / 2 - textHandleHeight / 2)}
rx={size / 4}
width={toDomPrecision(size / 2)}
height={toDomPrecision(textHandleHeight)}
/>
<rect
data-testid="selection.text-resize.right.handle"
className="tl-text-handle"
aria-label="bottom_left handle"
rx={size / 4}
x={toDomPrecision(width - size / 4)}
y={toDomPrecision(height / 2 - textHandleHeight / 2)}
width={toDomPrecision(size / 2)}
height={toDomPrecision(textHandleHeight)}
/>
</>
)}
{/* Crop Handles */}
{showCropHandles && (
<TldrawCropHandles
{...{
size,
width,
height,
hideAlternateHandles: hideAlternateCropHandles,
}}
/>
)}
</g>
</svg>
)
} }
)
const showCropHandles =
editor.isInAny(
'select.pointing_crop_handle',
'select.crop.idle',
'select.crop.pointing_crop'
) &&
!isChangingStyle &&
!isReadonlyMode
const shouldDisplayControls =
editor.isInAny(
'select.idle',
'select.pointing_selection',
'select.pointing_shape',
'select.crop.idle'
) &&
!isChangingStyle &&
!isReadonlyMode
const showCornerRotateHandles =
!isCoarsePointer &&
!(isTinyX || isTinyY) &&
(shouldDisplayControls || showCropHandles) &&
(onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) &&
!isLockedShape
const showMobileRotateHandle =
isCoarsePointer &&
(!isSmallX || !isSmallY) &&
(shouldDisplayControls || showCropHandles) &&
(onlyShape ? !editor.getShapeUtil(onlyShape).hideRotateHandle(onlyShape) : true) &&
!isLockedShape
const showResizeHandles =
shouldDisplayControls &&
(onlyShape
? editor.getShapeUtil(onlyShape).canResize(onlyShape) &&
!editor.getShapeUtil(onlyShape).hideResizeHandles(onlyShape)
: true) &&
!showCropHandles &&
!isLockedShape
const hideAlternateCornerHandles = isTinyX || isTinyY
const showOnlyOneHandle = isTinyX && isTinyY
const hideAlternateCropHandles = isSmallCropX || isSmallCropY
const showHandles = showResizeHandles || showCropHandles
const hideRotateCornerHandles = !showCornerRotateHandles
const hideMobileRotateHandle = !shouldDisplayControls || !showMobileRotateHandle
const hideTopLeftCorner = !shouldDisplayControls || !showHandles
const hideTopRightCorner = !shouldDisplayControls || !showHandles || hideAlternateCornerHandles
const hideBottomLeftCorner = !shouldDisplayControls || !showHandles || hideAlternateCornerHandles
const hideBottomRightCorner =
!shouldDisplayControls || !showHandles || (showOnlyOneHandle && !showCropHandles)
// If we're showing crop handles, then show the edges too.
// If we're showing resize handles, then show the edges only
// if we're not hiding them for some other reason.
let hideVerticalEdgeTargets = true
// The same logic above applies here, except another nuance is that
// we enable resizing for text on mobile (coarse).
let hideHorizontalEdgeTargets = true
if (showCropHandles) {
hideVerticalEdgeTargets = hideAlternateCropHandles
hideHorizontalEdgeTargets = hideAlternateCropHandles
} else if (showResizeHandles) {
hideVerticalEdgeTargets = hideAlternateCornerHandles || showOnlyOneHandle || isCoarsePointer
const isMobileAndTextShape = isCoarsePointer && onlyShape && onlyShape.type === 'text'
hideHorizontalEdgeTargets = hideVerticalEdgeTargets && !isMobileAndTextShape
}
const textHandleHeight = Math.min(24 / zoom, height - targetSizeY * 3)
const showTextResizeHandles =
shouldDisplayControls &&
isCoarsePointer &&
onlyShape &&
editor.isShapeOfType<TLTextShape>(onlyShape, 'text') &&
textHandleHeight * zoom >= 4
return (
<svg className="tl-overlays__item tl-selection__fg" data-testid="selection-foreground">
<g ref={rSvg}>
{shouldDisplayBox && (
<rect
className="tl-selection__fg__outline"
width={toDomPrecision(width)}
height={toDomPrecision(height)}
/>
)}
<RotateCornerHandle
data-testid="selection.rotate.top-left"
cx={0}
cy={0}
targetSize={targetSize}
corner="top_left_rotate"
cursor={isDefaultCursor ? getCursor('nwse-rotate', rotation) : undefined}
isHidden={hideRotateCornerHandles}
/>
<RotateCornerHandle
data-testid="selection.rotate.top-right"
cx={width + targetSize * 3}
cy={0}
targetSize={targetSize}
corner="top_right_rotate"
cursor={isDefaultCursor ? getCursor('nesw-rotate', rotation) : undefined}
isHidden={hideRotateCornerHandles}
/>
<RotateCornerHandle
data-testid="selection.rotate.bottom-left"
cx={0}
cy={height + targetSize * 3}
targetSize={targetSize}
corner="bottom_left_rotate"
cursor={isDefaultCursor ? getCursor('swne-rotate', rotation) : undefined}
isHidden={hideRotateCornerHandles}
/>
<RotateCornerHandle
data-testid="selection.rotate.bottom-right"
cx={width + targetSize * 3}
cy={height + targetSize * 3}
targetSize={targetSize}
corner="bottom_right_rotate"
cursor={isDefaultCursor ? getCursor('senw-rotate', rotation) : undefined}
isHidden={hideRotateCornerHandles}
/>
<MobileRotateHandle
data-testid="selection.rotate.mobile"
cx={isSmallX ? -targetSize * 1.5 : width / 2}
cy={isSmallX ? height / 2 : -targetSize * 1.5}
size={size}
isHidden={hideMobileRotateHandle}
/>
{/* Targets */}
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideVerticalEdgeTargets,
})}
data-testid="selection.resize.top"
aria-label="top target"
pointerEvents="all"
x={0}
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY))}
width={toDomPrecision(width)}
height={toDomPrecision(Math.max(1, targetSizeY * 2))}
style={isDefaultCursor ? { cursor: getCursor('ns-resize', rotation) } : undefined}
{...topEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideHorizontalEdgeTargets,
})}
data-testid="selection.resize.right"
aria-label="right target"
pointerEvents="all"
x={toDomPrecision(width - (isSmallX ? 0 : targetSizeX))}
y={0}
height={toDomPrecision(height)}
width={toDomPrecision(Math.max(1, targetSizeX * 2))}
style={isDefaultCursor ? { cursor: getCursor('ew-resize', rotation) } : undefined}
{...rightEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideVerticalEdgeTargets,
})}
data-testid="selection.resize.bottom"
aria-label="bottom target"
pointerEvents="all"
x={0}
y={toDomPrecision(height - (isSmallY ? 0 : targetSizeY))}
width={toDomPrecision(width)}
height={toDomPrecision(Math.max(1, targetSizeY * 2))}
style={isDefaultCursor ? { cursor: getCursor('ns-resize', rotation) } : undefined}
{...bottomEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideHorizontalEdgeTargets,
})}
data-testid="selection.resize.left"
aria-label="left target"
pointerEvents="all"
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 2 : targetSizeX))}
y={0}
height={toDomPrecision(height)}
width={toDomPrecision(Math.max(1, targetSizeX * 2))}
style={isDefaultCursor ? { cursor: getCursor('ew-resize', rotation) } : undefined}
{...leftEvents}
/>
{/* Corner Targets */}
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideTopLeftCorner,
})}
data-testid="selection.target.top-left"
aria-label="top-left target"
pointerEvents="all"
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 2 : targetSizeX * 1.5))}
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY * 1.5))}
width={toDomPrecision(targetSizeX * 3)}
height={toDomPrecision(targetSizeY * 3)}
style={isDefaultCursor ? { cursor: getCursor('nwse-resize', rotation) } : undefined}
{...topLeftEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideTopRightCorner,
})}
data-testid="selection.target.top-right"
aria-label="top-right target"
pointerEvents="all"
x={toDomPrecision(width - (isSmallX ? 0 : targetSizeX * 1.5))}
y={toDomPrecision(0 - (isSmallY ? targetSizeY * 2 : targetSizeY * 1.5))}
width={toDomPrecision(targetSizeX * 3)}
height={toDomPrecision(targetSizeY * 3)}
style={isDefaultCursor ? { cursor: getCursor('nesw-resize', rotation) } : undefined}
{...topRightEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideBottomRightCorner,
})}
data-testid="selection.target.bottom-right"
aria-label="bottom-right target"
pointerEvents="all"
x={toDomPrecision(width - (isSmallX ? targetSizeX : targetSizeX * 1.5))}
y={toDomPrecision(height - (isSmallY ? targetSizeY : targetSizeY * 1.5))}
width={toDomPrecision(targetSizeX * 3)}
height={toDomPrecision(targetSizeY * 3)}
style={isDefaultCursor ? { cursor: getCursor('nwse-resize', rotation) } : undefined}
{...bottomRightEvents}
/>
<rect
className={classNames('tl-transparent', {
'tl-hidden': hideBottomLeftCorner,
})}
data-testid="selection.target.bottom-left"
aria-label="bottom-left target"
pointerEvents="all"
x={toDomPrecision(0 - (isSmallX ? targetSizeX * 3 : targetSizeX * 1.5))}
y={toDomPrecision(height - (isSmallY ? 0 : targetSizeY * 1.5))}
width={toDomPrecision(targetSizeX * 3)}
height={toDomPrecision(targetSizeY * 3)}
style={isDefaultCursor ? { cursor: getCursor('nesw-resize', rotation) } : undefined}
{...bottomLeftEvents}
/>
{/* Resize Handles */}
{showResizeHandles && (
<>
<rect
data-testid="selection.resize.top-left"
className={classNames('tl-corner-handle', {
'tl-hidden': hideTopLeftCorner,
})}
aria-label="top_left handle"
x={toDomPrecision(0 - size / 2)}
y={toDomPrecision(0 - size / 2)}
width={toDomPrecision(size)}
height={toDomPrecision(size)}
/>
<rect
data-testid="selection.resize.top-right"
className={classNames('tl-corner-handle', {
'tl-hidden': hideTopRightCorner,
})}
aria-label="top_right handle"
x={toDomPrecision(width - size / 2)}
y={toDomPrecision(0 - size / 2)}
width={toDomPrecision(size)}
height={toDomPrecision(size)}
/>
<rect
data-testid="selection.resize.bottom-right"
className={classNames('tl-corner-handle', {
'tl-hidden': hideBottomRightCorner,
})}
aria-label="bottom_right handle"
x={toDomPrecision(width - size / 2)}
y={toDomPrecision(height - size / 2)}
width={toDomPrecision(size)}
height={toDomPrecision(size)}
/>
<rect
data-testid="selection.resize.bottom-left"
className={classNames('tl-corner-handle', {
'tl-hidden': hideBottomLeftCorner,
})}
aria-label="bottom_left handle"
x={toDomPrecision(0 - size / 2)}
y={toDomPrecision(height - size / 2)}
width={toDomPrecision(size)}
height={toDomPrecision(size)}
/>
</>
)}
{showTextResizeHandles && (
<>
<rect
data-testid="selection.text-resize.left.handle"
className="tl-text-handle"
aria-label="bottom_left handle"
x={toDomPrecision(0 - size / 4)}
y={toDomPrecision(height / 2 - textHandleHeight / 2)}
rx={size / 4}
width={toDomPrecision(size / 2)}
height={toDomPrecision(textHandleHeight)}
/>
<rect
data-testid="selection.text-resize.right.handle"
className="tl-text-handle"
aria-label="bottom_left handle"
rx={size / 4}
x={toDomPrecision(width - size / 4)}
y={toDomPrecision(height / 2 - textHandleHeight / 2)}
width={toDomPrecision(size / 2)}
height={toDomPrecision(textHandleHeight)}
/>
</>
)}
{/* Crop Handles */}
{showCropHandles && (
<TldrawCropHandles
{...{
size,
width,
height,
hideAlternateHandles: hideAlternateCropHandles,
}}
/>
)}
</g>
</svg>
)
})
export const RotateCornerHandle = function RotateCornerHandle({ export const RotateCornerHandle = function RotateCornerHandle({
cx, cx,

View file

@ -79,6 +79,16 @@
margin-left: var(--space-2); margin-left: var(--space-2);
} }
.tlui-button[data-state='hinted']::after {
background-color: var(--color-hint);
opacity: 1;
}
.tlui-button[data-state='hinted']:not(:disabled, :focus-visible):active:after {
background: var(--color-hint);
opacity: 1;
}
@media (hover: hover) { @media (hover: hover) {
.tlui-button::after { .tlui-button::after {
background-color: var(--color-muted-2); background-color: var(--color-muted-2);
@ -136,7 +146,6 @@
.tlui-button__menu { .tlui-button__menu {
height: 40px; height: 40px;
min-height: 40px; min-height: 40px;
min-width: 128px;
width: 100%; width: 100%;
gap: 8px; gap: 8px;
margin: -4px 0px; margin: -4px 0px;
@ -929,6 +938,10 @@
stroke-width: 1px; stroke-width: 1px;
} }
.tlui-menu__group {
width: 100%;
}
.tlui-menu__group:empty { .tlui-menu__group:empty {
display: none; display: none;
} }
@ -952,25 +965,22 @@
/* Menu Sizes */ /* Menu Sizes */
.tlui-menu[data-size='large'] > .tlui-menu__group, .tlui-menu[data-size='large'] > .tlui-menu__group {
.tlui-menu__submenu__content[data-size='large'] > .tlui-menu__group {
min-width: initial; min-width: initial;
} }
.tlui-menu[data-size='medium'] > .tlui-menu__group, .tlui-menu[data-size='medium'] > .tlui-menu__group {
.tlui-menu__submenu__content[data-size='medium'] > .tlui-menu__group {
min-width: 144px; min-width: 144px;
} }
.tlui-menu[data-size='small'] > .tlui-menu__group, .tlui-menu[data-size='small'] > .tlui-menu__group {
.tlui-menu__submenu__content[data-size='small'] > .tlui-menu__group {
min-width: 96px;
}
.tlui-menu[data-size='tiny'] > .tlui-menu__group,
.tlui-menu__submenu__content[data-size='tiny'] > .tlui-menu__group {
min-width: 0px; min-width: 0px;
} }
.tlui-menu[data-size='tiny'] > .tlui-menu__group {
min-width: 0px;
}
/* ------------------ Actions Menu ------------------ */ /* ------------------ Actions Menu ------------------ */
.tlui-actions-menu { .tlui-actions-menu {

View file

@ -21,7 +21,6 @@ import { useNativeClipboardEvents } from './hooks/useClipboardEvents'
import { useEditorEvents } from './hooks/useEditorEvents' import { useEditorEvents } from './hooks/useEditorEvents'
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts' import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
import { useReadonly } from './hooks/useReadonly' import { useReadonly } from './hooks/useReadonly'
import { useRelevantStyles } from './hooks/useRevelantStyles'
import { useTranslation } from './hooks/useTranslation/useTranslation' import { useTranslation } from './hooks/useTranslation/useTranslation'
/** /**
@ -159,7 +158,7 @@ const TldrawUiContent = React.memo(function TldrawUI() {
<div className="tlui-layout__top__right"> <div className="tlui-layout__top__right">
{SharePanel && <SharePanel />} {SharePanel && <SharePanel />}
{StylePanel && breakpoint >= PORTRAIT_BREAKPOINT.TABLET_SM && !isReadonlyMode && ( {StylePanel && breakpoint >= PORTRAIT_BREAKPOINT.TABLET_SM && !isReadonlyMode && (
<_StylePanel /> <StylePanel />
)} )}
</div> </div>
</div> </div>
@ -181,11 +180,3 @@ const TldrawUiContent = React.memo(function TldrawUI() {
</ToastProvider> </ToastProvider>
) )
}) })
function _StylePanel() {
const { StylePanel } = useTldrawUiComponents()
const relevantStyles = useRelevantStyles()
if (!StylePanel) return null
return <StylePanel relevantStyles={relevantStyles} />
}

View file

@ -1,5 +1,5 @@
import * as _ContextMenu from '@radix-ui/react-context-menu' import * as _ContextMenu from '@radix-ui/react-context-menu'
import { preventDefault, useContainer, useEditor } from '@tldraw/editor' import { preventDefault, useContainer, useEditor, useEditorComponents } from '@tldraw/editor'
import { memo, useCallback } from 'react' import { memo, useCallback } from 'react'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen' import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext' import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
@ -7,17 +7,17 @@ import { DefaultContextMenuContent } from './DefaultContextMenuContent'
/** @public */ /** @public */
export interface TLUiContextMenuProps { export interface TLUiContextMenuProps {
canvas: any
children?: any children?: any
} }
/** @public */ /** @public */
export const DefaultContextMenu = memo(function DefaultContextMenu({ export const DefaultContextMenu = memo(function DefaultContextMenu({
canvas,
children, children,
}: TLUiContextMenuProps) { }: TLUiContextMenuProps) {
const editor = useEditor() const editor = useEditor()
const { Canvas } = useEditorComponents()
const cb = useCallback( const cb = useCallback(
(isOpen: boolean) => { (isOpen: boolean) => {
if (!isOpen) { if (!isOpen) {
@ -68,7 +68,7 @@ export const DefaultContextMenu = memo(function DefaultContextMenu({
return ( return (
<_ContextMenu.Root dir="ltr" onOpenChange={handleOpenChange} modal={false}> <_ContextMenu.Root dir="ltr" onOpenChange={handleOpenChange} modal={false}>
<_ContextMenu.Trigger onContextMenu={undefined} dir="ltr"> <_ContextMenu.Trigger onContextMenu={undefined} dir="ltr">
{canvas} {Canvas ? <Canvas /> : null}
</_ContextMenu.Trigger> </_ContextMenu.Trigger>
{isOpen && ( {isOpen && (
<_ContextMenu.Portal container={container}> <_ContextMenu.Portal container={container}>

View file

@ -22,14 +22,14 @@ export function MobileStylePanel() {
const msg = useTranslation() const msg = useTranslation()
const relevantStyles = useRelevantStyles() const relevantStyles = useRelevantStyles()
const color = relevantStyles?.styles.get(DefaultColorStyle) const color = relevantStyles?.get(DefaultColorStyle)
const theme = getDefaultColorTheme({ isDarkMode: editor.user.getIsDarkMode() }) const theme = getDefaultColorTheme({ isDarkMode: editor.user.getIsDarkMode() })
const currentColor = ( const currentColor = (
color?.type === 'shared' ? theme[color.value as TLDefaultColorStyle] : theme.black color?.type === 'shared' ? theme[color.value as TLDefaultColorStyle] : theme.black
).solid ).solid
const disableStylePanel = useValue( const disableStylePanel = useValue(
'isHandOrEraserToolActive', 'disable style panel',
() => editor.isInAny('hand', 'zoom', 'eraser', 'laser'), () => editor.isInAny('hand', 'zoom', 'eraser', 'laser'),
[editor] [editor]
) )
@ -64,16 +64,8 @@ export function MobileStylePanel() {
</TldrawUiButton> </TldrawUiButton>
</TldrawUiPopoverTrigger> </TldrawUiPopoverTrigger>
<TldrawUiPopoverContent side="top" align="end"> <TldrawUiPopoverContent side="top" align="end">
<_StylePanel /> {StylePanel && <StylePanel isMobile />}
</TldrawUiPopoverContent> </TldrawUiPopoverContent>
</TldrawUiPopover> </TldrawUiPopover>
) )
} }
function _StylePanel() {
const { StylePanel } = useTldrawUiComponents()
const relevantStyles = useRelevantStyles()
if (!StylePanel) return null
return <StylePanel relevantStyles={relevantStyles} isMobile />
}

View file

@ -1,35 +1,31 @@
import { ReadonlySharedStyleMap, SharedStyle, useEditor } from '@tldraw/editor' import { useEditor } from '@tldraw/editor'
import classNames from 'classnames' import classNames from 'classnames'
import { memo, useCallback } from 'react' import { memo, useCallback } from 'react'
import { useRelevantStyles } from '../../hooks/useRevelantStyles'
import { DefaultStylePanelContent } from './DefaultStylePanelContent' import { DefaultStylePanelContent } from './DefaultStylePanelContent'
/** @public */ /** @public */
export interface TLUiStylePanelProps { export interface TLUiStylePanelProps {
isMobile?: boolean isMobile?: boolean
children?: any children?: any
relevantStyles: {
styles: ReadonlySharedStyleMap
opacity: SharedStyle<number>
} | null
} }
/** @public */ /** @public */
export const DefaultStylePanel = memo(function DefaultStylePanel({ export const DefaultStylePanel = memo(function DefaultStylePanel({
isMobile, isMobile,
children, children,
relevantStyles,
}: TLUiStylePanelProps) { }: TLUiStylePanelProps) {
const editor = useEditor() const editor = useEditor()
const styles = useRelevantStyles()
const handlePointerOut = useCallback(() => { const handlePointerOut = useCallback(() => {
if (!isMobile) { if (!isMobile) {
editor.updateInstanceState({ isChangingStyle: false }) editor.updateInstanceState({ isChangingStyle: false })
} }
}, [editor, isMobile]) }, [editor, isMobile])
if (!relevantStyles) return null const content = children ?? <DefaultStylePanelContent styles={styles} />
const content = children ?? <DefaultStylePanelContent relevantStyles={relevantStyles} />
return ( return (
<div <div

View file

@ -11,10 +11,10 @@ import {
GeoShapeGeoStyle, GeoShapeGeoStyle,
LineShapeSplineStyle, LineShapeSplineStyle,
ReadonlySharedStyleMap, ReadonlySharedStyleMap,
SharedStyle,
StyleProp, StyleProp,
minBy, minBy,
useEditor, useEditor,
useValue,
} from '@tldraw/editor' } from '@tldraw/editor'
import React from 'react' import React from 'react'
import { STYLES } from '../../../styles' import { STYLES } from '../../../styles'
@ -30,14 +30,13 @@ import { DropdownPicker } from './DropdownPicker'
/** @public */ /** @public */
export type TLUiStylePanelContentProps = { export type TLUiStylePanelContentProps = {
relevantStyles: ReturnType<typeof useRelevantStyles> styles: ReturnType<typeof useRelevantStyles>
} }
/** @public */ /** @public */
export function DefaultStylePanelContent({ relevantStyles }: TLUiStylePanelContentProps) { export function DefaultStylePanelContent({ styles }: TLUiStylePanelContentProps) {
if (!relevantStyles) return null if (!styles) return null
const { styles, opacity } = relevantStyles
const geo = styles.get(GeoShapeGeoStyle) const geo = styles.get(GeoShapeGeoStyle)
const arrowheadEnd = styles.get(ArrowShapeArrowheadEndStyle) const arrowheadEnd = styles.get(ArrowShapeArrowheadEndStyle)
const arrowheadStart = styles.get(ArrowShapeArrowheadStartStyle) const arrowheadStart = styles.get(ArrowShapeArrowheadStartStyle)
@ -51,7 +50,7 @@ export function DefaultStylePanelContent({ relevantStyles }: TLUiStylePanelConte
return ( return (
<> <>
<CommonStylePickerSet styles={styles} opacity={opacity} /> <CommonStylePickerSet styles={styles} />
{!hideText && <TextStylePickerSet styles={styles} />} {!hideText && <TextStylePickerSet styles={styles} />}
{!(hideGeo && hideArrowHeads && hideSpline) && ( {!(hideGeo && hideArrowHeads && hideSpline) && (
<div className="tlui-style-panel__section" aria-label="style panel styles"> <div className="tlui-style-panel__section" aria-label="style panel styles">
@ -68,52 +67,28 @@ function useStyleChangeCallback() {
const editor = useEditor() const editor = useEditor()
const trackEvent = useUiEvents() const trackEvent = useUiEvents()
return React.useMemo(() => { return React.useMemo(
return function handleStyleChange<T>(style: StyleProp<T>, value: T, squashing: boolean) { () =>
editor.batch(() => { function handleStyleChange<T>(style: StyleProp<T>, value: T, squashing: boolean) {
if (editor.isIn('select')) { editor.batch(() => {
editor.setStyleForSelectedShapes(style, value, { squashing }) if (editor.isIn('select')) {
} editor.setStyleForSelectedShapes(style, value, { squashing })
editor.setStyleForNextShapes(style, value, { squashing }) }
editor.updateInstanceState({ isChangingStyle: true }) editor.setStyleForNextShapes(style, value, { squashing })
}) editor.updateInstanceState({ isChangingStyle: true })
})
trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string }) trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string })
} },
}, [editor, trackEvent]) [editor, trackEvent]
)
} }
const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const function CommonStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
function CommonStylePickerSet({
styles,
opacity,
}: {
styles: ReadonlySharedStyleMap
opacity: SharedStyle<number>
}) {
const editor = useEditor()
const trackEvent = useUiEvents()
const msg = useTranslation() const msg = useTranslation()
const handleValueChange = useStyleChangeCallback() const handleValueChange = useStyleChangeCallback()
const handleOpacityValueChange = React.useCallback(
(value: number, ephemeral: boolean) => {
const item = tldrawSupportedOpacities[value]
editor.batch(() => {
if (editor.isIn('select')) {
editor.setOpacityForSelectedShapes(item, { ephemeral })
}
editor.setOpacityForNextShapes(item, { ephemeral })
editor.updateInstanceState({ isChangingStyle: true })
})
trackEvent('set-style', { source: 'style-panel', id: 'opacity', value })
},
[editor, trackEvent]
)
const color = styles.get(DefaultColorStyle) const color = styles.get(DefaultColorStyle)
const fill = styles.get(DefaultFillStyle) const fill = styles.get(DefaultFillStyle)
const dash = styles.get(DefaultDashStyle) const dash = styles.get(DefaultDashStyle)
@ -121,15 +96,6 @@ function CommonStylePickerSet({
const showPickers = fill !== undefined || dash !== undefined || size !== undefined const showPickers = fill !== undefined || dash !== undefined || size !== undefined
const opacityIndex =
opacity.type === 'mixed'
? -1
: tldrawSupportedOpacities.indexOf(
minBy(tldrawSupportedOpacities, (supportedOpacity) =>
Math.abs(supportedOpacity - opacity.value)
)!
)
return ( return (
<> <>
<div <div
@ -148,18 +114,7 @@ function CommonStylePickerSet({
onValueChange={handleValueChange} onValueChange={handleValueChange}
/> />
)} )}
{opacity === undefined ? null : ( <OpacitySlider />
<TldrawUiSlider
data-testid="style.opacity"
value={opacityIndex >= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
label={
opacity.type === 'mixed' ? 'style-panel.mixed' : `opacity-style.${opacity.value}`
}
onValueChange={handleOpacityValueChange}
steps={tldrawSupportedOpacities.length - 1}
title={msg('style-panel.opacity')}
/>
)}
</div> </div>
{showPickers && ( {showPickers && (
<div className="tlui-style-panel__section" aria-label="style panel styles"> <div className="tlui-style-panel__section" aria-label="style panel styles">
@ -331,3 +286,50 @@ function ArrowheadStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap })
/> />
) )
} }
const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
function OpacitySlider() {
const editor = useEditor()
const opacity = useValue('opacity', () => editor.getSharedOpacity(), [editor])
const trackEvent = useUiEvents()
const msg = useTranslation()
const handleOpacityValueChange = React.useCallback(
(value: number, ephemeral: boolean) => {
const item = tldrawSupportedOpacities[value]
editor.batch(() => {
if (editor.isIn('select')) {
editor.setOpacityForSelectedShapes(item, { ephemeral })
}
editor.setOpacityForNextShapes(item, { ephemeral })
editor.updateInstanceState({ isChangingStyle: true })
})
trackEvent('set-style', { source: 'style-panel', id: 'opacity', value })
},
[editor, trackEvent]
)
if (opacity === undefined) return null
const opacityIndex =
opacity.type === 'mixed'
? -1
: tldrawSupportedOpacities.indexOf(
minBy(tldrawSupportedOpacities, (supportedOpacity) =>
Math.abs(supportedOpacity - opacity.value)
)!
)
return (
<TldrawUiSlider
data-testid="style.opacity"
value={opacityIndex >= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
label={opacity.type === 'mixed' ? 'style-panel.mixed' : `opacity-style.${opacity.value}`}
onValueChange={handleOpacityValueChange}
steps={tldrawSupportedOpacities.length - 1}
title={msg('style-panel.opacity')}
/>
)
}

View file

@ -282,7 +282,7 @@ export function ArrangeMenuSubmenu() {
if (!(twoSelected || onlyFlippableShapeSelected)) return null if (!(twoSelected || onlyFlippableShapeSelected)) return null
return ( return (
<TldrawUiMenuSubmenu id="arrange" label="context-menu.arrange"> <TldrawUiMenuSubmenu id="arrange" label="context-menu.arrange" size="small">
{twoSelected && ( {twoSelected && (
<TldrawUiMenuGroup id="align"> <TldrawUiMenuGroup id="align">
<TldrawUiMenuItem {...actions['align-left']} /> <TldrawUiMenuItem {...actions['align-left']} />

View file

@ -139,6 +139,7 @@ export type TLUiDropdownMenuSubContentProps = {
id?: string id?: string
alignOffset?: number alignOffset?: number
sideOffset?: number sideOffset?: number
size?: 'tiny' | 'small' | 'medium' | 'wide'
children: any children: any
} }
@ -146,6 +147,7 @@ export type TLUiDropdownMenuSubContentProps = {
export function TldrawUiDropdownMenuSubContent({ export function TldrawUiDropdownMenuSubContent({
alignOffset = -1, alignOffset = -1,
sideOffset = -4, sideOffset = -4,
size = 'small',
children, children,
}: TLUiDropdownMenuSubContentProps) { }: TLUiDropdownMenuSubContentProps) {
const container = useContainer() const container = useContainer()
@ -156,6 +158,7 @@ export function TldrawUiDropdownMenuSubContent({
alignOffset={alignOffset} alignOffset={alignOffset}
sideOffset={sideOffset} sideOffset={sideOffset}
collisionPadding={4} collisionPadding={4}
data-size={size}
> >
{children} {children}
</_DropdownMenu.SubContent> </_DropdownMenu.SubContent>
@ -166,16 +169,12 @@ export function TldrawUiDropdownMenuSubContent({
/** @public */ /** @public */
export type TLUiDropdownMenuGroupProps = { export type TLUiDropdownMenuGroupProps = {
children: any children: any
size?: 'tiny' | 'small' | 'medium' | 'wide'
} }
/** @public */ /** @public */
export function TldrawUiDropdownMenuGroup({ export function TldrawUiDropdownMenuGroup({ children }: TLUiDropdownMenuGroupProps) {
children,
size = 'medium',
}: TLUiDropdownMenuGroupProps) {
return ( return (
<_DropdownMenu.Group dir="ltr" className="tlui-menu__group" data-size={size}> <_DropdownMenu.Group dir="ltr" className="tlui-menu__group">
{children} {children}
</_DropdownMenu.Group> </_DropdownMenu.Group>
) )

View file

@ -12,12 +12,11 @@ export type TLUiMenuGroupProps<TranslationKey extends string = string> = {
* The label to display on the item. If it's a string, it will be translated. If it's an object, the keys will be used as the language keys and the values will be translated. * The label to display on the item. If it's a string, it will be translated. If it's an object, the keys will be used as the language keys and the values will be translated.
*/ */
label?: TranslationKey | { [key: string]: TranslationKey } label?: TranslationKey | { [key: string]: TranslationKey }
small?: boolean
children?: any children?: any
} }
/** @public */ /** @public */
export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMenuGroupProps) { export function TldrawUiMenuGroup({ id, label, children }: TLUiMenuGroupProps) {
const { type: menuType, sourceId } = useTldrawUiMenuContext() const { type: menuType, sourceId } = useTldrawUiMenuContext()
const msg = useTranslation() const msg = useTranslation()
const labelToUse = unwrapLabel(label, menuType) const labelToUse = unwrapLabel(label, menuType)
@ -33,10 +32,7 @@ export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMe
} }
case 'menu': { case 'menu': {
return ( return (
<TldrawUiDropdownMenuGroup <TldrawUiDropdownMenuGroup data-testid={`${sourceId}-group.${id}`}>
data-testid={`${sourceId}-group.${id}`}
size={small ? 'tiny' : 'medium'}
>
{children} {children}
</TldrawUiDropdownMenuGroup> </TldrawUiDropdownMenuGroup>
) )
@ -46,7 +42,6 @@ export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMe
<ContextMenuGroup <ContextMenuGroup
dir="ltr" dir="ltr"
className="tlui-menu__group" className="tlui-menu__group"
data-size={small ? 'tiny' : 'medium'}
data-testid={`${sourceId}-group.${id}`} data-testid={`${sourceId}-group.${id}`}
> >
{children} {children}

View file

@ -24,7 +24,7 @@ export type TLUiMenuSubmenuProps<Translation extends string = string> = {
label?: Translation | { [key: string]: Translation } label?: Translation | { [key: string]: Translation }
disabled?: boolean disabled?: boolean
children: any children: any
size?: 'tiny' | 'small' | 'medium' | 'large' size?: 'tiny' | 'small' | 'medium' | 'wide'
} }
/** @public */ /** @public */
@ -32,7 +32,7 @@ export function TldrawUiMenuSubmenu<Translation extends string = string>({
id, id,
disabled = false, disabled = false,
label, label,
size, size = 'small',
children, children,
}: TLUiMenuSubmenuProps<Translation>) { }: TLUiMenuSubmenuProps<Translation>) {
const { type: menuType, sourceId } = useTldrawUiMenuContext() const { type: menuType, sourceId } = useTldrawUiMenuContext()
@ -55,7 +55,7 @@ export function TldrawUiMenuSubmenu<Translation extends string = string>({
label={labelStr!} label={labelStr!}
title={labelStr!} title={labelStr!}
/> />
<TldrawUiDropdownMenuSubContent id={`${sourceId}-sub-content.${id}`} data-size={size}> <TldrawUiDropdownMenuSubContent id={`${sourceId}-sub-content.${id}`} size={size}>
{children} {children}
</TldrawUiDropdownMenuSubContent> </TldrawUiDropdownMenuSubContent>
</TldrawUiDropdownMenuSub> </TldrawUiDropdownMenuSub>

View file

@ -4,18 +4,20 @@ import {
DefaultFillStyle, DefaultFillStyle,
DefaultSizeStyle, DefaultSizeStyle,
ReadonlySharedStyleMap, ReadonlySharedStyleMap,
SharedStyle,
SharedStyleMap, SharedStyleMap,
useEditor, useEditor,
useValue, useValue,
} from '@tldraw/editor' } from '@tldraw/editor'
const selectToolStyles = [DefaultColorStyle, DefaultDashStyle, DefaultFillStyle, DefaultSizeStyle] const selectToolStyles = Object.freeze([
DefaultColorStyle,
DefaultDashStyle,
DefaultFillStyle,
DefaultSizeStyle,
])
export function useRelevantStyles(): { /** @public */
styles: ReadonlySharedStyleMap export function useRelevantStyles(stylesToCheck = selectToolStyles): ReadonlySharedStyleMap | null {
opacity: SharedStyle<number>
} | null {
const editor = useEditor() const editor = useEditor()
return useValue( return useValue(
'getRelevantStyles', 'getRelevantStyles',
@ -25,13 +27,13 @@ export function useRelevantStyles(): {
editor.getSelectedShapeIds().length > 0 || !!editor.root.getCurrent()?.shapeType editor.getSelectedShapeIds().length > 0 || !!editor.root.getCurrent()?.shapeType
if (styles.size === 0 && editor.isIn('select') && editor.getSelectedShapeIds().length === 0) { if (styles.size === 0 && editor.isIn('select') && editor.getSelectedShapeIds().length === 0) {
for (const style of selectToolStyles) { for (const style of stylesToCheck) {
styles.applyValue(style, editor.getStyleForNextShape(style)) styles.applyValue(style, editor.getStyleForNextShape(style))
} }
} }
if (styles.size === 0 && !hasShape) return null if (styles.size === 0 && !hasShape) return null
return { styles, opacity: editor.getSharedOpacity() } return styles
}, },
[editor] [editor]
) )

View file

@ -2,7 +2,6 @@ import { act, render, screen } from '@testing-library/react'
import { import {
BaseBoxShapeTool, BaseBoxShapeTool,
BaseBoxShapeUtil, BaseBoxShapeUtil,
Canvas,
Editor, Editor,
HTMLContainer, HTMLContainer,
TLBaseShape, TLBaseShape,
@ -26,13 +25,10 @@ function checkAllShapes(editor: Editor, shapes: string[]) {
describe('<TldrawEditor />', () => { describe('<TldrawEditor />', () => {
it('Renders without crashing', async () => { it('Renders without crashing', async () => {
await renderTldrawComponent( await renderTldrawComponent(
<TldrawEditor tools={defaultTools} autoFocus initialState="select"> <TldrawEditor tools={defaultTools} autoFocus initialState="select" />,
<div data-testid="canvas-1" />
<Canvas />
</TldrawEditor>,
{ waitForPatterns: false } { waitForPatterns: false }
) )
await screen.findByTestId('canvas-1') await screen.findByTestId('canvas')
}) })
it('Creates its own store with core shapes', async () => { it('Creates its own store with core shapes', async () => {
@ -45,12 +41,9 @@ describe('<TldrawEditor />', () => {
initialState="select" initialState="select"
tools={defaultTools} tools={defaultTools}
autoFocus autoFocus
> />,
<div data-testid="canvas-1" />
</TldrawEditor>,
{ waitForPatterns: false } { waitForPatterns: false }
) )
await screen.findByTestId('canvas-1')
checkAllShapes(editor!, ['group']) checkAllShapes(editor!, ['group'])
}) })
@ -65,13 +58,9 @@ describe('<TldrawEditor />', () => {
editor = e editor = e
}} }}
autoFocus autoFocus
> />,
<div data-testid="canvas-1" />
<Canvas />
</TldrawEditor>,
{ waitForPatterns: false } { waitForPatterns: false }
) )
await screen.findByTestId('canvas-1')
expect(editor!).toBeTruthy() expect(editor!).toBeTruthy()
checkAllShapes(editor!, ['group']) checkAllShapes(editor!, ['group'])
@ -88,13 +77,9 @@ describe('<TldrawEditor />', () => {
expect(editor.store).toBe(store) expect(editor.store).toBe(store)
}} }}
autoFocus autoFocus
> />,
<div data-testid="canvas-1" />
<Canvas />
</TldrawEditor>,
{ waitForPatterns: false } { waitForPatterns: false }
) )
await screen.findByTestId('canvas-1')
}) })
it('throws if the store has different shapes to the ones passed in', async () => { it('throws if the store has different shapes to the ones passed in', async () => {
@ -148,11 +133,8 @@ describe('<TldrawEditor />', () => {
store={initialStore} store={initialStore}
onMount={onMount} onMount={onMount}
autoFocus autoFocus
> />
<div data-testid="canvas-1" />
</TldrawEditor>
) )
await screen.findByTestId('canvas-1')
const initialEditor = onMount.mock.lastCall[0] const initialEditor = onMount.mock.lastCall[0]
jest.spyOn(initialEditor, 'dispose') jest.spyOn(initialEditor, 'dispose')
expect(initialEditor.store).toBe(initialStore) expect(initialEditor.store).toBe(initialStore)
@ -164,11 +146,8 @@ describe('<TldrawEditor />', () => {
store={initialStore} store={initialStore}
onMount={onMount} onMount={onMount}
autoFocus autoFocus
> />
<div data-testid="canvas-2" />
</TldrawEditor>
) )
await screen.findByTestId('canvas-2')
// not called again: // not called again:
expect(onMount).toHaveBeenCalledTimes(1) expect(onMount).toHaveBeenCalledTimes(1)
// re-render with a new store: // re-render with a new store:
@ -180,11 +159,8 @@ describe('<TldrawEditor />', () => {
store={newStore} store={newStore}
onMount={onMount} onMount={onMount}
autoFocus autoFocus
> />
<div data-testid="canvas-3" />
</TldrawEditor>
) )
await screen.findByTestId('canvas-3')
expect(initialEditor.dispose).toHaveBeenCalledTimes(1) expect(initialEditor.dispose).toHaveBeenCalledTimes(1)
expect(onMount).toHaveBeenCalledTimes(2) expect(onMount).toHaveBeenCalledTimes(2)
expect(onMount.mock.lastCall[0].store).toBe(newStore) expect(onMount.mock.lastCall[0].store).toBe(newStore)
@ -201,12 +177,9 @@ describe('<TldrawEditor />', () => {
onMount={(editorApp) => { onMount={(editorApp) => {
editor = editorApp editor = editorApp
}} }}
> />,
<Canvas /> { waitForPatterns: false }
<div data-testid="canvas-1" />
</TldrawEditor>
) )
await screen.findByTestId('canvas-1')
expect(editor).toBeTruthy() expect(editor).toBeTruthy()
await act(async () => { await act(async () => {
@ -325,13 +298,9 @@ describe('Custom shapes', () => {
onMount={(editorApp) => { onMount={(editorApp) => {
editor = editorApp editor = editorApp
}} }}
> />,
<Canvas />
<div data-testid="canvas-1" />
</TldrawEditor>,
{ waitForPatterns: false } { waitForPatterns: false }
) )
await screen.findByTestId('canvas-1')
expect(editor).toBeTruthy() expect(editor).toBeTruthy()
await act(async () => { await act(async () => {

View file

@ -165,12 +165,10 @@ describe('Locked shapes', () => {
describe('Unlocking', () => { describe('Unlocking', () => {
it('Can unlock shapes', () => { it('Can unlock shapes', () => {
editor.setSelectedShapes([ids.lockedShapeA, ids.lockedShapeB]) editor.setSelectedShapes([ids.lockedShapeA, ids.lockedShapeB])
let lockedStatus = [ids.lockedShapeA, ids.lockedShapeB].map( const getLockedStatus = () =>
(id) => editor.getShape(id)!.isLocked [ids.lockedShapeA, ids.lockedShapeB].map((id) => editor.getShape(id)!.isLocked)
) expect(getLockedStatus()).toStrictEqual([true, true])
expect(lockedStatus).toStrictEqual([true, true])
editor.toggleLock(editor.getSelectedShapeIds()) editor.toggleLock(editor.getSelectedShapeIds())
lockedStatus = [ids.lockedShapeA, ids.lockedShapeB].map((id) => editor.getShape(id)!.isLocked) expect(getLockedStatus()).toStrictEqual([false, false])
expect(lockedStatus).toStrictEqual([false, false])
}) })
}) })

View file

@ -15,7 +15,7 @@ import { ReactElement } from 'react'
*/ */
export async function renderTldrawComponent( export async function renderTldrawComponent(
element: ReactElement, element: ReactElement,
{ waitForPatterns = true } = {} { waitForPatterns }: { waitForPatterns: boolean }
) { ) {
const result = render(element) const result = render(element)
if (waitForPatterns) await result.findByTestId('ready-pattern-fill-defs') if (waitForPatterns) await result.findByTestId('ready-pattern-fill-defs')
@ -24,7 +24,7 @@ export async function renderTldrawComponent(
export async function renderTldrawComponentWithEditor( export async function renderTldrawComponentWithEditor(
cb: (onMount: (editor: Editor) => void) => ReactElement, cb: (onMount: (editor: Editor) => void) => ReactElement,
opts?: { waitForPatterns?: boolean } opts: { waitForPatterns: boolean }
) { ) {
const editorPromise = promiseWithResolve<Editor>() const editorPromise = promiseWithResolve<Editor>()
const element = cb((editor) => { const element = cb((editor) => {

View file

@ -10,7 +10,8 @@ it('opens on right-click', async () => {
onMount={(editor) => { onMount={(editor) => {
editor.createShape({ id: createShapeId(), type: 'geo' }) editor.createShape({ id: createShapeId(), type: 'geo' })
}} }}
/> />,
{ waitForPatterns: false }
) )
const canvas = await screen.findByTestId('canvas') const canvas = await screen.findByTestId('canvas')
@ -38,7 +39,8 @@ it('tunnels context menu', async () => {
editor.createShape({ id: createShapeId(), type: 'geo' }) editor.createShape({ id: createShapeId(), type: 'geo' })
}} }}
components={components} components={components}
/> />,
{ waitForPatterns: false }
) )
const canvas = await screen.findByTestId('canvas') const canvas = await screen.findByTestId('canvas')