Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/editor/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { HistoryEntry } from '@tldraw/store';
import { IndexKey } from '@tldraw/utils';
import { JsonObject } from '@tldraw/utils';
import { JSX } from 'react/jsx-runtime';
import { JSXElementConstructor } from 'react';
import { LegacyMigrations } from '@tldraw/store';
import { MigrationSequence } from '@tldraw/store';
import { NamedExoticComponent } from 'react';
Expand Down Expand Up @@ -657,6 +658,9 @@ export const DefaultShapeIndicator: NamedExoticComponent<TLShapeIndicatorProps>;
// @public (undocumented)
export const DefaultShapeIndicators: NamedExoticComponent<TLShapeIndicatorsProps>;

// @public (undocumented)
export function DefaultShapeRenderer({ renderShape }: TLShapeRendererProps): ReactElement<unknown, JSXElementConstructor<any> | string>[];

// @public (undocumented)
export const DefaultShapeWrapper: ForwardRefExoticComponent<TLShapeWrapperProps & RefAttributes<HTMLDivElement>>;

Expand Down Expand Up @@ -3618,6 +3622,8 @@ export interface TLEditorComponents {
// (undocumented)
ShapeIndicators?: ComponentType | null;
// (undocumented)
ShapeRenderer?: ComponentType<TLShapeRendererProps> | null;
// (undocumented)
ShapeWrapper?: ComponentType<TLShapeWrapperProps & RefAttributes<HTMLDivElement>> | null;
// (undocumented)
SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null;
Expand Down Expand Up @@ -4342,6 +4348,12 @@ export interface TLShapeIndicatorsProps {
showAll?: boolean;
}

// @public (undocumented)
export interface TLShapeRendererProps {
// (undocumented)
renderShape(shape: TLRenderingShape): ReactElement;
}

// @public
export interface TLShapeUtilCanBeLaidOutOpts {
shapes?: TLShape[];
Expand Down
2 changes: 2 additions & 0 deletions packages/editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export { DefaultBackground } from './lib/components/default-components/DefaultBa
export { DefaultBrush, type TLBrushProps } from './lib/components/default-components/DefaultBrush'
export {
DefaultCanvas,
DefaultShapeRenderer,
type TLCanvasComponentProps,
type TLShapeRendererProps,
} from './lib/components/default-components/DefaultCanvas'
export {
DefaultCollaboratorHint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useQuickReactor, useValue } from '@tldraw/state-react'
import { TLHandle, TLShapeId } from '@tldraw/tlschema'
import { dedupe, modulate, objectMapValues } from '@tldraw/utils'
import classNames from 'classnames'
import { Fragment, JSX, useEffect, useRef, useState } from 'react'
import { Fragment, JSX, ReactElement, useEffect, useRef, useState } from 'react'
import type { TLRenderingShape } from '../../editor/Editor'
import { tlenv } from '../../globals/environment'
import { useCanvasEvents } from '../../hooks/useCanvasEvents'
import { useCoarsePointer } from '../../hooks/useCoarsePointer'
Expand Down Expand Up @@ -450,21 +451,30 @@ function CullingController() {
}

function ShapesToDisplay() {
const editor = useEditor()

const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
const { ShapeRenderer } = useEditorComponents()
const Renderer = ShapeRenderer ?? DefaultShapeRenderer

return (
<ShapeCullingProvider>
{renderingShapes.map((result) => (
<Shape key={result.id + '_shape'} {...result} />
))}
<Renderer renderShape={(shape) => <Shape key={shape.id + '_shape'} {...shape} />} />
<CullingController />
{tlenv.isSafari && <ReflowIfNeeded />}
</ShapeCullingProvider>
)
}

/** @public */
export interface TLShapeRendererProps {
renderShape(shape: TLRenderingShape): ReactElement
}

/** @public @react */
export function DefaultShapeRenderer({ renderShape }: TLShapeRendererProps) {
const editor = useEditor()
const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
return renderingShapes.map((shape) => renderShape(shape))
}

function HintedShapeIndicator() {
const editor = useEditor()
const { ShapeIndicator } = useEditorComponents()
Expand Down
48 changes: 29 additions & 19 deletions packages/editor/src/lib/editor/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5294,7 +5294,12 @@ export class Editor extends EventEmitter<TLEventMap> {
: this.getCurrentPageShapesSorted()
).filter((shape) => {
// Frames have labels positioned above the shape (outside bounds), so always include them
if (!candidateIds.has(shape.id) && !this.isShapeOfType(shape, 'frame')) return false
if (
!candidateIds.has(shape.id) &&
!this.isShapeOfType(shape, 'frame') &&
(shape as any).type !== 'grid'
)
return false

if (
(shape.isLocked && !hitLocked) ||
Expand All @@ -5316,21 +5321,15 @@ export class Editor extends EventEmitter<TLEventMap> {
const pointInShapeSpace = this.getPointInShapeSpace(shape, point)

// Check labels first
if (
this.isShapeOfType(shape, 'frame') ||
((this.isShapeOfType(shape, 'note') ||
this.isShapeOfType(shape, 'arrow') ||
(this.isShapeOfType(shape, 'geo') && shape.props.fill === 'none')) &&
this.getShapeUtil(shape).getText(shape)?.trim())
) {
if (geometry instanceof Group2d) {
for (const childGeometry of (geometry as Group2d).children) {
if (childGeometry.isLabel && childGeometry.isPointInBounds(pointInShapeSpace)) {
return shape
}
}
}

if (this.isShapeOfType(shape, 'frame')) {
if (this.isShapeOfType(shape, 'frame') || (shape as any).type === 'grid') {
// On the rare case that we've hit a frame (not its label), test again hitInside to be forced true;
// this prevents clicks from passing through the body of a frame to shapes behind it.

Expand Down Expand Up @@ -5489,7 +5488,12 @@ export class Editor extends EventEmitter<TLEventMap> {
return this.getCurrentPageShapesSorted()
.filter((shape) => {
if (this.isShapeHidden(shape)) return false
if (!candidateIds.has(shape.id) && !this.isShapeOfType(shape, 'frame')) return false
if (
!candidateIds.has(shape.id) &&
!this.isShapeOfType(shape, 'frame') &&
(shape as any).type !== 'grid'
)
return false
return this.isPointInShape(shape, point, opts)
})
.reverse()
Expand Down Expand Up @@ -9363,7 +9367,7 @@ export class Editor extends EventEmitter<TLEventMap> {
for (const shape of this.getSelectedShapes()) {
if (lowestDepth === 0) break

const isFrame = this.isShapeOfType(shape, 'frame')
const isFrame = this.isShapeOfType(shape, 'frame') || (shape as any).type == 'grid'
const ancestors = this.getShapeAncestors(shape)
if (isFrame) ancestors.push(shape)

Expand Down Expand Up @@ -9425,11 +9429,17 @@ export class Editor extends EventEmitter<TLEventMap> {
} else {
if (rootShapeIds.length === 1) {
const rootShape = shapes.find((s) => s.id === rootShapeIds[0])!

const isParentFrame =
this.isShapeOfType(parent, 'frame') || (parent as any).type == 'grid'
const isRootFrame =
this.isShapeOfType(rootShape, 'frame') || (rootShape as any).type == 'grid'

if (
this.isShapeOfType(parent, 'frame') &&
this.isShapeOfType(rootShape, 'frame') &&
rootShape.props.w === parent?.props.w &&
rootShape.props.h === parent?.props.h
isParentFrame &&
isRootFrame &&
(rootShape as any).props.w === (parent as any).props.w &&
(rootShape as any).props.h === (parent as any).props.h
) {
isDuplicating = true
}
Expand Down Expand Up @@ -9602,13 +9612,13 @@ export class Editor extends EventEmitter<TLEventMap> {
const onlyRoot = rootShapes[0] as TLFrameShape
// If the old bounds are in the viewport...
// todo: replace frame references with shapes that can accept children
if (this.isShapeOfType(onlyRoot, 'frame')) {
if (this.isShapeOfType(onlyRoot, 'frame') || (onlyRoot as any).type == 'grid') {
while (
this.getShapesAtPoint(point).some(
(shape) =>
this.isShapeOfType(shape, 'frame') &&
shape.props.w === onlyRoot.props.w &&
shape.props.h === onlyRoot.props.h
(this.isShapeOfType(shape, 'frame') || (shape as any).type == 'grid') &&
(shape as any).props.w === (onlyRoot as any).props.w &&
(shape as any).props.h === (onlyRoot as any).props.h
)
) {
point.x += bounds.w + 16
Expand Down
4 changes: 4 additions & 0 deletions packages/editor/src/lib/hooks/useEditorComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { DefaultBackground } from '../components/default-components/DefaultBackg
import { DefaultBrush, TLBrushProps } from '../components/default-components/DefaultBrush'
import {
DefaultCanvas,
DefaultShapeRenderer,
TLCanvasComponentProps,
TLShapeRendererProps,
} from '../components/default-components/DefaultCanvas'
import {
DefaultCollaboratorHint,
Expand Down Expand Up @@ -72,6 +74,7 @@ export interface TLEditorComponents {
SelectionForeground?: ComponentType<TLSelectionForegroundProps> | null
ShapeIndicator?: ComponentType<TLShapeIndicatorProps> | null
ShapeIndicators?: ComponentType | null
ShapeRenderer?: ComponentType<TLShapeRendererProps> | null
ShapeWrapper?: ComponentType<TLShapeWrapperProps & RefAttributes<HTMLDivElement>> | null
SnapIndicator?: ComponentType<TLSnapIndicatorProps> | null
Spinner?: ComponentType<React.SVGProps<SVGSVGElement>> | null
Expand Down Expand Up @@ -119,6 +122,7 @@ export function EditorComponentsProvider({
SelectionForeground: DefaultSelectionForeground,
ShapeIndicator: DefaultShapeIndicator,
ShapeIndicators: DefaultShapeIndicators,
ShapeRenderer: DefaultShapeRenderer,
ShapeWrapper: DefaultShapeWrapper,
SnapIndicator: DefaultSnapIndicator,
Spinner: DefaultSpinner,
Expand Down