Explicit shape type checks (#1594)
This PR adds shape type checks that use the shape util, e.g. `this.editor.isShapeOfType(shape, FrameShapeUtil)`. In part this is designed to help us track down where dependencies exist between the editor and our default shapes. ### Change Type - [x] `internal` — Any other changes that don't affect the published package
This commit is contained in:
parent
0bbdcdd91b
commit
21377c0f22
24 changed files with 118 additions and 87 deletions
|
@ -1185,18 +1185,12 @@ export const INDENT = " ";
|
|||
// @public
|
||||
export function isAnimated(buffer: ArrayBuffer): boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export function isGeoShape(shape: TLShape): shape is TLGeoShape;
|
||||
|
||||
// @public
|
||||
export function isGIF(buffer: ArrayBuffer): boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export const isImage: (ext: string) => boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export function isNoteShape(shape: TLShape): shape is TLNoteShape;
|
||||
|
||||
// @public
|
||||
export function isSerializable(value: any): boolean;
|
||||
|
||||
|
|
|
@ -248,8 +248,6 @@ export {
|
|||
getSvgAsImage,
|
||||
getSvgAsString,
|
||||
getTextBoundingBox,
|
||||
isGeoShape,
|
||||
isNoteShape,
|
||||
type TLCopyType,
|
||||
type TLExportType,
|
||||
} from './lib/utils/export'
|
||||
|
|
|
@ -2,6 +2,8 @@ import { RotateCorner, toDomPrecision } from '@tldraw/primitives'
|
|||
import classNames from 'classnames'
|
||||
import { useRef } from 'react'
|
||||
import { track } from 'signia-react'
|
||||
import { EmbedShapeUtil } from '../editor/shapes/embed/EmbedShapeUtil'
|
||||
import { TextShapeUtil } from '../editor/shapes/text/TextShapeUtil'
|
||||
import { getCursor } from '../hooks/useCursor'
|
||||
import { useEditor } from '../hooks/useEditor'
|
||||
import { useSelectionEvents } from '../hooks/useSelectionEvents'
|
||||
|
@ -32,7 +34,7 @@ export const SelectionFg = track(function SelectionFg() {
|
|||
|
||||
let bounds = editor.selectionBounds
|
||||
const shapes = editor.selectedShapes
|
||||
const onlyShape = shapes.length === 1 ? shapes[0] : null
|
||||
const onlyShape = editor.onlySelectedShape
|
||||
const isLockedShape = onlyShape && editor.isShapeOrAncestorLocked(onlyShape)
|
||||
|
||||
// if all shapes have an expandBy for the selection outline, we can expand by the l
|
||||
|
@ -92,12 +94,15 @@ export const SelectionFg = track(function SelectionFg() {
|
|||
(showSelectionBounds &&
|
||||
editor.isIn('select.resizing') &&
|
||||
onlyShape &&
|
||||
shapes[0].type === 'text')
|
||||
editor.isShapeOfType(onlyShape, TextShapeUtil))
|
||||
|
||||
if (IS_FIREFOX && shouldDisplayBox) {
|
||||
if (editor.onlySelectedShape?.type === 'embed') {
|
||||
shouldDisplayBox = false
|
||||
}
|
||||
if (
|
||||
onlyShape &&
|
||||
editor.isShapeOfType(onlyShape, EmbedShapeUtil) &&
|
||||
shouldDisplayBox &&
|
||||
IS_FIREFOX
|
||||
) {
|
||||
shouldDisplayBox = false
|
||||
}
|
||||
|
||||
const showCropHandles =
|
||||
|
@ -180,7 +185,8 @@ export const SelectionFg = track(function SelectionFg() {
|
|||
const showTextResizeHandles =
|
||||
shouldDisplayControls &&
|
||||
isCoarsePointer &&
|
||||
onlyShape?.type === 'text' &&
|
||||
onlyShape &&
|
||||
editor.isShapeOfType(onlyShape, TextShapeUtil) &&
|
||||
textHandleHeight * zoom >= 4
|
||||
|
||||
return (
|
||||
|
|
|
@ -728,7 +728,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return undefined
|
||||
}
|
||||
|
||||
const frameAncestors = this.getAncestorsById(shape.id).filter((s) => s.type === 'frame')
|
||||
const frameAncestors = this.getAncestorsById(shape.id).filter((shape) =>
|
||||
this.isShapeOfType(shape, FrameShapeUtil)
|
||||
)
|
||||
|
||||
if (frameAncestors.length === 0) return undefined
|
||||
|
||||
|
@ -1087,7 +1089,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @internal
|
||||
*/
|
||||
private _extractSharedProps(shape: TLShape, sharedProps: TLNullableShapeProps) {
|
||||
if (shape.type === 'group') {
|
||||
if (this.isShapeOfType(shape, GroupShapeUtil)) {
|
||||
// For groups, ignore the props of the group shape and instead include
|
||||
// the props of the group's children. These are the shapes that would have
|
||||
// their props changed if the user called `setProp` on the current selection.
|
||||
|
@ -1210,7 +1212,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// For groups, ignore the opacity of the group shape and instead include
|
||||
// the opacity of the group's children. These are the shapes that would have
|
||||
// their opacity changed if the user called `setOpacity` on the current selection.
|
||||
if (shape.type === 'group') {
|
||||
if (this.isShapeOfType(shape, GroupShapeUtil)) {
|
||||
for (const childId of this.getSortedChildIds(shape.id)) {
|
||||
addShape(childId)
|
||||
}
|
||||
|
@ -1274,7 +1276,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
/** @internal */
|
||||
@computed
|
||||
private get _arrowBindingsIndex() {
|
||||
return arrowBindingsIndex(this.store)
|
||||
return arrowBindingsIndex(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1541,9 +1543,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const nextFocusLayerId =
|
||||
filtered.length === 0
|
||||
? next?.focusLayerId
|
||||
: this.findCommonAncestor(
|
||||
compact(filtered.map((id) => this.getShapeById(id))),
|
||||
(shape) => shape.type === 'group'
|
||||
: this.findCommonAncestor(compact(filtered.map((id) => this.getShapeById(id))), (shape) =>
|
||||
this.isShapeOfType(shape, GroupShapeUtil)
|
||||
)
|
||||
|
||||
if (filtered.length !== next.selectedIds.length || nextFocusLayerId != next.focusLayerId) {
|
||||
|
@ -3003,7 +3004,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
|
||||
if (focusedShape) {
|
||||
// If we have a focused layer, look for an ancestor of the focused shape that is a group
|
||||
const match = this.findAncestor(focusedShape, (s) => s.type === 'group')
|
||||
const match = this.findAncestor(focusedShape, (shape) =>
|
||||
this.isShapeOfType(shape, GroupShapeUtil)
|
||||
)
|
||||
// If we have an ancestor that can become a focused layer, set it as the focused layer
|
||||
this.setFocusLayer(match?.id ?? null)
|
||||
this.select(focusedShape.id)
|
||||
|
@ -3237,7 +3240,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
let node = shape as TLShape | undefined
|
||||
while (node) {
|
||||
if (
|
||||
node.type === 'group' &&
|
||||
this.isShapeOfType(node, GroupShapeUtil) &&
|
||||
this.focusLayerId !== node.id &&
|
||||
!this.hasAncestor(this.focusLayerShape, node.id) &&
|
||||
(filter?.(node) ?? true)
|
||||
|
@ -4590,15 +4593,16 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
for (const shape of this.selectedShapes) {
|
||||
if (lowestDepth === 0) break
|
||||
|
||||
const isFrame = this.isShapeOfType(shape, FrameShapeUtil)
|
||||
const ancestors = this.getAncestors(shape)
|
||||
if (shape.type === 'frame') ancestors.push(shape)
|
||||
if (isFrame) ancestors.push(shape)
|
||||
|
||||
const depth = shape.type === 'frame' ? ancestors.length + 1 : ancestors.length
|
||||
const depth = isFrame ? ancestors.length + 1 : ancestors.length
|
||||
|
||||
if (depth < lowestDepth) {
|
||||
lowestDepth = depth
|
||||
lowestAncestors = ancestors
|
||||
pasteParentId = shape.type === 'frame' ? shape.id : shape.parentId
|
||||
pasteParentId = isFrame ? shape.id : shape.parentId
|
||||
} else if (depth === lowestDepth) {
|
||||
if (lowestAncestors.length !== ancestors.length) {
|
||||
throw Error(`Ancestors: ${lowestAncestors.length} !== ${ancestors.length}`)
|
||||
|
@ -4836,7 +4840,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (rootShapes.length === 1) {
|
||||
const onlyRoot = rootShapes[0] as TLFrameShape
|
||||
// If the old bounds are in the viewport...
|
||||
if (onlyRoot.type === 'frame') {
|
||||
if (this.isShapeOfType(onlyRoot, FrameShapeUtil)) {
|
||||
while (
|
||||
this.getShapesAtPoint(point).some(
|
||||
(shape) =>
|
||||
|
@ -6015,7 +6019,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (!bbox) return
|
||||
|
||||
const singleFrameShapeId =
|
||||
ids.length === 1 && this.getShapeById(ids[0])?.type === 'frame' ? ids[0] : null
|
||||
ids.length === 1 && this.isShapeOfType(this.getShapeById(ids[0])!, FrameShapeUtil)
|
||||
? ids[0]
|
||||
: null
|
||||
if (!singleFrameShapeId) {
|
||||
// Expand by an extra 32 pixels
|
||||
bbox.expandBy(padding)
|
||||
|
@ -6620,7 +6626,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
shapes = compact(
|
||||
shapes
|
||||
.map((shape) => {
|
||||
if (shape.type === 'group') {
|
||||
if (this.isShapeOfType(shape, GroupShapeUtil)) {
|
||||
return this.getSortedChildIds(shape.id).map((id) => this.getShapeById(id))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { TLArrowShape, TLShapeId } from '@tldraw/tlschema'
|
||||
import { TestEditor } from '../../test/TestEditor'
|
||||
import { TL } from '../../test/jsx'
|
||||
import { GeoShapeUtil } from '../shapes/geo/GeoShapeUtil'
|
||||
|
||||
let editor: TestEditor
|
||||
|
||||
|
@ -184,7 +185,7 @@ describe('arrowBindingsIndex', () => {
|
|||
editor.duplicateShapes()
|
||||
|
||||
const [box1Clone, box2Clone] = editor.selectedShapes
|
||||
.filter((s) => s.type === 'geo')
|
||||
.filter((shape) => editor.isShapeOfType(shape, GeoShapeUtil))
|
||||
.sort((a, b) => a.x - b.x)
|
||||
|
||||
expect(editor.getArrowsBoundTo(box2Clone.id)).toHaveLength(3)
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { TLArrowShape, TLShape, TLShapeId, TLStore } from '@tldraw/tlschema'
|
||||
import { TLArrowShape, TLShape, TLShapeId } from '@tldraw/tlschema'
|
||||
import { Computed, RESET_VALUE, computed, isUninitialized } from 'signia'
|
||||
import { Editor } from '../Editor'
|
||||
import { ArrowShapeUtil } from '../shapes/arrow/ArrowShapeUtil'
|
||||
|
||||
export type TLArrowBindingsIndex = Record<
|
||||
TLShapeId,
|
||||
undefined | { arrowId: TLShapeId; handleId: 'start' | 'end' }[]
|
||||
>
|
||||
|
||||
function isArrowType(shape: any): shape is TLArrowShape {
|
||||
return shape.type === 'arrow'
|
||||
}
|
||||
|
||||
export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsIndex> => {
|
||||
export const arrowBindingsIndex = (editor: Editor): Computed<TLArrowBindingsIndex> => {
|
||||
const { store } = editor
|
||||
const shapeHistory = store.query.filterHistory('shape')
|
||||
const arrowQuery = store.query.records('shape', () => ({ type: { eq: 'arrow' as const } }))
|
||||
function fromScratch() {
|
||||
|
@ -35,6 +34,7 @@ export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsInde
|
|||
|
||||
return bindings2Arrows
|
||||
}
|
||||
|
||||
return computed<TLArrowBindingsIndex>('arrowBindingsIndex', (_lastValue, lastComputedEpoch) => {
|
||||
if (isUninitialized(_lastValue)) {
|
||||
return fromScratch()
|
||||
|
@ -83,7 +83,7 @@ export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsInde
|
|||
|
||||
for (const changes of diff) {
|
||||
for (const newShape of Object.values(changes.added)) {
|
||||
if (isArrowType(newShape)) {
|
||||
if (editor.isShapeOfType(newShape, ArrowShapeUtil)) {
|
||||
const { start, end } = newShape.props
|
||||
if (start.type === 'binding') {
|
||||
addBinding(start.boundShapeId, newShape.id, 'start')
|
||||
|
@ -95,7 +95,11 @@ export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsInde
|
|||
}
|
||||
|
||||
for (const [prev, next] of Object.values(changes.updated) as [TLShape, TLShape][]) {
|
||||
if (!isArrowType(prev) || !isArrowType(next)) continue
|
||||
if (
|
||||
!editor.isShapeOfType(prev, ArrowShapeUtil) ||
|
||||
!editor.isShapeOfType(next, ArrowShapeUtil)
|
||||
)
|
||||
continue
|
||||
|
||||
for (const handle of ['start', 'end'] as const) {
|
||||
const prevTerminal = prev.props[handle]
|
||||
|
@ -120,7 +124,7 @@ export const arrowBindingsIndex = (store: TLStore): Computed<TLArrowBindingsInde
|
|||
}
|
||||
|
||||
for (const prev of Object.values(changes.removed)) {
|
||||
if (isArrowType(prev)) {
|
||||
if (editor.isShapeOfType(prev, ArrowShapeUtil)) {
|
||||
const { start, end } = prev.props
|
||||
if (start.type === 'binding') {
|
||||
removingBinding(start.boundShapeId, prev.id, 'start')
|
||||
|
|
|
@ -11,10 +11,8 @@ import {
|
|||
import { last, structuredClone } from '@tldraw/utils'
|
||||
import { DRAG_DISTANCE } from '../../../../constants'
|
||||
import { uniqueId } from '../../../../utils/data'
|
||||
import { DrawShapeUtil } from '../../../shapes/draw/DrawShapeUtil'
|
||||
import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types'
|
||||
|
||||
import { HighlightShapeUtil } from '../../../shapes/highlight/HighlightShapeUtil'
|
||||
import { StateNode } from '../../../tools/StateNode'
|
||||
|
||||
type DrawableShape = TLDrawShape | TLHighlightShape
|
||||
|
@ -28,11 +26,6 @@ export class Drawing extends StateNode {
|
|||
|
||||
shapeType: DrawableShape['type'] = this.parent.id === 'highlight' ? 'highlight' : 'draw'
|
||||
|
||||
util =
|
||||
this.shapeType === 'highlight'
|
||||
? this.editor.getShapeUtil(HighlightShapeUtil)
|
||||
: this.editor.getShapeUtil(DrawShapeUtil)
|
||||
|
||||
isPen = false
|
||||
|
||||
segmentMode = 'free' as 'free' | 'straight' | 'starting_straight' | 'starting_free'
|
||||
|
|
|
@ -84,7 +84,7 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
|
|||
|
||||
if (editingId && hoveredId !== editingId) {
|
||||
const editingShape = this.editor.getShapeById(editingId)
|
||||
if (editingShape && editingShape.type === 'embed') {
|
||||
if (editingShape && this.editor.isShapeOfType(editingShape, EmbedShapeUtil)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { last } from '@tldraw/utils'
|
|||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { defaultEmptyAs } from '../../../utils/string'
|
||||
import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil'
|
||||
import { GroupShapeUtil } from '../group/GroupShapeUtil'
|
||||
import { TLOnResizeEndHandler } from '../ShapeUtil'
|
||||
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
|
||||
import { TLExportColors } from '../shared/TLExportColors'
|
||||
|
@ -171,15 +172,15 @@ export class FrameShapeUtil extends BaseBoxShapeUtil<TLFrameShape> {
|
|||
}
|
||||
|
||||
onDragShapesOut = (_shape: TLFrameShape, shapes: TLShape[]): void => {
|
||||
const parentId = this.editor.getShapeById(_shape.parentId)
|
||||
const isInGroup = parentId?.type === 'group'
|
||||
const parent = this.editor.getShapeById(_shape.parentId)
|
||||
const isInGroup = parent && this.editor.isShapeOfType(parent, GroupShapeUtil)
|
||||
|
||||
// If frame is in a group, keep the shape
|
||||
// moved out in that group
|
||||
if (isInGroup) {
|
||||
this.editor.reparentShapesById(
|
||||
shapes.map((shape) => shape.id),
|
||||
parentId.id
|
||||
parent.id
|
||||
)
|
||||
} else {
|
||||
this.editor.reparentShapesById(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { StateNode } from '../../../tools/StateNode'
|
||||
import { TLEventHandlers } from '../../../types/event-types'
|
||||
import { GeoShapeUtil } from '../GeoShapeUtil'
|
||||
|
||||
export class Idle extends StateNode {
|
||||
static override id = 'idle'
|
||||
|
@ -15,7 +16,7 @@ export class Idle extends StateNode {
|
|||
onKeyUp: TLEventHandlers['onKeyUp'] = (info) => {
|
||||
if (info.key === 'Enter') {
|
||||
const shape = this.editor.onlySelectedShape
|
||||
if (shape && shape.type === 'geo') {
|
||||
if (shape && this.editor.isShapeOfType(shape, GeoShapeUtil)) {
|
||||
// todo: ensure that this only works with the most recently created shape, not just any geo shape that happens to be selected at the time
|
||||
this.editor.mark('editing shape')
|
||||
this.editor.setEditingId(shape.id)
|
||||
|
|
|
@ -55,7 +55,11 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
|
|||
|
||||
const isHintingOtherGroup =
|
||||
hintingIds.length > 0 &&
|
||||
hintingIds.some((id) => id !== shape.id && this.editor.getShapeById(id)?.type === 'group')
|
||||
hintingIds.some(
|
||||
(id) =>
|
||||
id !== shape.id &&
|
||||
this.editor.isShapeOfType(this.editor.getShapeById(id)!, GroupShapeUtil)
|
||||
)
|
||||
|
||||
if (
|
||||
// always show the outline while we're erasing the group
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { StateNode } from '../../../tools/StateNode'
|
||||
import { TLEventHandlers } from '../../../types/event-types'
|
||||
import { GeoShapeUtil } from '../../geo/GeoShapeUtil'
|
||||
import { TextShapeUtil } from '../TextShapeUtil'
|
||||
|
||||
export class Idle extends StateNode {
|
||||
static override id = 'idle'
|
||||
|
@ -17,7 +19,7 @@ export class Idle extends StateNode {
|
|||
(parent) => !selectedIds.includes(parent.id)
|
||||
)
|
||||
if (hoveringShape.id !== focusLayerId) {
|
||||
if (hoveringShape.type === 'text') {
|
||||
if (this.editor.isShapeOfType(hoveringShape, TextShapeUtil)) {
|
||||
this.editor.setHoveredId(hoveringShape.id)
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +41,7 @@ export class Idle extends StateNode {
|
|||
const { hoveredId } = this.editor
|
||||
if (hoveredId) {
|
||||
const shape = this.editor.getShapeById(hoveredId)!
|
||||
if (shape.type === 'text') {
|
||||
if (this.editor.isShapeOfType(shape, TextShapeUtil)) {
|
||||
requestAnimationFrame(() => {
|
||||
this.editor.setSelectedIds([shape.id])
|
||||
this.editor.setEditingId(shape.id)
|
||||
|
@ -63,7 +65,7 @@ export class Idle extends StateNode {
|
|||
onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
|
||||
if (info.key === 'Enter') {
|
||||
const shape = this.editor.selectedShapes[0]
|
||||
if (shape && shape.type === 'geo') {
|
||||
if (shape && this.editor.isShapeOfType(shape, GeoShapeUtil)) {
|
||||
this.editor.setSelectedTool('select')
|
||||
this.editor.setEditingId(shape.id)
|
||||
this.editor.root.current.value!.transition('editing_shape', {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { pointInPolygon } from '@tldraw/primitives'
|
||||
import { TLScribble, TLShapeId } from '@tldraw/tlschema'
|
||||
import { ScribbleManager } from '../../../managers/ScribbleManager'
|
||||
import { FrameShapeUtil } from '../../../shapes/frame/FrameShapeUtil'
|
||||
import { GroupShapeUtil } from '../../../shapes/group/GroupShapeUtil'
|
||||
import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
||||
|
@ -22,7 +24,8 @@ export class Erasing extends StateNode {
|
|||
.filter(
|
||||
(shape) =>
|
||||
this.editor.isShapeOrAncestorLocked(shape) ||
|
||||
((shape.type === 'group' || shape.type === 'frame') &&
|
||||
((this.editor.isShapeOfType(shape, GroupShapeUtil) ||
|
||||
this.editor.isShapeOfType(shape, FrameShapeUtil)) &&
|
||||
this.editor.isPointInShape(originPagePoint, shape))
|
||||
)
|
||||
.map((shape) => shape.id)
|
||||
|
@ -95,7 +98,7 @@ export class Erasing extends StateNode {
|
|||
const erasing = new Set<TLShapeId>(erasingIdsSet)
|
||||
|
||||
for (const shape of shapesArray) {
|
||||
if (shape.type === 'group') continue
|
||||
if (this.editor.isShapeOfType(shape, GroupShapeUtil)) continue
|
||||
|
||||
// Avoid testing masked shapes, unless the pointer is inside the mask
|
||||
const pageMask = this.editor.getPageMaskById(shape.id)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { TLShapeId } from '@tldraw/tlschema'
|
||||
import { FrameShapeUtil } from '../../../shapes/frame/FrameShapeUtil'
|
||||
import { GroupShapeUtil } from '../../../shapes/group/GroupShapeUtil'
|
||||
import { TLEventHandlers } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
||||
|
@ -15,12 +17,12 @@ export class Pointing extends StateNode {
|
|||
for (const shape of [...this.editor.sortedShapesArray].reverse()) {
|
||||
if (this.editor.isPointInShape(inputs.currentPagePoint, shape)) {
|
||||
// Skip groups
|
||||
if (shape.type === 'group') continue
|
||||
if (this.editor.isShapeOfType(shape, GroupShapeUtil)) continue
|
||||
|
||||
const hitShape = this.editor.getOutermostSelectableShape(shape)
|
||||
|
||||
// If we've hit a frame after hitting any other shape, stop here
|
||||
if (hitShape.type === 'frame' && erasing.size > initialSize) break
|
||||
if (this.editor.isShapeOfType(hitShape, FrameShapeUtil) && erasing.size > initialSize) break
|
||||
|
||||
erasing.add(hitShape.id)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
VecLike,
|
||||
} from '@tldraw/primitives'
|
||||
import { TLPageId, TLShape, TLShapeId } from '@tldraw/tlschema'
|
||||
import { FrameShapeUtil } from '../../../shapes/frame/FrameShapeUtil'
|
||||
import { GroupShapeUtil } from '../../../shapes/group/GroupShapeUtil'
|
||||
import { ShapeUtil } from '../../../shapes/ShapeUtil'
|
||||
import {
|
||||
TLCancelEvent,
|
||||
|
@ -39,7 +41,11 @@ export class Brushing extends StateNode {
|
|||
|
||||
this.excludedShapeIds = new Set(
|
||||
this.editor.shapesArray
|
||||
.filter((shape) => shape.type === 'group' || this.editor.isShapeOrAncestorLocked(shape))
|
||||
.filter(
|
||||
(shape) =>
|
||||
this.editor.isShapeOfType(shape, GroupShapeUtil) ||
|
||||
this.editor.isShapeOrAncestorLocked(shape)
|
||||
)
|
||||
.map((shape) => shape.id)
|
||||
)
|
||||
|
||||
|
@ -130,7 +136,7 @@ export class Brushing extends StateNode {
|
|||
// Should we even test for a single segment intersections? Only if
|
||||
// we're not holding the ctrl key for alternate selection mode
|
||||
// (only wraps count!), or if the shape is a frame.
|
||||
if (ctrlKey || shape.type === 'frame') {
|
||||
if (ctrlKey || this.editor.isShapeOfType(shape, FrameShapeUtil)) {
|
||||
continue testAllShapes
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Vec2d } from '@tldraw/primitives'
|
||||
import { TLGeoShape, TLShape, TLTextShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { debugFlags } from '../../../../utils/debug-flags'
|
||||
import { GroupShapeUtil } from '../../../shapes/group/GroupShapeUtil'
|
||||
import {
|
||||
TLClickEventInfo,
|
||||
TLEventHandlers,
|
||||
|
@ -318,7 +319,7 @@ export class Idle extends StateNode {
|
|||
case 'Enter': {
|
||||
const { selectedShapes } = this.editor
|
||||
|
||||
if (selectedShapes.every((shape) => shape.type === 'group')) {
|
||||
if (selectedShapes.every((shape) => this.editor.isShapeOfType(shape, GroupShapeUtil))) {
|
||||
this.editor.setSelectedIds(
|
||||
selectedShapes.flatMap((shape) => this.editor.getSortedChildIds(shape.id))
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { TLShape } from '@tldraw/tlschema'
|
||||
import { GroupShapeUtil } from '../../../shapes/group/GroupShapeUtil'
|
||||
import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
||||
|
@ -35,7 +36,7 @@ export class PointingShape extends StateNode {
|
|||
|
||||
const parent = this.editor.getParentShape(info.shape)
|
||||
|
||||
if (parent && parent.type === 'group') {
|
||||
if (parent && this.editor.isShapeOfType(parent, GroupShapeUtil)) {
|
||||
this.editor.cancelDoubleClick()
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
VecLike,
|
||||
} from '@tldraw/primitives'
|
||||
import { TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'
|
||||
import { FrameShapeUtil } from '../../../shapes/frame/FrameShapeUtil'
|
||||
import {
|
||||
TLEnterEventHandler,
|
||||
TLEventHandlers,
|
||||
|
@ -369,12 +370,12 @@ export class Resizing extends StateNode {
|
|||
const shape = this.editor.getShapeById(id)
|
||||
if (shape) {
|
||||
shapeSnapshots.set(shape.id, this._createShapeSnapshot(shape))
|
||||
if (shape.type === 'frame' && selectedIds.length === 1) return
|
||||
if (this.editor.isShapeOfType(shape, FrameShapeUtil) && selectedIds.length === 1) return
|
||||
this.editor.visitDescendants(shape.id, (descendantId) => {
|
||||
const descendent = this.editor.getShapeById(descendantId)
|
||||
if (descendent) {
|
||||
shapeSnapshots.set(descendent.id, this._createShapeSnapshot(descendent))
|
||||
if (descendent.type === 'frame') {
|
||||
if (this.editor.isShapeOfType(descendent, FrameShapeUtil)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { intersectLineSegmentPolyline, pointInPolygon } from '@tldraw/primitives
|
|||
import { TLScribble, TLShape, TLShapeId } from '@tldraw/tlschema'
|
||||
import { ScribbleManager } from '../../../managers/ScribbleManager'
|
||||
import { ShapeUtil } from '../../../shapes/ShapeUtil'
|
||||
import { FrameShapeUtil } from '../../../shapes/frame/FrameShapeUtil'
|
||||
import { GroupShapeUtil } from '../../../shapes/group/GroupShapeUtil'
|
||||
import { TLEventHandlers } from '../../../types/event-types'
|
||||
import { StateNode } from '../../StateNode'
|
||||
|
||||
|
@ -104,9 +106,9 @@ export class ScribbleBrushing extends StateNode {
|
|||
util = this.editor.getShapeUtil(shape)
|
||||
|
||||
if (
|
||||
shape.type === 'group' ||
|
||||
this.editor.isShapeOfType(shape, GroupShapeUtil) ||
|
||||
this.newlySelectedIds.has(shape.id) ||
|
||||
(shape.type === 'frame' &&
|
||||
(this.editor.isShapeOfType(shape, FrameShapeUtil) &&
|
||||
util.hitTestPoint(shape, this.editor.getPointInShapeSpace(shape, originPagePoint))) ||
|
||||
this.editor.isShapeOrAncestorLocked(shape)
|
||||
) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { TLArrowShape, TLShapePartial, createShapeId } from '@tldraw/tlschema'
|
||||
import { ArrowShapeUtil } from '../editor/shapes/arrow/ArrowShapeUtil'
|
||||
import { TestEditor } from './TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
@ -186,7 +187,11 @@ describe('When duplicating shapes that include arrows', () => {
|
|||
.selectAll()
|
||||
.deleteShapes()
|
||||
.createShapes(shapes)
|
||||
.select(...editor.shapesArray.filter((s) => s.type === 'arrow').map((s) => s.id))
|
||||
.select(
|
||||
...editor.shapesArray
|
||||
.filter((s) => editor.isShapeOfType(s, ArrowShapeUtil))
|
||||
.map((s) => s.id)
|
||||
)
|
||||
|
||||
const boundsBefore = editor.selectionBounds!
|
||||
editor.duplicateShapes()
|
||||
|
|
|
@ -6,6 +6,7 @@ import { TestEditor } from '../TestEditor'
|
|||
|
||||
import { defaultShapes } from '../../config/defaultShapes'
|
||||
import { defineShape } from '../../config/defineShape'
|
||||
import { ArrowShapeUtil } from '../../editor/shapes/arrow/ArrowShapeUtil'
|
||||
import { getSnapLines } from '../testutils/getSnapLines'
|
||||
|
||||
type __TopLeftSnapOnlyShape = any
|
||||
|
@ -1948,7 +1949,9 @@ describe('translating a shape with a bound shape', () => {
|
|||
props: { start: { type: 'binding' }, end: { type: 'binding' } },
|
||||
})
|
||||
|
||||
const newArrow = editor.shapesArray.find((s) => s.type === 'arrow' && s.id !== arrow1)
|
||||
const newArrow = editor.shapesArray.find(
|
||||
(s) => editor.isShapeOfType(s, ArrowShapeUtil) && s.id !== arrow1
|
||||
)
|
||||
expect(newArrow).toMatchObject({
|
||||
props: { start: { type: 'binding' }, end: { type: 'point' } },
|
||||
})
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { TLGeoShape, TLNoteShape, TLShape } from '@tldraw/tlschema'
|
||||
import { debugFlags } from './debug-flags'
|
||||
import { getBrowserCanvasMaxSize } from './getBrowserCanvasMaxSize'
|
||||
import { setPhysChunk } from './png'
|
||||
|
@ -170,13 +169,3 @@ export function getTextBoundingBox(text: SVGTextElement) {
|
|||
|
||||
return bbox
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function isGeoShape(shape: TLShape): shape is TLGeoShape {
|
||||
return shape.type === 'geo'
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function isNoteShape(shape: TLShape): shape is TLNoteShape {
|
||||
return shape.type === 'note'
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
DEFAULT_BOOKMARK_WIDTH,
|
||||
Editor,
|
||||
EmbedShapeUtil,
|
||||
GroupShapeUtil,
|
||||
TLEmbedShape,
|
||||
TLShapeId,
|
||||
TLShapePartial,
|
||||
|
@ -388,7 +389,8 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
|
|||
readonlyOk: false,
|
||||
onSelect(source) {
|
||||
trackEvent('group-shapes', { source })
|
||||
if (editor.selectedShapes.length === 1 && editor.selectedShapes[0].type === 'group') {
|
||||
const { onlySelectedShape } = editor
|
||||
if (onlySelectedShape && editor.isShapeOfType(onlySelectedShape, GroupShapeUtil)) {
|
||||
editor.mark('ungroup')
|
||||
editor.ungroupShapes(editor.selectedIds)
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { useEditor } from '@tldraw/editor'
|
||||
import {
|
||||
ArrowShapeUtil,
|
||||
DrawShapeUtil,
|
||||
GroupShapeUtil,
|
||||
LineShapeUtil,
|
||||
useEditor,
|
||||
} from '@tldraw/editor'
|
||||
import { useValue } from 'signia-react'
|
||||
|
||||
export function useOnlyFlippableShape() {
|
||||
|
@ -11,10 +17,10 @@ export function useOnlyFlippableShape() {
|
|||
selectedShapes.length === 1 &&
|
||||
selectedShapes.every(
|
||||
(shape) =>
|
||||
shape.type === 'group' ||
|
||||
shape.type === 'arrow' ||
|
||||
shape.type === 'line' ||
|
||||
shape.type === 'draw'
|
||||
editor.isShapeOfType(shape, GroupShapeUtil) ||
|
||||
editor.isShapeOfType(shape, ArrowShapeUtil) ||
|
||||
editor.isShapeOfType(shape, LineShapeUtil) ||
|
||||
editor.isShapeOfType(shape, DrawShapeUtil)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue