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

View file

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

View file

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

View file

@ -21,7 +21,7 @@ setDefaultEditorAssetUrls(assetUrls)
setDefaultUiAssetUrls(assetUrls)
const gettingStartedExamples = examples.find((e) => e.id === 'Getting Started')
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')
const router = createBrowserRouter([

View file

@ -306,11 +306,6 @@ export const CAMERA_SLIDE_FRICTION = 0.09;
// @public (undocumented)
export function canonicalizeRotation(a: number): number;
// @public (undocumented)
export function Canvas({ className }: {
className?: string;
}): JSX_2.Element;
// @public (undocumented)
export class Circle2d extends Geometry2d {
constructor(config: Omit<Geometry2dOptions, 'isClosed'> & {
@ -449,50 +444,49 @@ export const DEFAULT_ANIMATION_OPTIONS: {
export function DefaultBackground(): JSX_2.Element;
// @public (undocumented)
export const DefaultBrush: TLBrushComponent;
export const DefaultBrush: ({ brush, color, opacity, className }: TLBrushProps) => JSX_2.Element;
// @public (undocumented)
export const DefaultCollaboratorHint: TLCollaboratorHintComponent;
export function DefaultCanvas({ className }: TLCanvasComponentProps): JSX_2.Element;
// @public (undocumented)
export const DefaultCursor: NamedExoticComponent< {
className?: string | undefined;
point: null | VecModel;
zoom: number;
color?: string | undefined;
name: null | string;
chatMessage: string;
}>;
export function DefaultCollaboratorHint({ className, zoom, point, color, viewport, opacity, }: TLCollaboratorHintProps): JSX_2.Element;
// @public (undocumented)
export const DefaultCursor: NamedExoticComponent<TLCursorProps>;
// @public (undocumented)
export const DefaultErrorFallback: TLErrorFallbackComponent;
// @public (undocumented)
export const DefaultGrid: TLGridComponent;
export function DefaultGrid({ x, y, z, size }: TLGridProps): JSX_2.Element;
// @public (undocumented)
export const DefaultHandle: TLHandleComponent;
export function DefaultHandle({ handle, isCoarse, className, zoom }: TLHandleProps): JSX_2.Element;
// @public (undocumented)
export const DefaultHandles: TLHandlesComponent;
export const DefaultHandles: ({ children }: TLHandlesProps) => JSX_2.Element;
// @public (undocumented)
export const DefaultHoveredShapeIndicator: TLHoveredShapeIndicatorComponent;
export function DefaultHoveredShapeIndicator({ shapeId }: TLHoveredShapeIndicatorProps): JSX_2.Element | null;
// @public (undocumented)
export const DefaultScribble: TLScribbleComponent;
export function DefaultScribble({ scribble, zoom, color, opacity, className }: TLScribbleProps): JSX_2.Element | null;
// @public (undocumented)
export const DefaultSelectionBackground: TLSelectionBackgroundComponent;
export function DefaultSelectionBackground({ bounds, rotation }: TLSelectionBackgroundProps): JSX_2.Element;
// @public (undocumented)
export const DefaultSelectionForeground: TLSelectionForegroundComponent;
export function DefaultSelectionForeground({ bounds, rotation }: TLSelectionForegroundProps): JSX_2.Element;
// @public (undocumented)
export const DefaultSnapIndicator: TLSnapIndicatorComponent;
export const DefaultShapeIndicator: NamedExoticComponent<TLShapeIndicatorProps>;
// @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)
export const DefaultSvgDefs: () => null;
@ -1587,14 +1581,6 @@ export function setRuntimeOverrides(input: Partial<typeof runtime>): void;
// @public (undocumented)
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)
export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
constructor(editor: Editor);
@ -1898,9 +1884,6 @@ export type TLArrowPoint = {
arrowhead: TLArrowShapeArrowheadStyle;
};
// @public (undocumented)
export type TLBackgroundComponent = ComponentType;
// @public (undocumented)
export type TLBaseBoxShape = TLBaseShape<string, {
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;
// @public (undocumented)
export type TLBrushComponent = ComponentType<{
export type TLBrushProps = {
brush: BoxModel;
color?: string;
opacity?: number;
className?: string;
}>;
};
// @public (undocumented)
export type TLCancelEvent = (info: TLCancelEventInfo) => void;
@ -1965,14 +1948,14 @@ export type TLClickEventInfo = TLBaseEventInfo & {
export type TLCLickEventName = 'double_click' | 'quadruple_click' | 'triple_click';
// @public (undocumented)
export type TLCollaboratorHintComponent = ComponentType<{
export type TLCollaboratorHintProps = {
className?: string;
point: VecModel;
viewport: Box;
zoom: number;
opacity?: number;
color: string;
}>;
};
// @public (undocumented)
export type TLCommand<Name extends string = any, Data = any> = {
@ -2020,14 +2003,14 @@ export interface TLContent {
}
// @public (undocumented)
export type TLCursorComponent = ComponentType<{
export type TLCursorProps = {
className?: string;
point: null | VecModel;
zoom: number;
color?: string;
name: null | string;
chatMessage: string;
}>;
};
// @public (undocumented)
export const TldrawEditor: React_2.NamedExoticComponent<TldrawEditorProps>;
@ -2224,27 +2207,26 @@ export type TLExternalContentSource = {
};
// @public (undocumented)
export type TLGridComponent = ComponentType<{
export type TLGridProps = {
x: number;
y: number;
z: number;
size: number;
}>;
};
// @public (undocumented)
export type TLHandleComponent = ComponentType<{
export type TLHandleProps = {
shapeId: TLShapeId;
handle: TLHandle;
zoom: number;
isCoarse: boolean;
className?: string;
}>;
};
// @public (undocumented)
export type TLHandlesComponent = ComponentType<{
className?: string;
export type TLHandlesProps = {
children: any;
}>;
};
// @public (undocumented)
export type TLHistoryEntry = TLCommand | TLHistoryMark;
@ -2258,12 +2240,9 @@ export type TLHistoryMark = {
};
// @public (undocumented)
export type TLHoveredShapeIndicatorComponent = ComponentType<{
export type TLHoveredShapeIndicatorProps = {
shapeId: TLShapeId;
}>;
// @public (undocumented)
export type TLInFrontOfTheCanvas = ComponentType<object>;
};
// @public (undocumented)
export type TLInterruptEvent = (info: TLInterruptEventInfo) => void;
@ -2343,9 +2322,6 @@ export type TLOnRotateHandler<T extends TLShape> = TLEventChangeHandler<T>;
// @public (undocumented)
export type TLOnRotateStartHandler<T extends TLShape> = TLEventStartHandler<T>;
// @public (undocumented)
export type TLOnTheCanvas = ComponentType<object>;
// @public (undocumented)
export type TLOnTranslateEndHandler<T extends TLShape> = TLEventChangeHandler<T>;
@ -2442,25 +2418,25 @@ export type TLRotationSnapshot = {
};
// @public (undocumented)
export type TLScribbleComponent = ComponentType<{
export type TLScribbleProps = {
scribble: TLScribble;
zoom: number;
color?: string;
opacity?: number;
className?: string;
}>;
};
// @public (undocumented)
export type TLSelectionBackgroundComponent = React_3.ComponentType<{
export type TLSelectionBackgroundProps = {
bounds: Box;
rotation: number;
}>;
};
// @public (undocumented)
export type TLSelectionForegroundComponent = ComponentType<{
export type TLSelectionForegroundProps = {
bounds: Box;
rotation: number;
}>;
};
// @public (undocumented)
export type TLSelectionHandle = RotateCorner | SelectionCorner | SelectionEdge;
@ -2495,12 +2471,12 @@ export interface TLSessionStateSnapshot {
}
// @public (undocumented)
export type TLShapeIndicatorComponent = React_3.ComponentType<{
id: TLShapeId;
export type TLShapeIndicatorProps = {
shapeId: TLShapeId;
color?: string | undefined;
opacity?: number;
className?: string;
}>;
};
// @public (undocumented)
export interface TLShapeUtilCanvasSvgDef {
@ -2526,14 +2502,11 @@ export interface TLShapeUtilConstructor<T extends TLUnknownShape, U extends Shap
export type TLShapeUtilFlag<T> = (shape: T) => boolean;
// @public (undocumented)
export type TLSnapIndicatorComponent = React_3.ComponentType<{
export type TLSnapIndicatorProps = {
className?: string;
line: SnapIndicator;
zoom: number;
}>;
// @public (undocumented)
export type TLSpinnerComponent = ComponentType<object>;
};
// @public (undocumented)
export interface TLStateNodeConstructor {
@ -2584,9 +2557,6 @@ export type TLStoreWithStatus = {
readonly error?: undefined;
};
// @public (undocumented)
export type TLSvgDefsComponent = React.ComponentType;
// @public (undocumented)
export type TLSvgOptions = {
bounds: Box;
@ -2672,6 +2642,34 @@ export function useContainer(): HTMLDivElement;
// @public (undocumented)
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)
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 TldrawEditorProps,
} from './lib/TldrawEditor'
export { Canvas } from './lib/components/Canvas'
export {
ErrorBoundary,
OptionalErrorBoundary,
@ -42,63 +41,53 @@ export {
} from './lib/components/ErrorBoundary'
export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer'
export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer'
export { ShapeIndicator, type TLShapeIndicatorComponent } from './lib/components/ShapeIndicator'
export {
DefaultBackground,
type TLBackgroundComponent,
} from './lib/components/default-components/DefaultBackground'
export {
DefaultBrush,
type TLBrushComponent,
} from './lib/components/default-components/DefaultBrush'
export { DefaultBackground } from './lib/components/default-components/DefaultBackground'
export { DefaultBrush, type TLBrushProps } from './lib/components/default-components/DefaultBrush'
export { DefaultCanvas } from './lib/components/default-components/DefaultCanvas'
export {
DefaultCollaboratorHint,
type TLCollaboratorHintComponent,
type TLCollaboratorHintProps,
} from './lib/components/default-components/DefaultCollaboratorHint'
export {
DefaultCursor,
type TLCursorComponent,
type TLCursorProps,
} from './lib/components/default-components/DefaultCursor'
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 {
DefaultHandle,
type TLHandleComponent,
type TLHandleProps,
} from './lib/components/default-components/DefaultHandle'
export {
DefaultHandles,
type TLHandlesComponent,
type TLHandlesProps,
} from './lib/components/default-components/DefaultHandles'
export {
DefaultHoveredShapeIndicator,
type TLHoveredShapeIndicatorComponent,
type TLHoveredShapeIndicatorProps,
} 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 {
DefaultScribble,
type TLScribbleComponent,
type TLScribbleProps,
} from './lib/components/default-components/DefaultScribble'
export {
DefaultSelectionBackground,
type TLSelectionBackgroundComponent,
type TLSelectionBackgroundProps,
} from './lib/components/default-components/DefaultSelectionBackground'
export {
DefaultSelectionForeground,
type TLSelectionForegroundComponent,
type TLSelectionForegroundProps,
} from './lib/components/default-components/DefaultSelectionForeground'
export {
DefaultShapeIndicator,
type TLShapeIndicatorProps,
} from './lib/components/default-components/DefaultShapeIndicator'
export {
DefaultSnapIndicator,
type TLSnapIndicatorComponent,
type TLSnapIndicatorProps,
} from './lib/components/default-components/DefaultSnapIndictor'
export {
DefaultSpinner,
type TLSpinnerComponent,
} from './lib/components/default-components/DefaultSpinner'
export {
DefaultSvgDefs,
type TLSvgDefsComponent,
} from './lib/components/default-components/DefaultSvgDefs'
export { DefaultSpinner } from './lib/components/default-components/DefaultSpinner'
export { DefaultSvgDefs } from './lib/components/default-components/DefaultSvgDefs'
export {
TAB_ID,
createSessionStateSnapshotSignal,
@ -256,6 +245,7 @@ export { type TLResizeHandle, type TLSelectionHandle } from './lib/editor/types/
export { ContainerProvider, useContainer } from './lib/hooks/useContainer'
export { getCursor } from './lib/hooks/useCursor'
export { EditorContext, useEditor } from './lib/hooks/useEditor'
export { useEditorComponents } from './lib/hooks/useEditorComponents'
export type { TLEditorComponents } from './lib/hooks/useEditorComponents'
export { useShallowArrayIdentity, useShallowObjectIdentity } from './lib/hooks/useIdentity'
export { useIsCropping } from './lib/hooks/useIsCropping'

View file

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

View file

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

View file

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

View file

@ -1,18 +1,18 @@
import { BoxModel } from '@tldraw/tlschema'
import { ComponentType, useRef } from 'react'
import { useRef } from 'react'
import { useTransform } from '../../hooks/useTransform'
import { toDomPrecision } from '../../primitives/utils'
/** @public */
export type TLBrushComponent = ComponentType<{
export type TLBrushProps = {
brush: BoxModel
color?: string
opacity?: number
className?: string
}>
}
/** @public */
export const DefaultBrush: TLBrushComponent = ({ brush, color, opacity, className }) => {
export const DefaultBrush = ({ brush, color, opacity, className }: TLBrushProps) => {
const rSvg = useRef<SVGSVGElement>(null)
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 classNames from 'classnames'
import React from 'react'
import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS } from '../constants'
import { useCanvasEvents } from '../hooks/useCanvasEvents'
import { useCoarsePointer } from '../hooks/useCoarsePointer'
import { useDocumentEvents } from '../hooks/useDocumentEvents'
import { useEditor } from '../hooks/useEditor'
import { useEditorComponents } from '../hooks/useEditorComponents'
import { useFixSafariDoubleTapZoomPencilEvents } from '../hooks/useFixSafariDoubleTapZoomPencilEvents'
import { useGestureEvents } from '../hooks/useGestureEvents'
import { useHandleEvents } from '../hooks/useHandleEvents'
import { useScreenBounds } from '../hooks/useScreenBounds'
import { Mat } from '../primitives/Mat'
import { Vec } from '../primitives/Vec'
import { toDomPrecision } from '../primitives/utils'
import { debugFlags } from '../utils/debug-flags'
import { GeometryDebuggingView } from './GeometryDebuggingView'
import { LiveCollaborators } from './LiveCollaborators'
import { Shape } from './Shape'
import { ShapeIndicator } from './ShapeIndicator'
import { COARSE_HANDLE_RADIUS, HANDLE_RADIUS } from '../../constants'
import { useCanvasEvents } from '../../hooks/useCanvasEvents'
import { useCoarsePointer } from '../../hooks/useCoarsePointer'
import { useDocumentEvents } from '../../hooks/useDocumentEvents'
import { useEditor } from '../../hooks/useEditor'
import { useEditorComponents } from '../../hooks/useEditorComponents'
import { useFixSafariDoubleTapZoomPencilEvents } from '../../hooks/useFixSafariDoubleTapZoomPencilEvents'
import { useGestureEvents } from '../../hooks/useGestureEvents'
import { useHandleEvents } from '../../hooks/useHandleEvents'
import { useScreenBounds } from '../../hooks/useScreenBounds'
import { Mat } from '../../primitives/Mat'
import { Vec } from '../../primitives/Vec'
import { toDomPrecision } from '../../primitives/utils'
import { debugFlags } from '../../utils/debug-flags'
import { GeometryDebuggingView } from '../GeometryDebuggingView'
import { LiveCollaborators } from '../LiveCollaborators'
import { Shape } from '../Shape'
/** @public */
export function Canvas({ className }: { className?: string }) {
export type TLCanvasComponentProps = { className?: string }
/** @public */
export function DefaultCanvas({ className }: TLCanvasComponentProps) {
const editor = useEditor()
const { Background, SvgDefs } = useEditorComponents()
@ -378,12 +380,19 @@ function SelectedIdIndicators() {
[editor]
)
const { ShapeIndicator } = useEditorComponents()
if (!ShapeIndicator) return null
if (!shouldDisplay) return null
return (
<>
{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 editor = useEditor()
const { ShapeIndicator } = useEditorComponents()
const ids = dedupe(editor.getHintingShapeIds())
if (!ids.length) return null
if (!ShapeIndicator) return null
return (
<>
{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 classNames from 'classnames'
import { ComponentType, useRef } from 'react'
import { useRef } from 'react'
import { useTransform } from '../../hooks/useTransform'
import { Box } from '../../primitives/Box'
import { Vec } from '../../primitives/Vec'
import { clamp } from '../../primitives/utils'
/** @public */
export type TLCollaboratorHintComponent = ComponentType<{
export type TLCollaboratorHintProps = {
className?: string
point: VecModel
viewport: Box
zoom: number
opacity?: number
color: string
}>
}
/** @public */
export const DefaultCollaboratorHint: TLCollaboratorHintComponent = ({
export function DefaultCollaboratorHint({
className,
zoom,
point,
color,
viewport,
opacity = 1,
}) => {
}: TLCollaboratorHintProps) {
const rSvg = useRef<SVGSVGElement>(null)
useTransform(

View file

@ -1,19 +1,27 @@
import { VecModel } from '@tldraw/tlschema'
import classNames from 'classnames'
import { ComponentType, memo, useRef } from 'react'
import { memo, useRef } from 'react'
import { useTransform } from '../../hooks/useTransform'
/** @public */
export type TLCursorComponent = ComponentType<{
export type TLCursorProps = {
className?: string
point: VecModel | null
zoom: number
color?: string
name: string | null
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)
useTransform(rCursor, point?.x, point?.y, 1 / zoom)
@ -44,7 +52,4 @@ const _Cursor: TLCursorComponent = ({ className, zoom, point, color, name, chatM
)}
</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 { Editor } from '../../editor/Editor'
import { EditorContext } from '../../hooks/useEditor'
import { useEditorComponents } from '../../hooks/useEditorComponents'
import { hardResetEditor } from '../../utils/hardResetEditor'
import { refreshPage } from '../../utils/refreshPage'
import { Canvas } from '../Canvas'
import { ErrorBoundary } from '../ErrorBoundary'
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 [shouldShowResetConfirmation, setShouldShowResetConfirmation] = useState(false)
const { Canvas } = useEditorComponents()
const errorMessage = error instanceof Error ? error.message : String(error)
const errorStack = error instanceof Error ? error.stack : null
@ -135,9 +137,7 @@ My browser: ${navigator.userAgent}`
// a plain grey background.
<ErrorBoundary onError={noop} fallback={() => null}>
<EditorContext.Provider value={editor}>
<div className="tl-overlay tl-error-boundary__canvas">
<Canvas />
</div>
<div className="tl-overlay tl-error-boundary__canvas">{Canvas ? <Canvas /> : null}</div>
</EditorContext.Provider>
</ErrorBoundary>
)}

View file

@ -1,17 +1,16 @@
import { modulate } from '@tldraw/utils'
import { ComponentType } from 'react'
import { GRID_STEPS } from '../../constants'
/** @public */
export type TLGridComponent = ComponentType<{
export type TLGridProps = {
x: number
y: number
z: number
size: number
}>
}
/** @public */
export const DefaultGrid: TLGridComponent = ({ x, y, z, size }) => {
export function DefaultGrid({ x, y, z, size }: TLGridProps) {
return (
<svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>

View file

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

View file

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

View file

@ -1,13 +1,14 @@
import { TLShapeId } from '@tldraw/tlschema'
import { ComponentType } from 'react'
import { ShapeIndicator } from '../ShapeIndicator'
import { useEditorComponents } from '../../hooks/useEditorComponents'
/** @public */
export type TLHoveredShapeIndicatorComponent = ComponentType<{
export type TLHoveredShapeIndicatorProps = {
shapeId: TLShapeId
}>
}
/** @public */
export const DefaultHoveredShapeIndicator: TLHoveredShapeIndicatorComponent = ({ shapeId }) => {
return <ShapeIndicator className="tl-user-indicator__hovered" id={shapeId} />
export function DefaultHoveredShapeIndicator({ shapeId }: TLHoveredShapeIndicatorProps) {
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'
/** @public */
export type TLLoadingScreenComponent = ComponentType<object>
/** @public */
export const DefaultLoadingScreen: TLLoadingScreenComponent = () => {
export const DefaultLoadingScreen = () => {
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 classNames from 'classnames'
import { ComponentType } from 'react'
import { getSvgPathFromPoints } from '../../utils/getSvgPathFromPoints'
/** @public */
export type TLScribbleComponent = ComponentType<{
export type TLScribbleProps = {
scribble: TLScribble
zoom: number
color?: string
opacity?: number
className?: string
}>
}
/** @public */
export const DefaultScribble: TLScribbleComponent = ({
scribble,
zoom,
color,
opacity,
className,
}) => {
export function DefaultScribble({ scribble, zoom, color, opacity, className }: TLScribbleProps) {
if (!scribble.points.length) return null
return (

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -53,6 +53,7 @@ import {
getIndicesBetween,
getOwnProperty,
hasOwnProperty,
objectMapValues,
sortById,
sortByIndex,
structuredClone,
@ -6723,23 +6724,27 @@ export class Editor extends EventEmitter<TLEventMap> {
let remaining = duration
let t: number
type FromTo = { prop: string; from: number; to: number }
type ShapeAnimation = { partial: TLShapePartial; values: FromTo[] }
type ShapeAnimation = {
partial: TLShapePartial
values: { prop: string; from: number; to: number }[]
}
const animations: ShapeAnimation[] = []
partials.forEach((partial) => {
if (!partial) return
let partial: TLShapePartial | null | undefined, result: ShapeAnimation
for (let i = 0, n = partials.length; i < n; i++) {
partial = partials[i]
if (!partial) continue
const result: ShapeAnimation = {
result = {
partial,
values: [],
}
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) {
if (partial[key] !== undefined && shape[key] !== partial[key]) {
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)
this.animatingShapes.set(shape.id, animationId)
})
}
let value: ShapeAnimation
@ -6773,28 +6778,27 @@ export class Editor extends EventEmitter<TLEventMap> {
const { animatingShapes } = this
try {
const tPartials: TLShapePartial[] = []
const updates: TLShapePartial[] = []
for (let i = 0; i < animations.length; i++) {
value = animations[i]
let animationIdForShape: string | undefined
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) {
tPartials.push({
id: value.partial.id,
type: value.partial.type,
...value.values.reduce((acc, { prop, from, to }) => {
acc[prop] = from + (to - from) * t
return acc
}, {} as any),
})
}
}
this._updateShapes(tPartials, { squashing: true })
} catch (e) {
// noop
// Create the update
updates.push({
id: value.partial.id,
type: value.partial.type,
...value.values.reduce((acc, { prop, from, to }) => {
acc[prop] = from + (to - from) * t
return acc
}, {} as any),
})
}
this._updateShapes(updates, { squashing: true })
}
this.addListener('tick', handleTick)
@ -6968,20 +6972,24 @@ export class Editor extends EventEmitter<TLEventMap> {
partials: (TLShapePartial<T> | null | undefined)[],
historyOptions?: TLCommandHistoryOptions
) {
let compactedPartials = compact(partials)
if (this.animatingShapes.size > 0) {
compactedPartials.forEach((p) => this.animatingShapes.delete(p.id))
const compactedPartials: TLShapePartial<T>[] = Array(partials.length)
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)
return this
}
@ -6995,45 +7003,49 @@ export class Editor extends EventEmitter<TLEventMap> {
) => {
if (this.getInstanceState().isReadonly) return null
const partials = compact(_partials)
const snapshots: Record<string, TLShape> = {}
const updates: Record<string, TLShape> = {}
const snapshots = Object.fromEntries(
compact(partials.map(({ id }) => this.getShape(id))).map((shape) => {
return [shape.id, shape]
})
)
let shape: TLShape | undefined
let updated: TLShape
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(
partials.map((partial) => {
const prev = snapshots[partial.id]
if (!prev) return null
return applyPartialToShape(prev, partial)
})
)
// Get the current shape referenced by the partial
// If there is no current shape, we'll skip this update
shape = this.getShape(partial.id)
if (!shape) continue
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 }
},
{
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
// the new one. This is used for example when repositioning a text shape
// based on its new text content.
const result = Object.values(updates)
for (let i = 0; i < result.length; i++) {
const shape = result[i]
const current = this.store.get(shape.id)
if (!current) continue
const next = this.getShapeUtil(shape).onBeforeUpdate?.(current, shape)
if (next) {
result[i] = next
}
}
this.store.put(result)
this.store.put(
objectMapValues(updates).map((shape) => {
const current = this.store.get(shape.id)
if (current) {
const next = this.getShapeUtil(shape).onBeforeUpdate?.(current, shape)
if (next) return next
}
return shape
})
)
},
undo: ({ 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 {
if (!partial) return prev
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
switch (k) {
case 'id':
case 'type':
continue
default: {
if (v !== (prev as any)[k]) {
if (!next) {
next = { ...prev }
}
if (k === 'props') {
// props property
const nextProps = { ...prev.props } as JsonObject
for (const [propKey, propValue] of Object.entries(v as object)) {
if (propValue !== undefined) {
nextProps[propKey] = propValue
}
}
next!.props = nextProps
} else if (k === 'meta') {
// meta property
const nextMeta = { ...prev.meta } as JsonObject
for (const [metaKey, metaValue] of Object.entries(v as object)) {
if (metaValue !== undefined) {
nextMeta[metaKey] = metaValue
}
}
next!.meta = nextMeta
} else {
// base property
;(next as any)[k] = v
}
// Is the key a special key? We don't update those
if (k === 'id' || k === 'type' || k === 'typeName') continue
// Is the value the same as it was before?
if (v === (prev as any)[k]) continue
// There's a new value, so create the new shape if we haven't already (should we be cloning this?)
if (!next) next = { ...prev }
// for props / meta properties, we support updates with partials of this object
if (k === 'props' || k === 'meta') {
next[k] = { ...prev[k] } as JsonObject
for (const [nextKey, nextValue] of Object.entries(v as object)) {
if (nextValue !== undefined) {
;(next[k] as JsonObject)[nextKey] = nextValue
}
}
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 { ShapeIndicator, TLShapeIndicatorComponent } from '../components/ShapeIndicator'
import { ComponentType, createContext, useContext, useMemo } from 'react'
import { DefaultBackground } from '../components/default-components/DefaultBackground'
import { DefaultBrush, TLBrushProps } from '../components/default-components/DefaultBrush'
import {
DefaultBackground,
TLBackgroundComponent,
} from '../components/default-components/DefaultBackground'
import { DefaultBrush, TLBrushComponent } from '../components/default-components/DefaultBrush'
DefaultCanvas,
TLCanvasComponentProps,
} from '../components/default-components/DefaultCanvas'
import {
DefaultCollaboratorHint,
TLCollaboratorHintComponent,
TLCollaboratorHintProps,
} from '../components/default-components/DefaultCollaboratorHint'
import { DefaultCursor, TLCursorComponent } from '../components/default-components/DefaultCursor'
import { DefaultCursor, TLCursorProps } from '../components/default-components/DefaultCursor'
import {
DefaultErrorFallback,
TLErrorFallbackComponent,
} from '../components/default-components/DefaultErrorFallback'
import { DefaultGrid, TLGridComponent } from '../components/default-components/DefaultGrid'
import { DefaultHandle, TLHandleComponent } from '../components/default-components/DefaultHandle'
import { DefaultHandles, TLHandlesComponent } from '../components/default-components/DefaultHandles'
import { DefaultGrid, TLGridProps } from '../components/default-components/DefaultGrid'
import { DefaultHandle, TLHandleProps } from '../components/default-components/DefaultHandle'
import { DefaultHandles, TLHandlesProps } from '../components/default-components/DefaultHandles'
import {
DefaultHoveredShapeIndicator,
TLHoveredShapeIndicatorComponent,
TLHoveredShapeIndicatorProps,
} from '../components/default-components/DefaultHoveredShapeIndicator'
import { TLInFrontOfTheCanvas } from '../components/default-components/DefaultInFrontOfTheCanvas'
import { TLLoadingScreenComponent } from '../components/default-components/DefaultLoadingScreen'
import { TLOnTheCanvas } from '../components/default-components/DefaultOnTheCanvas'
import {
DefaultScribble,
TLScribbleComponent,
} from '../components/default-components/DefaultScribble'
import { DefaultScribble, TLScribbleProps } from '../components/default-components/DefaultScribble'
import {
DefaultSelectionBackground,
TLSelectionBackgroundComponent,
TLSelectionBackgroundProps,
} from '../components/default-components/DefaultSelectionBackground'
import {
DefaultSelectionForeground,
TLSelectionForegroundComponent,
TLSelectionForegroundProps,
} from '../components/default-components/DefaultSelectionForeground'
import {
DefaultShapeErrorFallback,
TLShapeErrorFallbackComponent,
} from '../components/default-components/DefaultShapeErrorFallback'
import {
DefaultShapeIndicator,
TLShapeIndicatorProps,
} from '../components/default-components/DefaultShapeIndicator'
import {
DefaultShapeIndicatorErrorFallback,
TLShapeIndicatorErrorFallbackComponent,
} from '../components/default-components/DefaultShapeIndicatorErrorFallback'
import {
DefaultSnapIndicator,
TLSnapIndicatorComponent,
TLSnapIndicatorProps,
} from '../components/default-components/DefaultSnapIndictor'
import { DefaultSpinner, TLSpinnerComponent } from '../components/default-components/DefaultSpinner'
import { DefaultSvgDefs, TLSvgDefsComponent } from '../components/default-components/DefaultSvgDefs'
import { DefaultSpinner } from '../components/default-components/DefaultSpinner'
import { DefaultSvgDefs } from '../components/default-components/DefaultSvgDefs'
import { useShallowObjectIdentity } from './useIdentity'
export interface BaseEditorComponents {
Background: TLBackgroundComponent
SvgDefs: TLSvgDefsComponent
Brush: TLBrushComponent
ZoomBrush: TLBrushComponent
Cursor: TLCursorComponent
CollaboratorBrush: TLBrushComponent
CollaboratorCursor: TLCursorComponent
CollaboratorHint: TLCollaboratorHintComponent
CollaboratorShapeIndicator: TLShapeIndicatorComponent
Grid: TLGridComponent
Scribble: TLScribbleComponent
CollaboratorScribble: TLScribbleComponent
SnapIndicator: TLSnapIndicatorComponent
Handles: TLHandlesComponent
Handle: TLHandleComponent
Spinner: TLSpinnerComponent
SelectionForeground: TLSelectionForegroundComponent
SelectionBackground: TLSelectionBackgroundComponent
HoveredShapeIndicator: TLHoveredShapeIndicatorComponent
OnTheCanvas: TLOnTheCanvas
InFrontOfTheCanvas: TLInFrontOfTheCanvas
LoadingScreen: TLLoadingScreenComponent
Background: ComponentType
SvgDefs: ComponentType
Brush: ComponentType<TLBrushProps>
ZoomBrush: ComponentType<TLBrushProps>
ShapeIndicator: ComponentType<TLShapeIndicatorProps>
Cursor: ComponentType<TLCursorProps>
Canvas: ComponentType<TLCanvasComponentProps>
CollaboratorBrush: ComponentType<TLBrushProps>
CollaboratorCursor: ComponentType<TLCursorProps>
CollaboratorHint: ComponentType<TLCollaboratorHintProps>
CollaboratorShapeIndicator: ComponentType<TLShapeIndicatorProps>
Grid: ComponentType<TLGridProps>
Scribble: ComponentType<TLScribbleProps>
CollaboratorScribble: ComponentType<TLScribbleProps>
SnapIndicator: ComponentType<TLSnapIndicatorProps>
Handles: ComponentType<TLHandlesProps>
Handle: ComponentType<TLHandleProps>
Spinner: ComponentType
SelectionForeground: ComponentType<TLSelectionForegroundProps>
SelectionBackground: ComponentType<TLSelectionBackgroundProps>
HoveredShapeIndicator: ComponentType<TLHoveredShapeIndicatorProps>
OnTheCanvas: ComponentType
InFrontOfTheCanvas: ComponentType
LoadingScreen: ComponentType
}
// These will always have defaults
@ -116,7 +116,7 @@ export function EditorComponentsProvider({
Cursor: DefaultCursor,
CollaboratorCursor: DefaultCursor,
CollaboratorHint: DefaultCollaboratorHint,
CollaboratorShapeIndicator: ShapeIndicator,
CollaboratorShapeIndicator: DefaultShapeIndicator,
Grid: DefaultGrid,
Scribble: DefaultScribble,
SnapIndicator: DefaultSnapIndicator,
@ -130,8 +130,10 @@ export function EditorComponentsProvider({
SelectionBackground: DefaultSelectionBackground,
SelectionForeground: DefaultSelectionForeground,
HoveredShapeIndicator: DefaultHoveredShapeIndicator,
ShapeIndicator: DefaultShapeIndicator,
OnTheCanvas: null,
InFrontOfTheCanvas: null,
Canvas: DefaultCanvas,
..._overrides,
}),
[_overrides]

View file

@ -74,9 +74,9 @@ import { TLExitEventHandler } from '@tldraw/editor';
import { TLFrameShape } from '@tldraw/editor';
import { TLGeoShape } from '@tldraw/editor';
import { TLHandle } from '@tldraw/editor';
import { TLHandlesComponent } from '@tldraw/editor';
import { TLHandlesProps } from '@tldraw/editor';
import { TLHighlightShape } from '@tldraw/editor';
import { TLHoveredShapeIndicatorComponent } from '@tldraw/editor';
import { TLHoveredShapeIndicatorProps } from '@tldraw/editor';
import { TLImageShape } from '@tldraw/editor';
import { TLInterruptEvent } from '@tldraw/editor';
import { TLKeyboardEvent } from '@tldraw/editor';
@ -100,9 +100,9 @@ import { TLPointerEventName } from '@tldraw/editor';
import { TLRecord } from '@tldraw/editor';
import { TLRotationSnapshot } from '@tldraw/editor';
import { TLSchema } from '@tldraw/editor';
import { TLScribbleComponent } from '@tldraw/editor';
import { TLSelectionBackgroundComponent } from '@tldraw/editor';
import { TLSelectionForegroundComponent } from '@tldraw/editor';
import { TLScribbleProps } from '@tldraw/editor';
import { TLSelectionBackgroundProps } from '@tldraw/editor';
import { TLSelectionForegroundProps } from '@tldraw/editor';
import { TLSelectionHandle } from '@tldraw/editor';
import { TLShape } from '@tldraw/editor';
import { TLShapeId } from '@tldraw/editor';
@ -347,7 +347,7 @@ export const defaultShapeUtils: TLAnyShapeUtilConstructor[];
export const DefaultStylePanel: NamedExoticComponent<TLUiStylePanelProps>;
// @public (undocumented)
export function DefaultStylePanelContent({ relevantStyles }: TLUiStylePanelContentProps): JSX_2.Element | null;
export function DefaultStylePanelContent({ styles }: TLUiStylePanelContentProps): JSX_2.Element | null;
// @public (undocumented)
export const DefaultToolbar: React_2.NamedExoticComponent<object>;
@ -1173,21 +1173,6 @@ export function Tldraw(props: TldrawProps): JSX_2.Element;
// @public (undocumented)
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)
export interface TldrawFile {
// (undocumented)
@ -1199,10 +1184,10 @@ export interface TldrawFile {
}
// @public (undocumented)
export const TldrawHandles: TLHandlesComponent;
export function TldrawHandles({ children }: TLHandlesProps): JSX_2.Element | null;
// @public (undocumented)
export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent;
export function TldrawHoveredShapeIndicator({ shapeId }: TLHoveredShapeIndicatorProps): JSX_2.Element | null;
// @public
export const TldrawImage: NamedExoticComponent< {
@ -1240,13 +1225,13 @@ export type TldrawProps = (Omit<TldrawUiProps, 'components'> & Omit<TldrawEditor
});
// @public (undocumented)
export const TldrawScribble: TLScribbleComponent;
export function TldrawScribble({ scribble, zoom, color, opacity, className }: TLScribbleProps): JSX_2.Element | null;
// @public (undocumented)
export const TldrawSelectionBackground: TLSelectionBackgroundComponent;
export const TldrawSelectionBackground: ({ bounds, rotation }: TLSelectionBackgroundProps) => JSX_2.Element | null;
// @public (undocumented)
export const TldrawSelectionForeground: TLSelectionForegroundComponent;
export const TldrawSelectionForeground: MemoExoticComponent<({ bounds, rotation, }: TLSelectionForegroundProps) => JSX_2.Element | null>;
// @public (undocumented)
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;
// @public (undocumented)
export function TldrawUiDropdownMenuGroup({ children, size, }: TLUiDropdownMenuGroupProps): JSX_2.Element;
export function TldrawUiDropdownMenuGroup({ children }: TLUiDropdownMenuGroupProps): JSX_2.Element;
// @public (undocumented)
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;
// @public (undocumented)
export function TldrawUiMenuGroup({ id, label, small, children }: TLUiMenuGroupProps): any;
export function TldrawUiMenuGroup({ id, label, children }: TLUiMenuGroupProps): any;
// @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;
@ -1459,8 +1444,6 @@ export type TLUiComponentsProviderProps = {
// @public (undocumented)
export interface TLUiContextMenuProps {
// (undocumented)
canvas: any;
// (undocumented)
children?: any;
}
@ -1549,7 +1532,6 @@ export type TLUiDropdownMenuContentProps = {
// @public (undocumented)
export type TLUiDropdownMenuGroupProps = {
children: any;
size?: 'medium' | 'small' | 'tiny' | 'wide';
};
// @public (undocumented)
@ -1866,7 +1848,6 @@ export type TLUiMenuGroupProps<TranslationKey extends string = string> = {
label?: {
[key: string]: TranslationKey;
} | TranslationKey;
small?: boolean;
children?: any;
};
@ -1894,7 +1875,7 @@ export type TLUiMenuSubmenuProps<Translation extends string = string> = {
} | Translation;
disabled?: boolean;
children: any;
size?: 'large' | 'medium' | 'small' | 'tiny';
size?: 'medium' | 'small' | 'tiny' | 'wide';
};
// @public (undocumented)
@ -1951,7 +1932,7 @@ export interface TLUiSliderProps {
// @public (undocumented)
export type TLUiStylePanelContentProps = {
relevantStyles: ReturnType<typeof useRelevantStyles>;
styles: ReturnType<typeof useRelevantStyles>;
};
// @public (undocumented)
@ -1960,11 +1941,6 @@ export interface TLUiStylePanelProps {
children?: any;
// (undocumented)
isMobile?: boolean;
// (undocumented)
relevantStyles: {
styles: ReadonlySharedStyleMap;
opacity: SharedStyle<number>;
} | null;
}
// @public (undocumented)
@ -2136,6 +2112,9 @@ export function useNativeClipboardEvents(): void;
// @public (undocumented)
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)
export function useTldrawUiComponents(): Partial<{
ContextMenu: ComponentType<TLUiContextMenuProps> | null;

View file

@ -3185,7 +3185,7 @@
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function DefaultStylePanelContent({ relevantStyles }: "
"text": "export declare function DefaultStylePanelContent({ styles }: "
},
{
"kind": "Reference",
@ -3223,7 +3223,7 @@
"overloadIndex": 1,
"parameters": [
{
"parameterName": "{ relevantStyles }",
"parameterName": "{ styles }",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
@ -13722,183 +13722,6 @@
],
"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",
"canonicalReference": "@tldraw/tldraw!TldrawFile:interface",
@ -14005,52 +13828,114 @@
"extendsTokenRanges": []
},
{
"kind": "Variable",
"canonicalReference": "@tldraw/tldraw!TldrawHandles:var",
"kind": "Function",
"canonicalReference": "@tldraw/tldraw!TldrawHandles:function(1)",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "TldrawHandles: "
"text": "export declare function TldrawHandles({ children }: "
},
{
"kind": "Reference",
"text": "TLHandlesComponent",
"canonicalReference": "@tldraw/editor!TLHandlesComponent:type"
"text": "TLHandlesProps",
"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",
"isReadonly": true,
"returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 6
},
"releaseTag": "Public",
"name": "TldrawHandles",
"variableTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
"overloadIndex": 1,
"parameters": [
{
"parameterName": "{ children }",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "TldrawHandles"
},
{
"kind": "Variable",
"canonicalReference": "@tldraw/tldraw!TldrawHoveredShapeIndicator:var",
"kind": "Function",
"canonicalReference": "@tldraw/tldraw!TldrawHoveredShapeIndicator:function(1)",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "TldrawHoveredShapeIndicator: "
"text": "export declare function TldrawHoveredShapeIndicator({ shapeId }: "
},
{
"kind": "Reference",
"text": "TLHoveredShapeIndicatorComponent",
"canonicalReference": "@tldraw/editor!TLHoveredShapeIndicatorComponent:type"
"text": "TLHoveredShapeIndicatorProps",
"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",
"isReadonly": true,
"returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 6
},
"releaseTag": "Public",
"name": "TldrawHoveredShapeIndicator",
"variableTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
"overloadIndex": 1,
"parameters": [
{
"parameterName": "{ shapeId }",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "TldrawHoveredShapeIndicator"
},
{
"kind": "Variable",
@ -14340,28 +14225,59 @@
}
},
{
"kind": "Variable",
"canonicalReference": "@tldraw/tldraw!TldrawScribble:var",
"kind": "Function",
"canonicalReference": "@tldraw/tldraw!TldrawScribble:function(1)",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "TldrawScribble: "
"text": "export declare function TldrawScribble({ scribble, zoom, color, opacity, className }: "
},
{
"kind": "Reference",
"text": "TLScribbleComponent",
"canonicalReference": "@tldraw/editor!TLScribbleComponent:type"
"text": "TLScribbleProps",
"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",
"isReadonly": true,
"returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 6
},
"releaseTag": "Public",
"name": "TldrawScribble",
"variableTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
"overloadIndex": 1,
"parameters": [
{
"parameterName": "{ scribble, zoom, color, opacity, className }",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "TldrawScribble"
},
{
"kind": "Variable",
@ -14372,10 +14288,27 @@
"kind": "Content",
"text": "TldrawSelectionBackground: "
},
{
"kind": "Content",
"text": "({ bounds, rotation }: "
},
{
"kind": "Reference",
"text": "TLSelectionBackgroundComponent",
"canonicalReference": "@tldraw/editor!TLSelectionBackgroundComponent:type"
"text": "TLSelectionBackgroundProps",
"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",
@ -14384,7 +14317,7 @@
"name": "TldrawSelectionBackground",
"variableTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
"endIndex": 6
}
},
{
@ -14396,10 +14329,36 @@
"kind": "Content",
"text": "TldrawSelectionForeground: "
},
{
"kind": "Content",
"text": "import(\"react\")."
},
{
"kind": "Reference",
"text": "TLSelectionForegroundComponent",
"canonicalReference": "@tldraw/editor!TLSelectionForegroundComponent:type"
"text": "MemoExoticComponent",
"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",
@ -14408,7 +14367,7 @@
"name": "TldrawSelectionForeground",
"variableTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
"endIndex": 8
}
},
{
@ -15525,7 +15484,7 @@
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function TldrawUiDropdownMenuGroup({ children, size, }: "
"text": "export declare function TldrawUiDropdownMenuGroup({ children }: "
},
{
"kind": "Reference",
@ -15559,7 +15518,7 @@
"overloadIndex": 1,
"parameters": [
{
"parameterName": "{ children, size, }",
"parameterName": "{ children }",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
@ -16181,7 +16140,7 @@
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function TldrawUiMenuGroup({ id, label, small, children }: "
"text": "export declare function TldrawUiMenuGroup({ id, label, children }: "
},
{
"kind": "Reference",
@ -16210,7 +16169,7 @@
"overloadIndex": 1,
"parameters": [
{
"parameterName": "{ id, label, small, children }",
"parameterName": "{ id, label, children }",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
@ -17518,34 +17477,6 @@
"name": "TLUiContextMenuProps",
"preserveMemberOrder": false,
"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",
"canonicalReference": "@tldraw/tldraw!TLUiContextMenuProps#children:member",
@ -18144,7 +18075,7 @@
},
{
"kind": "Content",
"text": "{\n children: any;\n size?: 'medium' | 'small' | 'tiny' | 'wide';\n}"
"text": "{\n children: any;\n}"
},
{
"kind": "Content",
@ -21351,7 +21282,7 @@
},
{
"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",
@ -21506,7 +21437,7 @@
},
{
"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",
@ -21808,7 +21739,7 @@
},
{
"kind": "Content",
"text": "{\n relevantStyles: "
"text": "{\n styles: "
},
{
"kind": "Reference",
@ -21822,7 +21753,7 @@
{
"kind": "Reference",
"text": "useRelevantStyles",
"canonicalReference": "@tldraw/tldraw!~useRelevantStyles:function"
"canonicalReference": "@tldraw/tldraw!useRelevantStyles:function"
},
{
"kind": "Content",
@ -21851,7 +21782,7 @@
"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",
"name": "TLUiStylePanelProps",
"preserveMemberOrder": false,
@ -21874,6 +21805,7 @@
"text": ";"
}
],
"fileUrlPath": "packages/tldraw/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx",
"isReadonly": false,
"isOptional": true,
"releaseTag": "Public",
@ -21901,6 +21833,7 @@
"text": ";"
}
],
"fileUrlPath": "packages/tldraw/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx",
"isReadonly": false,
"isOptional": true,
"releaseTag": "Public",
@ -21909,51 +21842,6 @@
"startIndex": 1,
"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": []
@ -23678,6 +23566,92 @@
"parameters": [],
"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",
"canonicalReference": "@tldraw/tldraw!useTldrawUiComponents:function(1)",

View file

@ -4,7 +4,6 @@
export * from '@tldraw/editor'
export { Tldraw, type TldrawProps } from './lib/Tldraw'
export { TldrawImage, type TldrawImageProps } from './lib/TldrawImage'
export { TldrawCropHandles, type TldrawCropHandlesProps } from './lib/canvas/TldrawCropHandles'
export { TldrawHandles } from './lib/canvas/TldrawHandles'
export { TldrawHoveredShapeIndicator } from './lib/canvas/TldrawHoveredShapeIndicator'
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 { useMenuIsOpen } from './lib/ui/hooks/useMenuIsOpen'
export { useReadonly } from './lib/ui/hooks/useReadonly'
export { useRelevantStyles } from './lib/ui/hooks/useRevelantStyles'
export {
toolbarItem,
useToolbarSchema,

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,14 @@
import {
ShapeIndicator,
TLHoveredShapeIndicatorComponent,
TLHoveredShapeIndicatorProps,
useEditor,
useEditorComponents,
useValue,
} from '@tldraw/editor'
/** @public */
export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent = ({ shapeId }) => {
export function TldrawHoveredShapeIndicator({ shapeId }: TLHoveredShapeIndicatorProps) {
const editor = useEditor()
const { ShapeIndicator } = useEditorComponents()
const showHoveredShapeIndicator = useValue(
'show hovered',
() => {
@ -23,8 +24,9 @@ export const TldrawHoveredShapeIndicator: TLHoveredShapeIndicatorComponent = ({
},
[editor]
)
if (!ShapeIndicator) 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 { getStroke } from '../shapes/shared/freehand/getStroke'
/** @public */
export const TldrawScribble: TLScribbleComponent = ({
scribble,
zoom,
color,
opacity,
className,
}) => {
export function TldrawScribble({ scribble, zoom, color, opacity, className }: TLScribbleProps) {
if (!scribble.points.length) return null
const stroke = getStroke(scribble.points, {

View file

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

View file

@ -1,8 +1,7 @@
import {
Box,
RotateCorner,
TLEmbedShape,
TLSelectionForegroundComponent,
TLSelectionForegroundProps,
TLTextShape,
getCursor,
toDomPrecision,
@ -18,428 +17,428 @@ import { useReadonly } from '../ui/hooks/useReadonly'
import { TldrawCropHandles } from './TldrawCropHandles'
/** @public */
export const TldrawSelectionForeground: TLSelectionForegroundComponent = track(
function TldrawSelectionForeground({ bounds, rotation }: { bounds: Box; rotation: number }) {
const editor = useEditor()
const rSvg = useRef<SVGSVGElement>(null)
export const TldrawSelectionForeground = track(function TldrawSelectionForeground({
bounds,
rotation,
}: TLSelectionForegroundProps) {
const editor = useEditor()
const rSvg = useRef<SVGSVGElement>(null)
const isReadonlyMode = useReadonly()
const topEvents = useSelectionEvents('top')
const rightEvents = useSelectionEvents('right')
const bottomEvents = useSelectionEvents('bottom')
const leftEvents = useSelectionEvents('left')
const topLeftEvents = useSelectionEvents('top_left')
const topRightEvents = useSelectionEvents('top_right')
const bottomRightEvents = useSelectionEvents('bottom_right')
const bottomLeftEvents = useSelectionEvents('bottom_left')
const isReadonlyMode = useReadonly()
const topEvents = useSelectionEvents('top')
const rightEvents = useSelectionEvents('right')
const bottomEvents = useSelectionEvents('bottom')
const leftEvents = useSelectionEvents('left')
const topLeftEvents = useSelectionEvents('top_left')
const topRightEvents = useSelectionEvents('top_right')
const bottomRightEvents = useSelectionEvents('bottom_right')
const bottomLeftEvents = useSelectionEvents('bottom_left')
const isDefaultCursor =
!editor.getIsMenuOpen() && editor.getInstanceState().cursor.type === 'default'
const isCoarsePointer = editor.getInstanceState().isCoarsePointer
const isDefaultCursor =
!editor.getIsMenuOpen() && editor.getInstanceState().cursor.type === 'default'
const isCoarsePointer = editor.getInstanceState().isCoarsePointer
const onlyShape = editor.getOnlySelectedShape()
const isLockedShape = onlyShape && editor.isShapeOrAncestorLocked(onlyShape)
const onlyShape = editor.getOnlySelectedShape()
const isLockedShape = onlyShape && editor.isShapeOrAncestorLocked(onlyShape)
// if all shapes have an expandBy for the selection outline, we can expand by the l
const expandOutlineBy = onlyShape
? editor.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape)
: 0
// if all shapes have an expandBy for the selection outline, we can expand by the l
const expandOutlineBy = onlyShape
? editor.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape)
: 0
useTransform(rSvg, bounds?.x, bounds?.y, 1, editor.getSelectionRotation(), {
x: -expandOutlineBy,
y: -expandOutlineBy,
})
useTransform(rSvg, bounds?.x, bounds?.y, 1, editor.getSelectionRotation(), {
x: -expandOutlineBy,
y: -expandOutlineBy,
})
if (!bounds) return null
bounds = bounds.clone().expandBy(expandOutlineBy).zeroFix()
if (!bounds) return null
bounds = bounds.clone().expandBy(expandOutlineBy).zeroFix()
const zoom = editor.getZoomLevel()
const isChangingStyle = editor.getInstanceState().isChangingStyle
const zoom = editor.getZoomLevel()
const isChangingStyle = editor.getInstanceState().isChangingStyle
const width = bounds.width
const height = bounds.height
const width = bounds.width
const height = bounds.height
const size = 8 / zoom
const isTinyX = width < size * 2
const isTinyY = height < size * 2
const size = 8 / zoom
const isTinyX = width < size * 2
const isTinyY = height < size * 2
const isSmallX = width < size * 4
const isSmallY = height < size * 4
const isSmallCropX = width < size * 5
const isSmallCropY = height < size * 5
const isSmallX = width < size * 4
const isSmallY = height < size * 4
const isSmallCropX = width < size * 5
const isSmallCropY = height < size * 5
const mobileHandleMultiplier = isCoarsePointer ? 1.75 : 1
const targetSize = (6 / zoom) * mobileHandleMultiplier
const mobileHandleMultiplier = isCoarsePointer ? 1.75 : 1
const targetSize = (6 / zoom) * mobileHandleMultiplier
const targetSizeX = (isSmallX ? targetSize / 2 : targetSize) * (mobileHandleMultiplier * 0.75)
const targetSizeY = (isSmallY ? 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 showSelectionBounds =
(onlyShape ? !editor.getShapeUtil(onlyShape).hideSelectionBoundsFg(onlyShape) : true) &&
!isChangingStyle
const showSelectionBounds =
(onlyShape ? !editor.getShapeUtil(onlyShape).hideSelectionBoundsFg(onlyShape) : true) &&
!isChangingStyle
let shouldDisplayBox =
(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 =
let shouldDisplayBox =
(showSelectionBounds &&
editor.isInAny(
'select.idle',
'select.brushing',
'select.scribble_brushing',
'select.pointing_canvas',
'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 &&
'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') &&
textHandleHeight * zoom >= 4
editor.isShapeOfType<TLTextShape>(onlyShape, 'text'))
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>
)
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(
'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({
cx,

View file

@ -79,6 +79,16 @@
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) {
.tlui-button::after {
background-color: var(--color-muted-2);
@ -136,7 +146,6 @@
.tlui-button__menu {
height: 40px;
min-height: 40px;
min-width: 128px;
width: 100%;
gap: 8px;
margin: -4px 0px;
@ -929,6 +938,10 @@
stroke-width: 1px;
}
.tlui-menu__group {
width: 100%;
}
.tlui-menu__group:empty {
display: none;
}
@ -952,25 +965,22 @@
/* Menu Sizes */
.tlui-menu[data-size='large'] > .tlui-menu__group,
.tlui-menu__submenu__content[data-size='large'] > .tlui-menu__group {
.tlui-menu[data-size='large'] > .tlui-menu__group {
min-width: initial;
}
.tlui-menu[data-size='medium'] > .tlui-menu__group,
.tlui-menu__submenu__content[data-size='medium'] > .tlui-menu__group {
.tlui-menu[data-size='medium'] > .tlui-menu__group {
min-width: 144px;
}
.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 {
.tlui-menu[data-size='small'] > .tlui-menu__group {
min-width: 0px;
}
.tlui-menu[data-size='tiny'] > .tlui-menu__group {
min-width: 0px;
}
/* ------------------ Actions Menu ------------------ */
.tlui-actions-menu {

View file

@ -21,7 +21,6 @@ import { useNativeClipboardEvents } from './hooks/useClipboardEvents'
import { useEditorEvents } from './hooks/useEditorEvents'
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
import { useReadonly } from './hooks/useReadonly'
import { useRelevantStyles } from './hooks/useRevelantStyles'
import { useTranslation } from './hooks/useTranslation/useTranslation'
/**
@ -159,7 +158,7 @@ const TldrawUiContent = React.memo(function TldrawUI() {
<div className="tlui-layout__top__right">
{SharePanel && <SharePanel />}
{StylePanel && breakpoint >= PORTRAIT_BREAKPOINT.TABLET_SM && !isReadonlyMode && (
<_StylePanel />
<StylePanel />
)}
</div>
</div>
@ -181,11 +180,3 @@ const TldrawUiContent = React.memo(function TldrawUI() {
</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 { preventDefault, useContainer, useEditor } from '@tldraw/editor'
import { preventDefault, useContainer, useEditor, useEditorComponents } from '@tldraw/editor'
import { memo, useCallback } from 'react'
import { useMenuIsOpen } from '../../hooks/useMenuIsOpen'
import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
@ -7,17 +7,17 @@ import { DefaultContextMenuContent } from './DefaultContextMenuContent'
/** @public */
export interface TLUiContextMenuProps {
canvas: any
children?: any
}
/** @public */
export const DefaultContextMenu = memo(function DefaultContextMenu({
canvas,
children,
}: TLUiContextMenuProps) {
const editor = useEditor()
const { Canvas } = useEditorComponents()
const cb = useCallback(
(isOpen: boolean) => {
if (!isOpen) {
@ -68,7 +68,7 @@ export const DefaultContextMenu = memo(function DefaultContextMenu({
return (
<_ContextMenu.Root dir="ltr" onOpenChange={handleOpenChange} modal={false}>
<_ContextMenu.Trigger onContextMenu={undefined} dir="ltr">
{canvas}
{Canvas ? <Canvas /> : null}
</_ContextMenu.Trigger>
{isOpen && (
<_ContextMenu.Portal container={container}>

View file

@ -22,14 +22,14 @@ export function MobileStylePanel() {
const msg = useTranslation()
const relevantStyles = useRelevantStyles()
const color = relevantStyles?.styles.get(DefaultColorStyle)
const color = relevantStyles?.get(DefaultColorStyle)
const theme = getDefaultColorTheme({ isDarkMode: editor.user.getIsDarkMode() })
const currentColor = (
color?.type === 'shared' ? theme[color.value as TLDefaultColorStyle] : theme.black
).solid
const disableStylePanel = useValue(
'isHandOrEraserToolActive',
'disable style panel',
() => editor.isInAny('hand', 'zoom', 'eraser', 'laser'),
[editor]
)
@ -64,16 +64,8 @@ export function MobileStylePanel() {
</TldrawUiButton>
</TldrawUiPopoverTrigger>
<TldrawUiPopoverContent side="top" align="end">
<_StylePanel />
{StylePanel && <StylePanel isMobile />}
</TldrawUiPopoverContent>
</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 { memo, useCallback } from 'react'
import { useRelevantStyles } from '../../hooks/useRevelantStyles'
import { DefaultStylePanelContent } from './DefaultStylePanelContent'
/** @public */
export interface TLUiStylePanelProps {
isMobile?: boolean
children?: any
relevantStyles: {
styles: ReadonlySharedStyleMap
opacity: SharedStyle<number>
} | null
}
/** @public */
export const DefaultStylePanel = memo(function DefaultStylePanel({
isMobile,
children,
relevantStyles,
}: TLUiStylePanelProps) {
const editor = useEditor()
const styles = useRelevantStyles()
const handlePointerOut = useCallback(() => {
if (!isMobile) {
editor.updateInstanceState({ isChangingStyle: false })
}
}, [editor, isMobile])
if (!relevantStyles) return null
const content = children ?? <DefaultStylePanelContent relevantStyles={relevantStyles} />
const content = children ?? <DefaultStylePanelContent styles={styles} />
return (
<div

View file

@ -11,10 +11,10 @@ import {
GeoShapeGeoStyle,
LineShapeSplineStyle,
ReadonlySharedStyleMap,
SharedStyle,
StyleProp,
minBy,
useEditor,
useValue,
} from '@tldraw/editor'
import React from 'react'
import { STYLES } from '../../../styles'
@ -30,14 +30,13 @@ import { DropdownPicker } from './DropdownPicker'
/** @public */
export type TLUiStylePanelContentProps = {
relevantStyles: ReturnType<typeof useRelevantStyles>
styles: ReturnType<typeof useRelevantStyles>
}
/** @public */
export function DefaultStylePanelContent({ relevantStyles }: TLUiStylePanelContentProps) {
if (!relevantStyles) return null
export function DefaultStylePanelContent({ styles }: TLUiStylePanelContentProps) {
if (!styles) return null
const { styles, opacity } = relevantStyles
const geo = styles.get(GeoShapeGeoStyle)
const arrowheadEnd = styles.get(ArrowShapeArrowheadEndStyle)
const arrowheadStart = styles.get(ArrowShapeArrowheadStartStyle)
@ -51,7 +50,7 @@ export function DefaultStylePanelContent({ relevantStyles }: TLUiStylePanelConte
return (
<>
<CommonStylePickerSet styles={styles} opacity={opacity} />
<CommonStylePickerSet styles={styles} />
{!hideText && <TextStylePickerSet styles={styles} />}
{!(hideGeo && hideArrowHeads && hideSpline) && (
<div className="tlui-style-panel__section" aria-label="style panel styles">
@ -68,52 +67,28 @@ function useStyleChangeCallback() {
const editor = useEditor()
const trackEvent = useUiEvents()
return React.useMemo(() => {
return function handleStyleChange<T>(style: StyleProp<T>, value: T, squashing: boolean) {
editor.batch(() => {
if (editor.isIn('select')) {
editor.setStyleForSelectedShapes(style, value, { squashing })
}
editor.setStyleForNextShapes(style, value, { squashing })
editor.updateInstanceState({ isChangingStyle: true })
})
return React.useMemo(
() =>
function handleStyleChange<T>(style: StyleProp<T>, value: T, squashing: boolean) {
editor.batch(() => {
if (editor.isIn('select')) {
editor.setStyleForSelectedShapes(style, value, { squashing })
}
editor.setStyleForNextShapes(style, value, { squashing })
editor.updateInstanceState({ isChangingStyle: true })
})
trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string })
}
}, [editor, trackEvent])
trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string })
},
[editor, trackEvent]
)
}
const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
function CommonStylePickerSet({
styles,
opacity,
}: {
styles: ReadonlySharedStyleMap
opacity: SharedStyle<number>
}) {
const editor = useEditor()
const trackEvent = useUiEvents()
function CommonStylePickerSet({ styles }: { styles: ReadonlySharedStyleMap }) {
const msg = useTranslation()
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 fill = styles.get(DefaultFillStyle)
const dash = styles.get(DefaultDashStyle)
@ -121,15 +96,6 @@ function CommonStylePickerSet({
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 (
<>
<div
@ -148,18 +114,7 @@ function CommonStylePickerSet({
onValueChange={handleValueChange}
/>
)}
{opacity === undefined ? null : (
<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')}
/>
)}
<OpacitySlider />
</div>
{showPickers && (
<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
return (
<TldrawUiMenuSubmenu id="arrange" label="context-menu.arrange">
<TldrawUiMenuSubmenu id="arrange" label="context-menu.arrange" size="small">
{twoSelected && (
<TldrawUiMenuGroup id="align">
<TldrawUiMenuItem {...actions['align-left']} />

View file

@ -139,6 +139,7 @@ export type TLUiDropdownMenuSubContentProps = {
id?: string
alignOffset?: number
sideOffset?: number
size?: 'tiny' | 'small' | 'medium' | 'wide'
children: any
}
@ -146,6 +147,7 @@ export type TLUiDropdownMenuSubContentProps = {
export function TldrawUiDropdownMenuSubContent({
alignOffset = -1,
sideOffset = -4,
size = 'small',
children,
}: TLUiDropdownMenuSubContentProps) {
const container = useContainer()
@ -156,6 +158,7 @@ export function TldrawUiDropdownMenuSubContent({
alignOffset={alignOffset}
sideOffset={sideOffset}
collisionPadding={4}
data-size={size}
>
{children}
</_DropdownMenu.SubContent>
@ -166,16 +169,12 @@ export function TldrawUiDropdownMenuSubContent({
/** @public */
export type TLUiDropdownMenuGroupProps = {
children: any
size?: 'tiny' | 'small' | 'medium' | 'wide'
}
/** @public */
export function TldrawUiDropdownMenuGroup({
children,
size = 'medium',
}: TLUiDropdownMenuGroupProps) {
export function TldrawUiDropdownMenuGroup({ children }: TLUiDropdownMenuGroupProps) {
return (
<_DropdownMenu.Group dir="ltr" className="tlui-menu__group" data-size={size}>
<_DropdownMenu.Group dir="ltr" className="tlui-menu__group">
{children}
</_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.
*/
label?: TranslationKey | { [key: string]: TranslationKey }
small?: boolean
children?: any
}
/** @public */
export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMenuGroupProps) {
export function TldrawUiMenuGroup({ id, label, children }: TLUiMenuGroupProps) {
const { type: menuType, sourceId } = useTldrawUiMenuContext()
const msg = useTranslation()
const labelToUse = unwrapLabel(label, menuType)
@ -33,10 +32,7 @@ export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMe
}
case 'menu': {
return (
<TldrawUiDropdownMenuGroup
data-testid={`${sourceId}-group.${id}`}
size={small ? 'tiny' : 'medium'}
>
<TldrawUiDropdownMenuGroup data-testid={`${sourceId}-group.${id}`}>
{children}
</TldrawUiDropdownMenuGroup>
)
@ -46,7 +42,6 @@ export function TldrawUiMenuGroup({ id, label, small = false, children }: TLUiMe
<ContextMenuGroup
dir="ltr"
className="tlui-menu__group"
data-size={small ? 'tiny' : 'medium'}
data-testid={`${sourceId}-group.${id}`}
>
{children}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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