From 7ffda2335ce1c9b20e453436db438b08d03e9a87 Mon Sep 17 00:00:00 2001 From: David Sheldrick Date: Mon, 13 Nov 2023 14:31:27 +0000 Subject: [PATCH] No impure getters pt3 (#2203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow up to #2189 and #2202 ### Change Type - [x] `patch` — Bug fix [^1]: publishes a `patch` release, for devDependencies use `internal` [^2]: will not publish a new version --- .../examples/e2e/tests/test-clipboard.spec.ts | 12 +- apps/examples/e2e/tests/test-smoke.spec.ts | 2 +- .../src/examples/ShapeMetaExample.tsx | 2 +- .../examples/only-editor/MiniSelectTool.ts | 2 +- packages/editor/api-report.md | 4 + packages/editor/api/api.json | 80 +++++++++- packages/editor/src/lib/components/Canvas.tsx | 21 ++- .../DefaultSelectionForeground.tsx | 2 +- packages/editor/src/lib/editor/Editor.ts | 44 ++--- .../src/lib/editor/managers/SnapManager.ts | 2 +- packages/editor/src/lib/utils/rotation.ts | 2 +- .../lib/canvas/TldrawSelectionForeground.tsx | 4 +- .../src/lib/defaultExternalContentHandlers.ts | 2 +- .../lib/shapes/arrow/ArrowShapeUtil.test.ts | 14 +- .../src/lib/shapes/arrow/ArrowShapeUtil.tsx | 2 +- .../src/lib/shapes/arrow/toolStates/Idle.ts | 2 +- .../src/lib/shapes/geo/GeoShapeTool.test.ts | 2 +- .../src/lib/shapes/geo/toolStates/Idle.ts | 2 +- .../src/lib/shapes/image/ImageShapeUtil.tsx | 2 +- .../src/lib/shapes/text/toolStates/Idle.ts | 2 +- .../SelectTool/children/Crop/children/Idle.ts | 2 +- .../children/Crop/children/TranslatingCrop.ts | 2 +- .../lib/tools/SelectTool/children/Cropping.ts | 4 +- .../src/lib/tools/SelectTool/children/Idle.ts | 14 +- .../SelectTool/children/PointingCropHandle.ts | 2 +- .../children/PointingResizeHandle.ts | 2 +- .../lib/tools/SelectTool/children/Resizing.ts | 9 +- .../tools/SelectTool/children/Translating.ts | 4 +- .../getShouldEnterCropModeOnPointerDown.ts | 2 +- .../src/lib/ui/components/ContextMenu.tsx | 6 +- .../src/lib/ui/components/DebugPanel.tsx | 2 +- .../src/lib/ui/components/EditLinkDialog.tsx | 6 +- .../tldraw/src/lib/ui/hooks/useActions.tsx | 7 +- .../src/lib/ui/hooks/useContextMenuSchema.tsx | 2 +- .../lib/ui/hooks/useHasLinkShapeSelected.ts | 2 +- .../tldraw/src/lib/ui/hooks/useMenuSchema.tsx | 4 +- .../src/lib/ui/hooks/useOnlyFlippableShape.ts | 2 +- .../src/lib/ui/hooks/useShowAutoSizeToggle.ts | 2 +- packages/tldraw/src/test/EraserTool.test.ts | 2 +- .../tldraw/src/test/HighlightShape.test.ts | 2 +- packages/tldraw/src/test/SelectTool.test.ts | 12 +- .../src/test/arrowBindingsIndex.test.tsx | 29 ++-- .../tldraw/src/test/arrows-megabus.test.ts | 2 +- .../commands/getInitialMetaForShape.test.ts | 4 +- .../test/commands/moveShapesToPage.test.ts | 6 +- .../tldraw/src/test/commands/stretch.test.tsx | 4 +- packages/tldraw/src/test/flipShapes.test.ts | 2 +- packages/tldraw/src/test/frames.test.ts | 150 +++++++++--------- packages/tldraw/src/test/paste.test.ts | 4 +- packages/tldraw/src/test/select.test.tsx | 4 +- .../tldraw/src/test/selection-omnibus.test.ts | 8 +- packages/tldraw/src/test/translating.test.ts | 10 +- 52 files changed, 309 insertions(+), 209 deletions(-) diff --git a/apps/examples/e2e/tests/test-clipboard.spec.ts b/apps/examples/e2e/tests/test-clipboard.spec.ts index 55b9f2cc3..b7ebf8226 100644 --- a/apps/examples/e2e/tests/test-clipboard.spec.ts +++ b/apps/examples/e2e/tests/test-clipboard.spec.ts @@ -24,7 +24,7 @@ test.describe.skip('clipboard tests', () => { await page.mouse.up() expect(await page.evaluate(() => editor.currentPageShapes.length)).toBe(1) - expect(await page.evaluate(() => editor.selectedShapes.length)).toBe(1) + expect(await page.evaluate(() => editor.getSelectedShapes().length)).toBe(1) await page.keyboard.down('Control') await page.keyboard.press('KeyC') @@ -33,7 +33,7 @@ test.describe.skip('clipboard tests', () => { await page.keyboard.up('Control') expect(await page.evaluate(() => editor.currentPageShapes.length)).toBe(2) - expect(await page.evaluate(() => editor.selectedShapes.length)).toBe(1) + expect(await page.evaluate(() => editor.getSelectedShapes().length)).toBe(1) }) test('copy and paste from main menu', async ({ page }) => { @@ -43,7 +43,7 @@ test.describe.skip('clipboard tests', () => { await page.mouse.up() expect(await page.evaluate(() => editor.currentPageShapes.length)).toBe(1) - expect(await page.evaluate(() => editor.selectedShapes.length)).toBe(1) + expect(await page.evaluate(() => editor.getSelectedShapes().length)).toBe(1) await page.getByTestId('main.menu').click() await page.getByTestId('menu-item.edit').click() @@ -54,7 +54,7 @@ test.describe.skip('clipboard tests', () => { await page.getByTestId('menu-item.paste').click() expect(await page.evaluate(() => editor.currentPageShapes.length)).toBe(2) - expect(await page.evaluate(() => editor.selectedShapes.length)).toBe(1) + expect(await page.evaluate(() => editor.getSelectedShapes().length)).toBe(1) }) test('copy and paste from context menu', async ({ page }) => { @@ -64,7 +64,7 @@ test.describe.skip('clipboard tests', () => { await page.mouse.up() expect(await page.evaluate(() => editor.currentPageShapes.length)).toBe(1) - expect(await page.evaluate(() => editor.selectedShapes.length)).toBe(1) + expect(await page.evaluate(() => editor.getSelectedShapes().length)).toBe(1) await page.mouse.click(100, 100, { button: 'right' }) await page.getByTestId('menu-item.copy').click() @@ -74,6 +74,6 @@ test.describe.skip('clipboard tests', () => { await page.getByTestId('menu-item.paste').click() expect(await page.evaluate(() => editor.currentPageShapes.length)).toBe(2) - expect(await page.evaluate(() => editor.selectedShapes.length)).toBe(1) + expect(await page.evaluate(() => editor.getSelectedShapes().length)).toBe(1) }) }) diff --git a/apps/examples/e2e/tests/test-smoke.spec.ts b/apps/examples/e2e/tests/test-smoke.spec.ts index 42e138cc1..2504edcca 100644 --- a/apps/examples/e2e/tests/test-smoke.spec.ts +++ b/apps/examples/e2e/tests/test-smoke.spec.ts @@ -65,7 +65,7 @@ test.describe('smoke tests', () => { expect(await getAllShapeTypes(page)).toEqual(['geo']) const getSelectedShapeColor = async () => - await page.evaluate(() => (editor.selectedShapes[0] as TLGeoShape).props.color) + await page.evaluate(() => (editor.getSelectedShapes()[0] as TLGeoShape).props.color) // change style expect(await getSelectedShapeColor()).toBe('black') diff --git a/apps/examples/src/examples/ShapeMetaExample.tsx b/apps/examples/src/examples/ShapeMetaExample.tsx index d2c5db549..4d36c2d9c 100644 --- a/apps/examples/src/examples/ShapeMetaExample.tsx +++ b/apps/examples/src/examples/ShapeMetaExample.tsx @@ -24,7 +24,7 @@ type ShapeWithMyMeta = TLShape & { meta: { label: string } } export const ShapeLabelUiWithHelper = track(function ShapeLabelUiWithHelper() { const editor = useEditor() - const onlySelectedShape = editor.onlySelectedShape as ShapeWithMyMeta | null + const onlySelectedShape = editor.getOnlySelectedShape() as ShapeWithMyMeta | null if (!onlySelectedShape) { return null diff --git a/apps/examples/src/examples/only-editor/MiniSelectTool.ts b/apps/examples/src/examples/only-editor/MiniSelectTool.ts index 7cde8cef2..2904cbeb7 100644 --- a/apps/examples/src/examples/only-editor/MiniSelectTool.ts +++ b/apps/examples/src/examples/only-editor/MiniSelectTool.ts @@ -103,7 +103,7 @@ class PointingState extends StateNode { override onPointerMove: TLEventHandlers['onPointerUp'] = () => { if (this.editor.inputs.isDragging) { - this.parent.transition('dragging', { shapes: [...this.editor.selectedShapes] }) + this.parent.transition('dragging', { shapes: [...this.editor.getSelectedShapes()] }) } } } diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index 88f575c71..a9f8c4adc 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -689,6 +689,7 @@ export class Editor extends EventEmitter { getInitialMetaForShape(_shape: TLShape): JsonObject; getInstanceState(): TLInstance; getIsMenuOpen(): boolean; + getOnlySelectedShape(): null | TLShape; getOpenMenus(): string[]; getOutermostSelectableShape(shape: TLShape | TLShapeId, filter?: (shape: TLShape) => boolean): TLShape; getPage(page: TLPage | TLPageId): TLPage | undefined; @@ -698,6 +699,7 @@ export class Editor extends EventEmitter { getPointInShapeSpace(shape: TLShape | TLShapeId, point: VecLike): Vec2d; getSelectedShapeAtPoint(point: VecLike): TLShape | undefined; getSelectedShapeIds(): TLShapeId[]; + getSelectedShapes(): TLShape[]; getShape(shape: TLParentId | TLShape): T | undefined; getShapeAncestors(shape: TLShape | TLShapeId, acc?: TLShape[]): TLShape[]; getShapeAndDescendantIds(ids: TLShapeId[]): Set; @@ -791,6 +793,7 @@ export class Editor extends EventEmitter { mark(markId?: string, onUndo?: boolean, onRedo?: boolean): this; moveShapesToPage(shapes: TLShape[] | TLShapeId[], pageId: TLPageId): this; nudgeShapes(shapes: TLShape[] | TLShapeId[], offset: VecLike, historyOptions?: TLCommandHistoryOptions): this; + // @deprecated (undocumented) get onlySelectedShape(): null | TLShape; // @deprecated (undocumented) get openMenus(): string[]; @@ -849,6 +852,7 @@ export class Editor extends EventEmitter { selectAll(): this; // @deprecated (undocumented) get selectedShapeIds(): TLShapeId[]; + // @deprecated (undocumented) get selectedShapes(): TLShape[]; get selectionPageBounds(): Box2d | null; get selectionRotatedPageBounds(): Box2d | undefined; diff --git a/packages/editor/api/api.json b/packages/editor/api/api.json index 4380678f0..61239b92b 100644 --- a/packages/editor/api/api.json +++ b/packages/editor/api/api.json @@ -9068,7 +9068,7 @@ { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#duplicateShapes:member(1)", - "docComment": "/**\n * Duplicate shapes.\n *\n * @param shapes - The shapes (or shape ids) to duplicate.\n *\n * @param offset - The offset (in pixels) to apply to the duplicated shapes.\n *\n * @example\n * ```ts\n * editor.duplicateShapes(['box1', 'box2'], { x: 8, y: 8 })\n * editor.duplicateShapes(editor.selectedShapes, { x: 8, y: 8 })\n * ```\n *\n * @public\n */\n", + "docComment": "/**\n * Duplicate shapes.\n *\n * @param shapes - The shapes (or shape ids) to duplicate.\n *\n * @param offset - The offset (in pixels) to apply to the duplicated shapes.\n *\n * @example\n * ```ts\n * editor.duplicateShapes(['box1', 'box2'], { x: 8, y: 8 })\n * editor.duplicateShapes(editor.getSelectedShapes(), { x: 8, y: 8 })\n * ```\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", @@ -10517,6 +10517,42 @@ "isAbstract": false, "name": "getIsMenuOpen" }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#getOnlySelectedShape:member(1)", + "docComment": "/**\n * The app's only selected shape.\n *\n * @returns Null if there is no shape or more than one selected shape, otherwise the selected shape.\n *\n * @public @readonly\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "getOnlySelectedShape(): " + }, + { + "kind": "Content", + "text": "null | " + }, + { + "kind": "Reference", + "text": "TLShape", + "canonicalReference": "@tldraw/tlschema!TLShape:type" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 3 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "getOnlySelectedShape" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#getOpenMenus:member(1)", @@ -11045,6 +11081,42 @@ "isAbstract": false, "name": "getSelectedShapeIds" }, + { + "kind": "Method", + "canonicalReference": "@tldraw/editor!Editor#getSelectedShapes:member(1)", + "docComment": "/**\n * An array containing all of the currently selected shapes.\n *\n * @public @readonly\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "getSelectedShapes(): " + }, + { + "kind": "Reference", + "text": "TLShape", + "canonicalReference": "@tldraw/tlschema!TLShape:type" + }, + { + "kind": "Content", + "text": "[]" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 3 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [], + "isOptional": false, + "isAbstract": false, + "name": "getSelectedShapes" + }, { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#getShape:member(1)", @@ -14029,7 +14101,7 @@ { "kind": "Method", "canonicalReference": "@tldraw/editor!Editor#nudgeShapes:member(1)", - "docComment": "/**\n * Move shapes by a delta.\n *\n * @param shapes - The shapes (or shape ids) to move.\n *\n * @param direction - The direction in which to move the shapes.\n *\n * @param historyOptions - The history options for the change.\n *\n * @example\n * ```ts\n * editor.nudgeShapes(['box1', 'box2'], { x: 8, y: 8 })\n * editor.nudgeShapes(editor.selectedShapes, { x: 8, y: 8 }, { squashing: true })\n * ```\n *\n */\n", + "docComment": "/**\n * Move shapes by a delta.\n *\n * @param shapes - The shapes (or shape ids) to move.\n *\n * @param direction - The direction in which to move the shapes.\n *\n * @param historyOptions - The history options for the change.\n *\n * @example\n * ```ts\n * editor.nudgeShapes(['box1', 'box2'], { x: 8, y: 8 })\n * editor.nudgeShapes(editor.getSelectedShapes(), { x: 8, y: 8 }, { squashing: true })\n * ```\n *\n */\n", "excerptTokens": [ { "kind": "Content", @@ -14125,7 +14197,7 @@ { "kind": "Property", "canonicalReference": "@tldraw/editor!Editor#onlySelectedShape:member", - "docComment": "/**\n * The app's only selected shape.\n *\n * @returns Null if there is no shape or more than one selected shape, otherwise the selected shape.\n *\n * @example\n * ```ts\n * editor.onlySelectedShape\n * ```\n *\n * @public @readonly\n */\n", + "docComment": "/**\n * @deprecated\n *\n * Use `getOnlySelectedShape` instead.\n */\n", "excerptTokens": [ { "kind": "Content", @@ -15775,7 +15847,7 @@ { "kind": "Property", "canonicalReference": "@tldraw/editor!Editor#selectedShapes:member", - "docComment": "/**\n * An array containing all of the currently selected shapes.\n *\n * @example\n * ```ts\n * editor.selectedShapes\n * ```\n *\n * @public @readonly\n */\n", + "docComment": "/**\n * @deprecated\n *\n * Use `getSelectedShapes` instead.\n */\n", "excerptTokens": [ { "kind": "Content", diff --git a/packages/editor/src/lib/components/Canvas.tsx b/packages/editor/src/lib/components/Canvas.tsx index 64bc7be01..c4978f210 100644 --- a/packages/editor/src/lib/components/Canvas.tsx +++ b/packages/editor/src/lib/components/Canvas.tsx @@ -214,7 +214,9 @@ function HandlesWrapper() { const isCoarse = useValue('coarse pointer', () => editor.getInstanceState().isCoarsePointer, [ editor, ]) - const onlySelectedShape = useValue('onlySelectedShape', () => editor.onlySelectedShape, [editor]) + const onlySelectedShape = useValue('onlySelectedShape', () => editor.getOnlySelectedShape(), [ + editor, + ]) const isChangingStyle = useValue( 'isChangingStyle', () => editor.getInstanceState().isChangingStyle, @@ -225,13 +227,24 @@ function HandlesWrapper() { ]) const handles = useValue( 'handles', - () => (editor.onlySelectedShape ? editor.getShapeHandles(editor.onlySelectedShape) : undefined), + () => { + const onlySelectedShape = editor.getOnlySelectedShape() + if (onlySelectedShape) { + return editor.getShapeHandles(onlySelectedShape) + } + return undefined + }, [editor] ) const transform = useValue( 'transform', - () => - editor.onlySelectedShape ? editor.getShapePageTransform(editor.onlySelectedShape) : undefined, + () => { + const onlySelectedShape = editor.getOnlySelectedShape() + if (onlySelectedShape) { + return editor.getShapePageTransform(onlySelectedShape) + } + return undefined + }, [editor] ) diff --git a/packages/editor/src/lib/components/default-components/DefaultSelectionForeground.tsx b/packages/editor/src/lib/components/default-components/DefaultSelectionForeground.tsx index 7e8e6c1c6..72ca6dc54 100644 --- a/packages/editor/src/lib/components/default-components/DefaultSelectionForeground.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultSelectionForeground.tsx @@ -20,7 +20,7 @@ export const DefaultSelectionForeground: TLSelectionForegroundComponent = ({ const editor = useEditor() const rSvg = useRef(null) - const onlyShape = useValue('only selected shape', () => editor.onlySelectedShape, [editor]) + const onlyShape = useValue('only selected shape', () => editor.getOnlySelectedShape(), [editor]) // if all shapes have an expandBy for the selection outline, we can expand by the l const expandOutlineBy = onlyShape diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index 279a7bf10..56e9ad038 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -1002,7 +1002,7 @@ export class Editor extends EventEmitter { }, extras: { activeStateNode: this.root.path.get(), - selectedShapes: this.selectedShapes, + selectedShapes: this.getSelectedShapes(), editingShape: this.editingShapeId ? this.getShape(this.editingShapeId) : undefined, inputs: this.inputs, }, @@ -1481,19 +1481,21 @@ export class Editor extends EventEmitter { /** * An array containing all of the currently selected shapes. * - * @example - * ```ts - * editor.selectedShapes - * ``` - * * @public * @readonly */ - @computed get selectedShapes(): TLShape[] { + @computed getSelectedShapes(): TLShape[] { const { selectedShapeIds } = this.getCurrentPageState() return compact(selectedShapeIds.map((id) => this.store.get(id))) } + /** + * @deprecated Use `getSelectedShapes` instead. + */ + get selectedShapes() { + return this.getSelectedShapes() + } + /** * Select one or more shapes. * @@ -1652,22 +1654,24 @@ export class Editor extends EventEmitter { /** * The app's only selected shape. * - * @example - * ```ts - * editor.onlySelectedShape - * ``` - * * @returns Null if there is no shape or more than one selected shape, otherwise the selected * shape. * * @public * @readonly */ - @computed get onlySelectedShape(): TLShape | null { - const { selectedShapes } = this + @computed getOnlySelectedShape(): TLShape | null { + const selectedShapes = this.getSelectedShapes() return selectedShapes.length === 1 ? selectedShapes[0] : null } + /** + * @deprecated Use `getOnlySelectedShape` instead. + */ + get onlySelectedShape() { + return this.getOnlySelectedShape() + } + /** * The current page bounds of all the selected shapes. If the * selection is rotated, then these bounds are the axis-aligned @@ -5138,7 +5142,7 @@ export class Editor extends EventEmitter { * @example * ```ts * editor.nudgeShapes(['box1', 'box2'], { x: 8, y: 8 }) - * editor.nudgeShapes(editor.selectedShapes, { x: 8, y: 8 }, { squashing: true }) + * editor.nudgeShapes(editor.getSelectedShapes(), { x: 8, y: 8 }, { squashing: true }) * ``` * * @param shapes - The shapes (or shape ids) to move. @@ -5201,7 +5205,7 @@ export class Editor extends EventEmitter { * @example * ```ts * editor.duplicateShapes(['box1', 'box2'], { x: 8, y: 8 }) - * editor.duplicateShapes(editor.selectedShapes, { x: 8, y: 8 }) + * editor.duplicateShapes(editor.getSelectedShapes(), { x: 8, y: 8 }) * ``` * * @param shapes - The shapes (or shape ids) to duplicate. @@ -7288,7 +7292,7 @@ export class Editor extends EventEmitter { private _selectionSharedStyles = computed( '_selectionSharedStyles', () => { - const { selectedShapes } = this + const selectedShapes = this.getSelectedShapes() const sharedStyles = new SharedStyleMap() for (const selectedShape of selectedShapes) { @@ -7418,7 +7422,7 @@ export class Editor extends EventEmitter { * @param historyOptions - The history options for the change. */ setOpacityForSelectedShapes(opacity: number, historyOptions?: TLCommandHistoryOptions): this { - const { selectedShapes } = this + const selectedShapes = this.getSelectedShapes() if (selectedShapes.length > 0) { const shapesToUpdate: TLShape[] = [] @@ -7505,7 +7509,7 @@ export class Editor extends EventEmitter { value: T, historyOptions?: TLCommandHistoryOptions ): this { - const { selectedShapes } = this + const selectedShapes = this.getSelectedShapes() if (selectedShapes.length > 0) { const updates: { @@ -7847,7 +7851,7 @@ export class Editor extends EventEmitter { let lowestAncestors: TLShape[] = [] // Among the selected shapes, find the shape with the fewest ancestors and use its first ancestor. - for (const shape of this.selectedShapes) { + for (const shape of this.getSelectedShapes()) { if (lowestDepth === 0) break const isFrame = this.isShapeOfType(shape, 'frame') diff --git a/packages/editor/src/lib/editor/managers/SnapManager.ts b/packages/editor/src/lib/editor/managers/SnapManager.ts index 7575ee083..0c7b34a90 100644 --- a/packages/editor/src/lib/editor/managers/SnapManager.ts +++ b/packages/editor/src/lib/editor/managers/SnapManager.ts @@ -286,7 +286,7 @@ export class SnapManager { // This needs to be external from any expensive work @computed get currentCommonAncestor() { - return this.editor.findCommonAncestor(this.editor.selectedShapes) + return this.editor.findCommonAncestor(this.editor.getSelectedShapes()) } // Points which belong to snappable shapes diff --git a/packages/editor/src/lib/utils/rotation.ts b/packages/editor/src/lib/utils/rotation.ts index 2087c66d6..9ced76be5 100644 --- a/packages/editor/src/lib/utils/rotation.ts +++ b/packages/editor/src/lib/utils/rotation.ts @@ -7,11 +7,11 @@ import { Vec2d } from '../primitives/Vec2d' /** @internal */ export function getRotationSnapshot({ editor }: { editor: Editor }): TLRotationSnapshot | null { + const selectedShapes = editor.getSelectedShapes() const { selectionRotation, selectionRotatedPageBounds: selectionBounds, inputs: { originPagePoint }, - selectedShapes, } = editor // todo: this assumes we're rotating the selected shapes diff --git a/packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx b/packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx index c7d3d78d7..f66ef80c0 100644 --- a/packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx +++ b/packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx @@ -37,8 +37,8 @@ export const TldrawSelectionForeground: TLSelectionForegroundComponent = track( !editor.getIsMenuOpen() && editor.getInstanceState().cursor.type === 'default' const isCoarsePointer = editor.getInstanceState().isCoarsePointer - const shapes = editor.selectedShapes - const onlyShape = editor.onlySelectedShape + const shapes = editor.getSelectedShapes() + 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 diff --git a/packages/tldraw/src/lib/defaultExternalContentHandlers.ts b/packages/tldraw/src/lib/defaultExternalContentHandlers.ts index 214809ded..b3f900b8b 100644 --- a/packages/tldraw/src/lib/defaultExternalContentHandlers.ts +++ b/packages/tldraw/src/lib/defaultExternalContentHandlers.ts @@ -476,7 +476,7 @@ function centerSelecitonAroundPoint(editor: Editor, position: VecLike) { const offset = selectionPageBounds!.center.sub(position) editor.updateShapes( - editor.selectedShapes.map((shape) => { + editor.getSelectedShapes().map((shape) => { const localRotation = editor.getShapeParentTransform(shape).decompose().rotation const localDelta = Vec2d.Rot(offset, -localRotation) return { diff --git a/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts index 804f83a72..ac97bc689 100644 --- a/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +++ b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts @@ -488,24 +488,24 @@ describe("an arrow's parents", () => { editor.setCurrentTool('frame') editor.pointerDown(0, 0).pointerMove(100, 100).pointerUp() - frameId = editor.onlySelectedShape!.id + frameId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor.pointerDown(10, 10).pointerMove(20, 20).pointerUp() - boxAid = editor.onlySelectedShape!.id + boxAid = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor.pointerDown(10, 80).pointerMove(20, 90).pointerUp() - boxBid = editor.onlySelectedShape!.id + boxBid = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor.pointerDown(110, 10).pointerMove(120, 20).pointerUp() - boxCid = editor.onlySelectedShape!.id + boxCid = editor.getOnlySelectedShape()!.id }) it("are updated when the arrow's bound shapes change", () => { // draw arrow from a to empty space within frame, but don't pointer up yet editor.setCurrentTool('arrow') editor.pointerDown(15, 15).pointerMove(50, 50) - const arrowId = editor.onlySelectedShape!.id + const arrowId = editor.getOnlySelectedShape()!.id expect(editor.getShape(arrowId)).toMatchObject({ props: { @@ -540,7 +540,7 @@ describe("an arrow's parents", () => { // draw arrow from a to b editor.setCurrentTool('arrow') editor.pointerDown(15, 15).pointerMove(15, 85).pointerUp() - const arrowId = editor.onlySelectedShape!.id + const arrowId = editor.getOnlySelectedShape()!.id expect(editor.getShape(arrowId)).toMatchObject({ parentId: frameId, @@ -564,7 +564,7 @@ describe("an arrow's parents", () => { // draw arrow from a to c editor.setCurrentTool('arrow') editor.pointerDown(15, 15).pointerMove(115, 15).pointerUp() - const arrowId = editor.onlySelectedShape!.id + const arrowId = editor.getOnlySelectedShape()!.id expect(editor.getShape(arrowId)).toMatchObject({ parentId: editor.currentPageId, props: { diff --git a/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx index 99246c590..c237fb0a9 100644 --- a/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx @@ -487,7 +487,7 @@ export class ArrowShapeUtil extends ShapeUtil { // Not a class component, but eslint can't tell that :( // eslint-disable-next-line react-hooks/rules-of-hooks const theme = useDefaultColorTheme() - const onlySelectedShape = this.editor.onlySelectedShape + const onlySelectedShape = this.editor.getOnlySelectedShape() const shouldDisplayHandles = this.editor.isInAny( 'select.idle', diff --git a/packages/tldraw/src/lib/shapes/arrow/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/arrow/toolStates/Idle.ts index bf9cb524d..f898d6693 100644 --- a/packages/tldraw/src/lib/shapes/arrow/toolStates/Idle.ts +++ b/packages/tldraw/src/lib/shapes/arrow/toolStates/Idle.ts @@ -18,7 +18,7 @@ export class Idle extends StateNode { override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { if (info.key === 'Enter') { if (this.editor.getInstanceState().isReadonly) return null - const { onlySelectedShape } = this.editor + const onlySelectedShape = this.editor.getOnlySelectedShape() // If the only selected shape is editable, start editing it if ( onlySelectedShape && diff --git a/packages/tldraw/src/lib/shapes/geo/GeoShapeTool.test.ts b/packages/tldraw/src/lib/shapes/geo/GeoShapeTool.test.ts index 053f57ef5..45b4f3338 100644 --- a/packages/tldraw/src/lib/shapes/geo/GeoShapeTool.test.ts +++ b/packages/tldraw/src/lib/shapes/geo/GeoShapeTool.test.ts @@ -103,7 +103,7 @@ describe('When in the idle state', () => { expect(editor.currentPageShapes.length).toBe(2) editor.selectAll() - expect(editor.selectedShapes.length).toBe(2) + expect(editor.getSelectedShapes().length).toBe(2) editor.keyUp('Enter') editor.expectPathToBe('root.select.idle') diff --git a/packages/tldraw/src/lib/shapes/geo/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/geo/toolStates/Idle.ts index 94528966d..536c1fd49 100644 --- a/packages/tldraw/src/lib/shapes/geo/toolStates/Idle.ts +++ b/packages/tldraw/src/lib/shapes/geo/toolStates/Idle.ts @@ -15,7 +15,7 @@ export class Idle extends StateNode { if (info.key === 'Enter') { if (this.editor.getInstanceState().isReadonly) return null - const { onlySelectedShape } = this.editor + const onlySelectedShape = this.editor.getOnlySelectedShape() // If the only selected shape is editable, start editing it if ( onlySelectedShape && diff --git a/packages/tldraw/src/lib/shapes/image/ImageShapeUtil.tsx b/packages/tldraw/src/lib/shapes/image/ImageShapeUtil.tsx index e4107e971..5cc885f0e 100644 --- a/packages/tldraw/src/lib/shapes/image/ImageShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/image/ImageShapeUtil.tsx @@ -86,7 +86,7 @@ export class ImageShapeUtil extends BaseBoxShapeUtil { const isSelected = useValue( 'onlySelectedShape', - () => shape.id === this.editor.onlySelectedShape?.id, + () => shape.id === this.editor.getOnlySelectedShape()?.id, [this.editor] ) diff --git a/packages/tldraw/src/lib/shapes/text/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/text/toolStates/Idle.ts index 23fb673e8..8376ef1cb 100644 --- a/packages/tldraw/src/lib/shapes/text/toolStates/Idle.ts +++ b/packages/tldraw/src/lib/shapes/text/toolStates/Idle.ts @@ -24,7 +24,7 @@ export class Idle extends StateNode { override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { if (info.key === 'Enter') { if (this.editor.getInstanceState().isReadonly) return null - const { onlySelectedShape } = this.editor + const onlySelectedShape = this.editor.getOnlySelectedShape() // If the only selected shape is editable, start editing it if ( onlySelectedShape && diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/Idle.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/Idle.ts index f6bf31eb2..acb60da70 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/Idle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/Idle.ts @@ -11,7 +11,7 @@ export class Idle extends StateNode { { ephemeral: true } ) - const { onlySelectedShape } = this.editor + const onlySelectedShape = this.editor.getOnlySelectedShape() // well this fucking sucks. what the fuck. // it's possible for a user to enter cropping, then undo diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/TranslatingCrop.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/TranslatingCrop.ts index 7ee6e49c1..6d4ad7d85 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/TranslatingCrop.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/TranslatingCrop.ts @@ -88,7 +88,7 @@ export class TranslatingCrop extends StateNode { } private createSnapshot() { - const shape = this.editor.onlySelectedShape as ShapeWithCrop + const shape = this.editor.getOnlySelectedShape() as ShapeWithCrop return { shape } } diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/Cropping.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Cropping.ts index 1961c8c51..c6ecc8c22 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/children/Cropping.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Cropping.ts @@ -60,7 +60,7 @@ export class Cropping extends StateNode { } private updateCursor() { - const selectedShape = this.editor.selectedShapes[0] + const selectedShape = this.editor.getSelectedShapes()[0] if (!selectedShape) return const cursorType = CursorTypeMap[this.info.handle!] @@ -229,7 +229,7 @@ export class Cropping extends StateNode { inputs: { originPagePoint }, } = this.editor - const shape = this.editor.onlySelectedShape as TLImageShape + const shape = this.editor.getOnlySelectedShape() as TLImageShape const selectionBounds = this.editor.selectionRotatedPageBounds! diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/Idle.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Idle.ts index c7ae9154b..bbdceb61f 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/children/Idle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Idle.ts @@ -67,8 +67,8 @@ export class Idle extends StateNode { } const selectedShapeIds = this.editor.getSelectedShapeIds() + const onlySelectedShape = this.editor.getOnlySelectedShape() const { - onlySelectedShape, inputs: { currentPagePoint }, } = this.editor @@ -224,7 +224,7 @@ export class Idle extends StateNode { case 'selection': { if (this.editor.getInstanceState().isReadonly) break - const { onlySelectedShape } = this.editor + const onlySelectedShape = this.editor.getOnlySelectedShape() if (onlySelectedShape) { const util = this.editor.getShapeUtil(onlySelectedShape) @@ -346,8 +346,8 @@ export class Idle extends StateNode { } const selectedShapeIds = this.editor.getSelectedShapeIds() + const onlySelectedShape = this.editor.getOnlySelectedShape() const { - onlySelectedShape, inputs: { currentPagePoint }, } = this.editor @@ -425,7 +425,7 @@ export class Idle extends StateNode { override onKeyUp = (info: TLKeyboardEventInfo) => { switch (info.code) { case 'Enter': { - const { selectedShapes } = this.editor + const selectedShapes = this.editor.getSelectedShapes() // On enter, if every selected shape is a group, then select all of the children of the groups if ( @@ -438,7 +438,7 @@ export class Idle extends StateNode { } // If the only selected shape is editable, then begin editing it - const { onlySelectedShape } = this.editor + const onlySelectedShape = this.editor.getOnlySelectedShape() if (onlySelectedShape && this.shouldStartEditingShape(onlySelectedShape)) { this.startEditingShape(onlySelectedShape, { ...info, @@ -457,7 +457,9 @@ export class Idle extends StateNode { } } - private shouldStartEditingShape(shape: TLShape | null = this.editor.onlySelectedShape): boolean { + private shouldStartEditingShape( + shape: TLShape | null = this.editor.getOnlySelectedShape() + ): boolean { if (!shape) return false if (this.editor.isShapeOrAncestorLocked(shape) && shape.type !== 'embed') return false if (!this.canInteractWithShapeInReadOnly(shape)) return false diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/PointingCropHandle.ts b/packages/tldraw/src/lib/tools/SelectTool/children/PointingCropHandle.ts index 505410a76..a21be4dda 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/children/PointingCropHandle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/PointingCropHandle.ts @@ -25,7 +25,7 @@ export class PointingCropHandle extends StateNode { override onEnter = (info: TLPointingCropHandleInfo) => { this.info = info this.parent.currentToolIdMask = info.onInteractionEnd - const selectedShape = this.editor.selectedShapes[0] + const selectedShape = this.editor.getSelectedShapes()[0] if (!selectedShape) return this.updateCursor(selectedShape) diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/PointingResizeHandle.ts b/packages/tldraw/src/lib/tools/SelectTool/children/PointingResizeHandle.ts index b0fb70fc2..de6584232 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/children/PointingResizeHandle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/PointingResizeHandle.ts @@ -32,7 +32,7 @@ export class PointingResizeHandle extends StateNode { private info = {} as PointingResizeHandleInfo private updateCursor() { - const selected = this.editor.selectedShapes + const selected = this.editor.getSelectedShapes() const cursorType = CursorTypeMap[this.info.handle!] this.editor.updateInstanceState({ cursor: { type: cursorType, rotation: selected.length === 1 ? selected[0].rotation : 0 }, diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/Resizing.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Resizing.ts index cf8fa576a..cf46a6626 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/children/Resizing.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Resizing.ts @@ -63,7 +63,9 @@ export class Resizing extends StateNode { } this.snapshot = this._createSnapshot() - this.markId = isCreating ? `creating:${this.editor.onlySelectedShape!.id}` : 'starting resizing' + this.markId = isCreating + ? `creating:${this.editor.getOnlySelectedShape()!.id}` + : 'starting resizing' if (!isCreating) this.editor.mark(this.markId) @@ -107,8 +109,9 @@ export class Resizing extends StateNode { private complete() { this.handleResizeEnd() - if (this.editAfterComplete && this.editor.onlySelectedShape) { - this.editor.setEditingShape(this.editor.onlySelectedShape.id) + const onlySelectedShape = this.editor.getOnlySelectedShape() + if (this.editAfterComplete && onlySelectedShape) { + this.editor.setEditingShape(onlySelectedShape.id) this.editor.setCurrentTool('select.editing_shape') return } diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/Translating.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Translating.ts index 9dfe300cb..6cd52bc67 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/children/Translating.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Translating.ts @@ -53,7 +53,7 @@ export class Translating extends StateNode { this.isCreating = isCreating this.editAfterComplete = editAfterComplete - this.markId = isCreating ? `creating:${this.editor.onlySelectedShape!.id}` : 'translating' + this.markId = isCreating ? `creating:${this.editor.getOnlySelectedShape()!.id}` : 'translating' this.editor.mark(this.markId) this.isCloning = false this.info = info @@ -166,7 +166,7 @@ export class Translating extends StateNode { this.editor.setCurrentTool(this.info.onInteractionEnd) } else { if (this.editAfterComplete) { - const onlySelected = this.editor.onlySelectedShape + const onlySelected = this.editor.getOnlySelectedShape() if (onlySelected) { this.editor.setEditingShape(onlySelected.id) this.editor.setCurrentTool('select.editing_shape') diff --git a/packages/tldraw/src/lib/tools/selection-logic/getShouldEnterCropModeOnPointerDown.ts b/packages/tldraw/src/lib/tools/selection-logic/getShouldEnterCropModeOnPointerDown.ts index f19be7fba..e4b82eddb 100644 --- a/packages/tldraw/src/lib/tools/selection-logic/getShouldEnterCropModeOnPointerDown.ts +++ b/packages/tldraw/src/lib/tools/selection-logic/getShouldEnterCropModeOnPointerDown.ts @@ -1,7 +1,7 @@ import { Editor } from '@tldraw/editor' export function getShouldEnterCropMode(editor: Editor): boolean { - const { onlySelectedShape } = editor + const onlySelectedShape = editor.getOnlySelectedShape() return !!( onlySelectedShape && !editor.isShapeOrAncestorLocked(onlySelectedShape) && diff --git a/packages/tldraw/src/lib/ui/components/ContextMenu.tsx b/packages/tldraw/src/lib/ui/components/ContextMenu.tsx index 75a1e21c1..3eb014058 100644 --- a/packages/tldraw/src/lib/ui/components/ContextMenu.tsx +++ b/packages/tldraw/src/lib/ui/components/ContextMenu.tsx @@ -27,7 +27,7 @@ export const ContextMenu = function ContextMenu({ children }: { children: any }) const cb = useCallback( (isOpen: boolean) => { if (!isOpen) { - const { onlySelectedShape } = editor + const onlySelectedShape = editor.getOnlySelectedShape() if (onlySelectedShape && editor.isShapeOrAncestorLocked(onlySelectedShape)) { editor.setSelectedShapes([]) @@ -35,8 +35,8 @@ export const ContextMenu = function ContextMenu({ children }: { children: any }) } else { // Weird route: selecting locked shapes on long press if (editor.getInstanceState().isCoarsePointer) { + const selectedShapes = editor.getSelectedShapes() const { - selectedShapes, inputs: { currentPagePoint }, } = editor @@ -45,7 +45,7 @@ export const ContextMenu = function ContextMenu({ children }: { children: any }) if ( // if there are no selected shapes - !editor.selectedShapes.length || + !editor.getSelectedShapes().length || // OR if none of the shapes at the point include the selected shape !shapesAtPoint.some((s) => selectedShapes.includes(s)) ) { diff --git a/packages/tldraw/src/lib/ui/components/DebugPanel.tsx b/packages/tldraw/src/lib/ui/components/DebugPanel.tsx index 801faa1a1..6124d0dbb 100644 --- a/packages/tldraw/src/lib/ui/components/DebugPanel.tsx +++ b/packages/tldraw/src/lib/ui/components/DebugPanel.tsx @@ -175,7 +175,7 @@ const DebugMenuContent = track(function DebugMenuContent({ return count } - const { selectedShapes } = editor + const selectedShapes = editor.getSelectedShapes() const shapes = selectedShapes.length === 0 ? editor.renderingShapes : selectedShapes diff --git a/packages/tldraw/src/lib/ui/components/EditLinkDialog.tsx b/packages/tldraw/src/lib/ui/components/EditLinkDialog.tsx index 6d4349c1f..4851ece61 100644 --- a/packages/tldraw/src/lib/ui/components/EditLinkDialog.tsx +++ b/packages/tldraw/src/lib/ui/components/EditLinkDialog.tsx @@ -23,7 +23,7 @@ type ShapeWithUrl = TLBaseShape export const EditLinkDialog = track(function EditLinkDialog({ onClose }: TLUiDialogProps) { const editor = useEditor() - const selectedShape = editor.onlySelectedShape + const selectedShape = editor.getOnlySelectedShape() if ( !(selectedShape && 'url' in selectedShape.props && typeof selectedShape.props.url === 'string') @@ -89,7 +89,7 @@ export const EditLinkDialogInner = track(function EditLinkDialogInner({ }, []) const handleClear = useCallback(() => { - const { onlySelectedShape } = editor + const onlySelectedShape = editor.getOnlySelectedShape() if (!onlySelectedShape) return editor.updateShapes([ { id: onlySelectedShape.id, type: onlySelectedShape.type, props: { url: '' } }, @@ -98,7 +98,7 @@ export const EditLinkDialogInner = track(function EditLinkDialogInner({ }, [editor, onClose]) const handleComplete = useCallback(() => { - const { onlySelectedShape } = editor + const onlySelectedShape = editor.getOnlySelectedShape() if (!onlySelectedShape) return diff --git a/packages/tldraw/src/lib/ui/hooks/useActions.tsx b/packages/tldraw/src/lib/ui/hooks/useActions.tsx index 3b2480d7b..d5ee305c3 100644 --- a/packages/tldraw/src/lib/ui/hooks/useActions.tsx +++ b/packages/tldraw/src/lib/ui/hooks/useActions.tsx @@ -233,7 +233,8 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { trackEvent('toggle-auto-size', { source }) editor.mark('toggling auto size') editor.updateShapes( - editor.selectedShapes + editor + .getSelectedShapes() .filter( (shape): shape is TLTextShape => editor.isShapeOfType(shape, 'text') && shape.props.autoSize === false @@ -299,7 +300,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { editor.batch(() => { trackEvent('convert-to-bookmark', { source }) - const shapes = editor.selectedShapes + const shapes = editor.getSelectedShapes() const createList: TLShapePartial[] = [] const deleteList: TLShapeId[] = [] @@ -441,7 +442,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) { if (mustGoBackToSelectToolFirst()) return trackEvent('group-shapes', { source }) - const { onlySelectedShape } = editor + const onlySelectedShape = editor.getOnlySelectedShape() if (onlySelectedShape && editor.isShapeOfType(onlySelectedShape, 'group')) { editor.mark('ungroup') editor.ungroupShapes(editor.getSelectedShapeIds()) diff --git a/packages/tldraw/src/lib/ui/hooks/useContextMenuSchema.tsx b/packages/tldraw/src/lib/ui/hooks/useContextMenuSchema.tsx index 41635522a..099b44801 100644 --- a/packages/tldraw/src/lib/ui/hooks/useContextMenuSchema.tsx +++ b/packages/tldraw/src/lib/ui/hooks/useContextMenuSchema.tsx @@ -76,7 +76,7 @@ export const TLUiContextMenuSchemaProvider = track(function TLUiContextMenuSchem const allowUngroup = useAllowUngroup() const hasClipboardWrite = Boolean(window.navigator.clipboard?.write) const showEditLink = useHasLinkShapeSelected() - const { onlySelectedShape } = editor + const onlySelectedShape = editor.getOnlySelectedShape() const isShapeLocked = onlySelectedShape && editor.isShapeOrAncestorLocked(onlySelectedShape) const contextTLUiMenuSchema = useMemo(() => { diff --git a/packages/tldraw/src/lib/ui/hooks/useHasLinkShapeSelected.ts b/packages/tldraw/src/lib/ui/hooks/useHasLinkShapeSelected.ts index b6507f4bb..7e4c764df 100644 --- a/packages/tldraw/src/lib/ui/hooks/useHasLinkShapeSelected.ts +++ b/packages/tldraw/src/lib/ui/hooks/useHasLinkShapeSelected.ts @@ -5,7 +5,7 @@ export function useHasLinkShapeSelected() { return useValue( 'hasLinkShapeSelected', () => { - const { selectedShapes } = editor + const selectedShapes = editor.getSelectedShapes() return ( selectedShapes.length === 1 && 'url' in selectedShapes[0].props && diff --git a/packages/tldraw/src/lib/ui/hooks/useMenuSchema.tsx b/packages/tldraw/src/lib/ui/hooks/useMenuSchema.tsx index c52727669..c4e45b66b 100644 --- a/packages/tldraw/src/lib/ui/hooks/useMenuSchema.tsx +++ b/packages/tldraw/src/lib/ui/hooks/useMenuSchema.tsx @@ -84,7 +84,7 @@ export function TLUiMenuSchemaProvider({ overrides, children }: TLUiMenuSchemaPr const oneEmbedSelected = useValue( 'oneEmbedSelected', () => { - const { onlySelectedShape } = editor + const onlySelectedShape = editor.getOnlySelectedShape() if (!onlySelectedShape) return false return !!( editor.isShapeOfType(onlySelectedShape, 'embed') && @@ -98,7 +98,7 @@ export function TLUiMenuSchemaProvider({ overrides, children }: TLUiMenuSchemaPr const oneEmbeddableBookmarkSelected = useValue( 'oneEmbeddableBookmarkSelected', () => { - const { onlySelectedShape } = editor + const onlySelectedShape = editor.getOnlySelectedShape() if (!onlySelectedShape) return false return !!( editor.isShapeOfType(onlySelectedShape, 'bookmark') && diff --git a/packages/tldraw/src/lib/ui/hooks/useOnlyFlippableShape.ts b/packages/tldraw/src/lib/ui/hooks/useOnlyFlippableShape.ts index 4792f813b..662aa594c 100644 --- a/packages/tldraw/src/lib/ui/hooks/useOnlyFlippableShape.ts +++ b/packages/tldraw/src/lib/ui/hooks/useOnlyFlippableShape.ts @@ -12,7 +12,7 @@ export function useOnlyFlippableShape() { return useValue( 'onlyFlippableShape', () => { - const { selectedShapes } = editor + const selectedShapes = editor.getSelectedShapes() return ( selectedShapes.length === 1 && selectedShapes.every( diff --git a/packages/tldraw/src/lib/ui/hooks/useShowAutoSizeToggle.ts b/packages/tldraw/src/lib/ui/hooks/useShowAutoSizeToggle.ts index 656615f07..6fc3ccd66 100644 --- a/packages/tldraw/src/lib/ui/hooks/useShowAutoSizeToggle.ts +++ b/packages/tldraw/src/lib/ui/hooks/useShowAutoSizeToggle.ts @@ -5,7 +5,7 @@ export function useShowAutoSizeToggle() { return useValue( 'showAutoSizeToggle', () => { - const { selectedShapes } = editor + const selectedShapes = editor.getSelectedShapes() return ( selectedShapes.length === 1 && editor.isShapeOfType(selectedShapes[0], 'text') && diff --git a/packages/tldraw/src/test/EraserTool.test.ts b/packages/tldraw/src/test/EraserTool.test.ts index 78041efa8..f08a43a1e 100644 --- a/packages/tldraw/src/test/EraserTool.test.ts +++ b/packages/tldraw/src/test/EraserTool.test.ts @@ -415,7 +415,7 @@ describe('When clicking and dragging', () => { describe('Does not erase hollow shapes on click', () => { it('Returns to select on cancel', () => { - editor.selectAll().deleteShapes(editor.selectedShapes) + editor.selectAll().deleteShapes(editor.getSelectedShapes()) expect(editor.currentPageShapes.length).toBe(0) editor.createShape({ id: createShapeId(), diff --git a/packages/tldraw/src/test/HighlightShape.test.ts b/packages/tldraw/src/test/HighlightShape.test.ts index 1d585b4bd..421c1a1f3 100644 --- a/packages/tldraw/src/test/HighlightShape.test.ts +++ b/packages/tldraw/src/test/HighlightShape.test.ts @@ -19,6 +19,6 @@ describe('Highlight shape', () => { editor.setCurrentTool('highlight').pointerDown(60, 60).pointerUp() editor.setCurrentTool('select').pointerDown(70, 70).pointerUp() - expect(editor.selectedShapes).toHaveLength(1) + expect(editor.getSelectedShapes()).toHaveLength(1) }) }) diff --git a/packages/tldraw/src/test/SelectTool.test.ts b/packages/tldraw/src/test/SelectTool.test.ts index 09a03a667..1b897bc10 100644 --- a/packages/tldraw/src/test/SelectTool.test.ts +++ b/packages/tldraw/src/test/SelectTool.test.ts @@ -94,7 +94,7 @@ describe('TLSelectTool.Translating', () => { editor.pointerDown(150, 150, { target: 'shape', shape }) editor.pointerMove(150, 250) editor.pointerUp() - const box2Id = editor.onlySelectedShape!.id + const box2Id = editor.getOnlySelectedShape()!.id expect(editor.currentPageShapes.length).toStrictEqual(2) expect(ids.box1).not.toEqual(box2Id) @@ -318,11 +318,11 @@ describe('When editing shapes', () => { // start editing the geo shape editor.doubleClick(50, 50, { target: 'shape', shape: editor.getShape(ids.geo1) }) expect(editor.editingShapeId).toBe(ids.geo1) - expect(editor.onlySelectedShape?.id).toBe(ids.geo1) + expect(editor.getOnlySelectedShape()?.id).toBe(ids.geo1) // point the text shape editor.pointerDown(50, 50, { target: 'shape', shape: editor.getShape(ids.text1) }) expect(editor.editingShapeId).toBe(null) - expect(editor.onlySelectedShape?.id).toBe(ids.text1) + expect(editor.getOnlySelectedShape()?.id).toBe(ids.text1) }) // The behavior described here will only work end to end, not with the library, @@ -334,12 +334,12 @@ describe('When editing shapes', () => { // start editing the geo shape editor.doubleClick(50, 50, { target: 'shape', shape: editor.getShape(ids.geo1) }) expect(editor.editingShapeId).toBe(ids.geo1) - expect(editor.onlySelectedShape?.id).toBe(ids.geo1) + expect(editor.getOnlySelectedShape()?.id).toBe(ids.geo1) // point the other geo shape editor.pointerDown(50, 50, { target: 'shape', shape: editor.getShape(ids.geo2) }) // that other shape should now be editing and selected! expect(editor.editingShapeId).toBe(ids.geo2) - expect(editor.onlySelectedShape?.id).toBe(ids.geo2) + expect(editor.getOnlySelectedShape()?.id).toBe(ids.geo2) }) // This works but only end to end — the logic had to move to React @@ -352,7 +352,7 @@ describe('When editing shapes', () => { editor.pointerDown(50, 50, { target: 'shape', shape: editor.getShape(ids.text2) }) // that other shape should now be editing and selected! expect(editor.editingShapeId).toBe(ids.text2) - expect(editor.onlySelectedShape?.id).toBe(ids.text2) + expect(editor.getOnlySelectedShape()?.id).toBe(ids.text2) }) it('Double clicking the canvas creates a new text shape', () => { diff --git a/packages/tldraw/src/test/arrowBindingsIndex.test.tsx b/packages/tldraw/src/test/arrowBindingsIndex.test.tsx index 304a4da72..d643c568a 100644 --- a/packages/tldraw/src/test/arrowBindingsIndex.test.tsx +++ b/packages/tldraw/src/test/arrowBindingsIndex.test.tsx @@ -18,12 +18,12 @@ describe('arrowBindingsIndex', () => { editor.selectNone() editor.setCurrentTool('arrow') editor.pointerDown(50, 50) - expect(editor.onlySelectedShape).toBe(null) + expect(editor.getOnlySelectedShape()).toBe(null) expect(editor.getArrowsBoundTo(ids.box1)).toEqual([]) editor.pointerMove(50, 55) - expect(editor.onlySelectedShape).not.toBe(null) - const arrow = editor.onlySelectedShape! + expect(editor.getOnlySelectedShape()).not.toBe(null) + const arrow = editor.getOnlySelectedShape()! expect(arrow.type).toBe('arrow') expect(editor.getArrowsBoundTo(ids.box1)).toEqual([ { arrowId: arrow.id, handleId: 'start' }, @@ -48,7 +48,7 @@ describe('arrowBindingsIndex', () => { expect(editor.getArrowsBoundTo(ids.box1)).toEqual([]) editor.pointerMove(250, 50) - const arrow1 = editor.onlySelectedShape! + const arrow1 = editor.getOnlySelectedShape()! expect(arrow1.type).toBe('arrow') expect(editor.getArrowsBoundTo(ids.box1)).toEqual([{ arrowId: arrow1.id, handleId: 'start' }]) @@ -62,7 +62,7 @@ describe('arrowBindingsIndex', () => { // start at box 1 and end on the page editor.setCurrentTool('arrow') editor.pointerDown(50, 50).pointerMove(50, -50).pointerUp(50, -50) - const arrow2 = editor.onlySelectedShape! + const arrow2 = editor.getOnlySelectedShape()! expect(arrow2.type).toBe('arrow') expect(editor.getArrowsBoundTo(ids.box1)).toEqual([ @@ -73,7 +73,7 @@ describe('arrowBindingsIndex', () => { // start outside box 1 and end in box 1 editor.setCurrentTool('arrow') editor.pointerDown(0, -50).pointerMove(50, 50).pointerUp(50, 50) - const arrow3 = editor.onlySelectedShape! + const arrow3 = editor.getOnlySelectedShape()! expect(arrow3.type).toBe('arrow') expect(editor.getArrowsBoundTo(ids.box1)).toEqual([ @@ -91,7 +91,7 @@ describe('arrowBindingsIndex', () => { editor.expectToBeIn('arrow.pointing') editor.pointerMove(250, -50) editor.expectToBeIn('select.dragging_handle') - const arrow4 = editor.onlySelectedShape! + const arrow4 = editor.getOnlySelectedShape()! expect(editor.getArrowsBoundTo(ids.box2)).toEqual([ { arrowId: arrow1.id, handleId: 'end' }, @@ -110,7 +110,7 @@ describe('arrowBindingsIndex', () => { // start outside box 2 and enter in box 2 editor.setCurrentTool('arrow') editor.pointerDown(250, -50).pointerMove(250, 50).pointerUp(250, 50) - const arrow5 = editor.onlySelectedShape! + const arrow5 = editor.getOnlySelectedShape()! expect(arrow5.type).toBe('arrow') expect(editor.getArrowsBoundTo(ids.box1)).toEqual([ @@ -150,23 +150,23 @@ describe('arrowBindingsIndex', () => { // span both boxes editor.setCurrentTool('arrow') editor.pointerDown(50, 50).pointerMove(250, 50).pointerUp(250, 50) - arrowAId = editor.onlySelectedShape!.id + arrowAId = editor.getOnlySelectedShape()!.id // start at box 1 and leave editor.setCurrentTool('arrow') editor.pointerDown(50, 50).pointerMove(50, -50).pointerUp(50, -50) - arrowBId = editor.onlySelectedShape!.id + arrowBId = editor.getOnlySelectedShape()!.id // start outside box 1 and enter editor.setCurrentTool('arrow') editor.pointerDown(50, -50).pointerMove(50, 50).pointerUp(50, 50) - arrowCId = editor.onlySelectedShape!.id + arrowCId = editor.getOnlySelectedShape()!.id // start at box 2 and leave editor.setCurrentTool('arrow') editor.pointerDown(250, 50).pointerMove(250, -50).pointerUp(250, -50) - arrowDId = editor.onlySelectedShape!.id + arrowDId = editor.getOnlySelectedShape()!.id // start outside box 2 and enter editor.setCurrentTool('arrow') editor.pointerDown(250, -50).pointerMove(250, 50).pointerUp(250, 50) - arrowEId = editor.onlySelectedShape!.id + arrowEId = editor.getOnlySelectedShape()!.id }) it('deletes the entry if you delete the bound shapes', () => { expect(editor.getArrowsBoundTo(ids.box2)).toHaveLength(3) @@ -236,7 +236,8 @@ describe('arrowBindingsIndex', () => { editor.selectAll() editor.duplicateShapes(editor.getSelectedShapeIds()) - const [box1Clone, box2Clone] = editor.selectedShapes + const [box1Clone, box2Clone] = editor + .getSelectedShapes() .filter((shape) => editor.isShapeOfType(shape, 'geo')) .sort((a, b) => a.x - b.x) diff --git a/packages/tldraw/src/test/arrows-megabus.test.ts b/packages/tldraw/src/test/arrows-megabus.test.ts index 49a507f17..4a7da9dc7 100644 --- a/packages/tldraw/src/test/arrows-megabus.test.ts +++ b/packages/tldraw/src/test/arrows-megabus.test.ts @@ -18,7 +18,7 @@ const ids = { arrow3: createShapeId('arrow3'), } -const arrow = () => editor.onlySelectedShape as TLArrowShape +const arrow = () => editor.getOnlySelectedShape() as TLArrowShape beforeEach(() => { editor = new TestEditor() diff --git a/packages/tldraw/src/test/commands/getInitialMetaForShape.test.ts b/packages/tldraw/src/test/commands/getInitialMetaForShape.test.ts index 7d989efa8..4622ec964 100644 --- a/packages/tldraw/src/test/commands/getInitialMetaForShape.test.ts +++ b/packages/tldraw/src/test/commands/getInitialMetaForShape.test.ts @@ -10,12 +10,12 @@ beforeEach(() => { it('Sets shape meta by default to an empty object', () => { const id = createShapeId() editor.createShapes([{ id, type: 'geo' }]).select(id) - expect(editor.onlySelectedShape!.meta).toStrictEqual({}) + expect(editor.getOnlySelectedShape()!.meta).toStrictEqual({}) }) it('Sets shape meta', () => { editor.getInitialMetaForShape = (shape) => ({ firstThreeCharactersOfId: shape.id.slice(0, 3) }) const id = createShapeId() editor.createShapes([{ id, type: 'geo' }]).select(id) - expect(editor.onlySelectedShape!.meta).toStrictEqual({ firstThreeCharactersOfId: 'sha' }) + expect(editor.getOnlySelectedShape()!.meta).toStrictEqual({ firstThreeCharactersOfId: 'sha' }) }) diff --git a/packages/tldraw/src/test/commands/moveShapesToPage.test.ts b/packages/tldraw/src/test/commands/moveShapesToPage.test.ts index 0e4926230..03c82ae2c 100644 --- a/packages/tldraw/src/test/commands/moveShapesToPage.test.ts +++ b/packages/tldraw/src/test/commands/moveShapesToPage.test.ts @@ -189,9 +189,9 @@ describe('arrows', () => { editor.pointerMove(255, 255) editor.pointerMove(450, 450) editor.pointerUp(450, 450) - const arrow = editor.onlySelectedShape! + const arrow = editor.getOnlySelectedShape()! - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toCloselyMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toCloselyMatchObject({ // exiting at the bottom right corner of the first box x: 300, y: 300, @@ -224,7 +224,7 @@ describe('arrows', () => { .pointerMove(255, 255) .pointerMove(450, 450) .pointerUp(450, 450) - const arrow = editor.onlySelectedShape! + const arrow = editor.getOnlySelectedShape()! expect(editor.getArrowsBoundTo(firstBox.id).length).toBe(1) expect(editor.getArrowsBoundTo(secondBox.id).length).toBe(1) diff --git a/packages/tldraw/src/test/commands/stretch.test.tsx b/packages/tldraw/src/test/commands/stretch.test.tsx index ebb3b6954..309dfc174 100644 --- a/packages/tldraw/src/test/commands/stretch.test.tsx +++ b/packages/tldraw/src/test/commands/stretch.test.tsx @@ -50,7 +50,7 @@ describe('when multiple shapes are selected', () => { it('stretches horizontally and preserves aspect ratio', () => { const videoA = createVideoShape() editor.selectAll() - expect(editor.selectedShapes.length).toBe(4) + expect(editor.getSelectedShapes().length).toBe(4) editor.stretchShapes(editor.getSelectedShapeIds(), 'horizontal') jest.advanceTimersByTime(1000) const newHeight = (500 * 9) / 16 @@ -76,7 +76,7 @@ describe('when multiple shapes are selected', () => { it('stretches vertically and preserves aspect ratio', () => { const videoA = createVideoShape() editor.selectAll() - expect(editor.selectedShapes.length).toBe(4) + expect(editor.getSelectedShapes().length).toBe(4) editor.stretchShapes(editor.getSelectedShapeIds(), 'vertical') jest.advanceTimersByTime(1000) const newWidth = (500 * 16) / 9 diff --git a/packages/tldraw/src/test/flipShapes.test.ts b/packages/tldraw/src/test/flipShapes.test.ts index b25b1153e..5e500b512 100644 --- a/packages/tldraw/src/test/flipShapes.test.ts +++ b/packages/tldraw/src/test/flipShapes.test.ts @@ -297,7 +297,7 @@ describe('When one shape is selected', () => { editor.selectAll() editor.groupShapes(editor.getSelectedShapeIds()) // this will also select the new group - const groupBefore = editor.selectedShapes[0] + const groupBefore = editor.getSelectedShapes()[0] editor.on('change', fn) editor.flipShapes(editor.getSelectedShapeIds(), 'horizontal') diff --git a/packages/tldraw/src/test/frames.test.ts b/packages/tldraw/src/test/frames.test.ts index cb1586078..9445b8391 100644 --- a/packages/tldraw/src/test/frames.test.ts +++ b/packages/tldraw/src/test/frames.test.ts @@ -20,8 +20,8 @@ describe('creating frames', () => { it('can be done', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - expect(editor.onlySelectedShape?.type).toBe('frame') - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getOnlySelectedShape()?.type).toBe('frame') + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 100, y: 100, w: 100, @@ -31,9 +31,9 @@ describe('creating frames', () => { it('will create with a default size if no dragging happens', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerUp(100, 100) - expect(editor.onlySelectedShape?.type).toBe('frame') + expect(editor.getOnlySelectedShape()?.type).toBe('frame') const { w, h } = editor.getShapeUtil('frame').getDefaultProps() - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 100 - w / 2, y: 100 - h / 2, w, @@ -43,7 +43,7 @@ describe('creating frames', () => { it('can be canceled while pointing', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).cancel().pointerUp(100, 100) - expect(editor.onlySelectedShape?.type).toBe(undefined) + expect(editor.getOnlySelectedShape()?.type).toBe(undefined) expect(editor.currentPageShapes).toHaveLength(0) }) it('can be canceled while dragging', () => { @@ -52,35 +52,35 @@ describe('creating frames', () => { editor.expectPathToBe('root.select.resizing') editor.cancel() editor.pointerUp() - expect(editor.onlySelectedShape?.type).toBe(undefined) + expect(editor.getOnlySelectedShape()?.type).toBe(undefined) expect(editor.currentPageShapes).toHaveLength(0) }) it('can be undone', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - expect(editor.onlySelectedShape?.type).toBe('frame') + expect(editor.getOnlySelectedShape()?.type).toBe('frame') expect(editor.currentPageShapes).toHaveLength(1) editor.undo() - expect(editor.onlySelectedShape?.type).toBe(undefined) + expect(editor.getOnlySelectedShape()?.type).toBe(undefined) expect(editor.currentPageShapes).toHaveLength(0) }) it('can be done inside other frames', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameAId = editor.onlySelectedShape!.id + const frameAId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('frame') editor.pointerDown(125, 125).pointerMove(175, 175).pointerUp(175, 175) expect(editor.currentPageShapes).toHaveLength(2) - expect(editor.onlySelectedShape?.parentId).toEqual(frameAId) + expect(editor.getOnlySelectedShape()?.parentId).toEqual(frameAId) - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 125, y: 125, w: 50, @@ -91,7 +91,7 @@ describe('creating frames', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameAId = editor.onlySelectedShape!.id + const frameAId = editor.getOnlySelectedShape()!.id editor.rotateSelection(Math.PI / 2) @@ -100,9 +100,9 @@ describe('creating frames', () => { expect(editor.currentPageShapes).toHaveLength(2) - expect(editor.onlySelectedShape?.parentId).toEqual(frameAId) + expect(editor.getOnlySelectedShape()?.parentId).toEqual(frameAId) - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toCloselyMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toCloselyMatchObject({ x: 125, y: 125, w: 50, @@ -118,7 +118,7 @@ describe('creating frames', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(49, 149) - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 49, y: 100, w: 51, @@ -128,7 +128,7 @@ describe('creating frames', () => { // x should snap editor.keyDown('Control') expect(editor.snaps.lines).toHaveLength(1) - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 50, y: 100, w: 50, @@ -184,7 +184,7 @@ describe('frame shapes', () => { editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) editor.resizeSelection({ scaleX: 0.5, scaleY: 0.5 }, 'bottom_right') - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toCloselyMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toCloselyMatchObject({ x: 100, y: 100, w: 50, @@ -198,7 +198,7 @@ describe('frame shapes', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) editor.resizeSelection({ scaleX: 0.5, scaleY: 0.5 }, 'bottom_right', { altKey: true }) - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toCloselyMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toCloselyMatchObject({ x: 125, y: 125, w: 50, @@ -210,12 +210,12 @@ describe('frame shapes', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor.pointerDown(125, 125).pointerMove(175, 175).pointerUp(175, 175) - const boxId = editor.onlySelectedShape!.id + const boxId = editor.getOnlySelectedShape()!.id editor.select(frameId) @@ -239,42 +239,42 @@ describe('frame shapes', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id editor.createShapes([ { type: 'geo', id: ids.boxA, x: 250, y: 250, props: { w: 50, h: 50, fill: 'solid' } }, ]) - expect(editor.onlySelectedShape!.parentId).toBe(editor.currentPageId) + expect(editor.getOnlySelectedShape()!.parentId).toBe(editor.currentPageId) editor.setCurrentTool('select') editor.pointerDown(275, 275).pointerMove(150, 150) jest.advanceTimersByTime(300) - expect(editor.onlySelectedShape!.id).toBe(ids.boxA) - expect(editor.onlySelectedShape!.parentId).toBe(frameId) + expect(editor.getOnlySelectedShape()!.id).toBe(ids.boxA) + expect(editor.getOnlySelectedShape()!.parentId).toBe(frameId) editor.pointerMove(275, 275) jest.advanceTimersByTime(250) - expect(editor.onlySelectedShape!.parentId).toBe(editor.currentPageId) + expect(editor.getOnlySelectedShape()!.parentId).toBe(editor.currentPageId) editor.pointerMove(150, 150) jest.advanceTimersByTime(250) - expect(editor.onlySelectedShape!.parentId).toBe(frameId) + expect(editor.getOnlySelectedShape()!.parentId).toBe(frameId) editor.pointerUp(150, 150) - expect(editor.onlySelectedShape!.parentId).toBe(frameId) + expect(editor.getOnlySelectedShape()!.parentId).toBe(frameId) }) it('can have shapes dragged on top and dropped before the timeout fires', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id // Create a new shape off of the frame editor.createShapes([ @@ -282,37 +282,37 @@ describe('frame shapes', () => { ]) // It should be a child of the page - expect(editor.onlySelectedShape!.parentId).toBe(editor.currentPageId) + expect(editor.getOnlySelectedShape()!.parentId).toBe(editor.currentPageId) // Drag the shape on top of the frame editor.setCurrentTool('select') editor.pointerDown(275, 275, ids.boxA).pointerMove(150, 150) // The timeout has not fired yet, so the shape is still a child of the current page - expect(editor.onlySelectedShape!.parentId).toBe(editor.currentPageId) + expect(editor.getOnlySelectedShape()!.parentId).toBe(editor.currentPageId) // On pointer up, the shape should be dropped into the frame editor.pointerUp() - expect(editor.onlySelectedShape!.parentId).toBe(frameId) + expect(editor.getOnlySelectedShape()!.parentId).toBe(frameId) }) it('does not reparent shapes that are being dragged from within the frame', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id // create a box within the frame editor.setCurrentTool('geo') editor.pointerDown(125, 125).pointerMove(175, 175).pointerUp(175, 175) - expect(editor.onlySelectedShape!.parentId).toBe(frameId) - const boxAid = editor.onlySelectedShape!.id + expect(editor.getOnlySelectedShape()!.parentId).toBe(frameId) + const boxAid = editor.getOnlySelectedShape()!.id // create another box within the frame editor.setCurrentTool('geo') editor.pointerDown(130, 130).pointerMove(180, 180).pointerUp(180, 180) - expect(editor.onlySelectedShape!.parentId).toBe(frameId) - const boxBid = editor.onlySelectedShape!.id + expect(editor.getOnlySelectedShape()!.parentId).toBe(frameId) + const boxBid = editor.getOnlySelectedShape()!.id // dragging box A around should not cause the index to change or the frame to be highlighted @@ -325,8 +325,8 @@ describe('frame shapes', () => { jest.advanceTimersByTime(2500) - expect(editor.onlySelectedShape!.id).toBe(boxAid) - expect(editor.onlySelectedShape!.parentId).toBe(frameId) + expect(editor.getOnlySelectedShape()!.id).toBe(boxAid) + expect(editor.getOnlySelectedShape()!.parentId).toBe(frameId) expect(editor.hintingShapeIds).toHaveLength(0) // box A should still be beneath box B expect(editor.getShape(boxAid)!.index.localeCompare(editor.getShape(boxBid)!.index)).toBe(-1) @@ -355,12 +355,12 @@ describe('frame shapes', () => { editor.setCurrentTool('geo') editor.pointerDown(125, 125).pointerMove(175, 175).pointerUp(175, 175) - const innerBoxId = editor.onlySelectedShape!.id + const innerBoxId = editor.getOnlySelectedShape()!.id // make a shape outside the frame editor.setCurrentTool('geo') editor.pointerDown(275, 125).pointerMove(280, 130).pointerUp(280, 130) - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 275, y: 125, w: 5, @@ -370,13 +370,13 @@ describe('frame shapes', () => { // drag it a pixel up, it should not snap even though it's at the same y as the box inside the frame editor.setCurrentTool('select') editor - .pointerDown(277.5, 127.5, editor.onlySelectedShape!.id) + .pointerDown(277.5, 127.5, editor.getOnlySelectedShape()!.id) .pointerMove(287.5, 126.5) .pointerMove(277.5, 126.5) // now try to snap editor.keyDown('Control') - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 275, y: 124, w: 5, @@ -388,7 +388,7 @@ describe('frame shapes', () => { editor.pointerMove(287.5, 126.5).pointerMove(277.5, 126.5) expect(editor.snaps.lines).toHaveLength(1) - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 275, y: 125, w: 5, @@ -399,17 +399,17 @@ describe('frame shapes', () => { it('children of a frame will not snap to shapes outside the frame', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id // make a shape inside the frame editor.setCurrentTool('geo') editor.pointerDown(125, 125).pointerMove(175, 175).pointerUp(175, 175) - const innerBoxId = editor.onlySelectedShape!.id + const innerBoxId = editor.getOnlySelectedShape()!.id // make a shape outside the frame editor.setCurrentTool('geo') editor.pointerDown(275, 125).pointerMove(280, 130).pointerUp(280, 130) - const outerBoxId = editor.onlySelectedShape!.id + const outerBoxId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('select') editor.pointerDown(150, 150, innerBoxId).pointerMove(150, 50).pointerMove(150, 148) @@ -429,7 +429,7 @@ describe('frame shapes', () => { editor.setCurrentTool('geo') editor.pointerDown(150, 150).pointerMove(250, 250).pointerUp(250, 250) - expect(editor.getShapePageBounds(editor.onlySelectedShape!)).toMatchObject({ + expect(editor.getShapePageBounds(editor.getOnlySelectedShape()!)).toMatchObject({ x: 150, y: 150, w: 100, @@ -437,13 +437,13 @@ describe('frame shapes', () => { }) // mask should be a 50px box around the top left corner - expect(editor.getShapeClipPath(editor.onlySelectedShape!.id)).toMatchInlineSnapshot( + expect(editor.getShapeClipPath(editor.getOnlySelectedShape()!.id)).toMatchInlineSnapshot( `"polygon(-50px -50px,50px -50px,50px 50px,-50px 50px)"` ) - editor.reparentShapes([editor.onlySelectedShape!.id], editor.currentPageId) + editor.reparentShapes([editor.getOnlySelectedShape()!.id], editor.currentPageId) - expect(editor.getShapeClipPath(editor.onlySelectedShape!.id)).toBeUndefined() + expect(editor.getShapeClipPath(editor.getOnlySelectedShape()!.id)).toBeUndefined() }) it('masks its nested children', () => { @@ -453,12 +453,12 @@ describe('frame shapes', () => { editor.setCurrentTool('frame') editor.pointerDown(150, 150).pointerMove(250, 250).pointerUp(250, 250) - const innerFrameId = editor.onlySelectedShape!.id + const innerFrameId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor.pointerDown(100, 100).pointerMove(250, 250).pointerUp(250, 250) - const boxId = editor.onlySelectedShape!.id + const boxId = editor.getOnlySelectedShape()!.id editor.reparentShapes([boxId], innerFrameId) @@ -471,12 +471,12 @@ describe('frame shapes', () => { it('arrows started within the frame will bind to it and have the page as their parent', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('arrow') editor.pointerDown(150, 150).pointerMove(250, 250).pointerUp(250, 250) - const arrow = editor.onlySelectedShape! as TLArrowShape + const arrow = editor.getOnlySelectedShape()! as TLArrowShape expect(arrow.props.start).toMatchObject({ boundShapeId: frameId }) expect(arrow.props.end).toMatchObject({ type: 'point' }) @@ -487,7 +487,7 @@ describe('frame shapes', () => { it('arrows started within the frame can bind to a shape within the frame ', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor @@ -496,12 +496,12 @@ describe('frame shapes', () => { .pointerUp(175, 175) .setStyleForSelectedShapes(DefaultFillStyle, 'solid') .setStyleForNextShapes(DefaultFillStyle, 'solid') - const boxId = editor.onlySelectedShape!.id + const boxId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('arrow') editor.pointerDown(150, 150).pointerMove(190, 190).pointerUp(190, 190) - const arrow = editor.onlySelectedShape! as TLArrowShape + const arrow = editor.getOnlySelectedShape()! as TLArrowShape expect(arrow.props.start).toMatchObject({ boundShapeId: boxId }) expect(arrow.props.end).toMatchObject({ boundShapeId: frameId }) @@ -513,7 +513,7 @@ describe('frame shapes', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id expect(editor.getSelectedShapeIds()[0]).toBe(frameId) expect(editor.getCurrentPageState().editingShapeId).toBe(null) @@ -529,7 +529,7 @@ describe('frame shapes', () => { it('can be selected with box brushing only if the whole frame is selected', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id // select from outside the frame editor.setCurrentTool('select') @@ -541,7 +541,7 @@ describe('frame shapes', () => { editor.pointerMove(250, 250) expect(editor.getSelectedShapeIds()).toHaveLength(1) - expect(editor.onlySelectedShape!.id).toBe(frameId) + expect(editor.getOnlySelectedShape()!.id).toBe(frameId) }) it('can be selected with scribble brushing only if the drag starts outside the frame', () => { @@ -561,12 +561,12 @@ describe('frame shapes', () => { it('children of a frame will not be selected from outside of the frame', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - editor.onlySelectedShape!.id + editor.getOnlySelectedShape()!.id // make a shape inside the frame that extends out of the frame editor.setCurrentTool('geo') editor.pointerDown(150, 150).pointerMove(400, 400).pointerUp(400, 400) - const innerBoxId = editor.onlySelectedShape!.id + const innerBoxId = editor.getOnlySelectedShape()!.id // select from outside the frame via box brushing editor.setCurrentTool('select') @@ -581,7 +581,7 @@ describe('frame shapes', () => { // Check if the inner box was selected expect(editor.getSelectedShapeIds()).toHaveLength(1) - expect(editor.onlySelectedShape!.id).toBe(innerBoxId) + expect(editor.getOnlySelectedShape()!.id).toBe(innerBoxId) // Deselect everything editor.deselect() @@ -600,7 +600,7 @@ describe('frame shapes', () => { it('arrows will not bind to parts of shapes outside the frame', () => { editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - editor.onlySelectedShape!.id + editor.getOnlySelectedShape()!.id // make a shape inside the frame that extends out of the frame editor.setCurrentTool('geo') @@ -610,14 +610,14 @@ describe('frame shapes', () => { .pointerUp(400, 400) .setStyleForSelectedShapes(DefaultFillStyle, 'solid') .setStyleForNextShapes(DefaultFillStyle, 'solid') - const innerBoxId = editor.onlySelectedShape!.id + const innerBoxId = editor.getOnlySelectedShape()!.id // Make an arrow that binds to the inner box's bottom right corner editor.setCurrentTool('arrow') editor.pointerDown(500, 500).pointerMove(375, 375) // Check if the arrow's handles remain points - let arrow = editor.onlySelectedShape! as TLArrowShape + let arrow = editor.getOnlySelectedShape()! as TLArrowShape expect(arrow.props.start).toMatchObject({ type: 'point', x: 0, @@ -633,7 +633,7 @@ describe('frame shapes', () => { editor.pointerMove(175, 175).pointerUp(175, 175) // Check if arrow's end handle is bound to the inner box - arrow = editor.onlySelectedShape! as TLArrowShape + arrow = editor.getOnlySelectedShape()! as TLArrowShape expect(arrow.props.end).toMatchObject({ boundShapeId: innerBoxId }) }) }) @@ -655,7 +655,7 @@ test('arrows bound to a shape within a group within a frame are reparented if th // └──────────────────────────┘ editor.setCurrentTool('frame') editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200) - const frameId = editor.onlySelectedShape!.id + const frameId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor @@ -664,7 +664,7 @@ test('arrows bound to a shape within a group within a frame are reparented if th .pointerUp(120, 120) .setStyleForSelectedShapes(DefaultFillStyle, 'solid') .setStyleForNextShapes(DefaultFillStyle, 'solid') - const boxAId = editor.onlySelectedShape!.id + const boxAId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor @@ -673,7 +673,7 @@ test('arrows bound to a shape within a group within a frame are reparented if th .pointerUp(190, 120) .setStyleForSelectedShapes(DefaultFillStyle, 'solid') .setStyleForNextShapes(DefaultFillStyle, 'solid') - const boxBId = editor.onlySelectedShape!.id + const boxBId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('geo') editor @@ -682,16 +682,16 @@ test('arrows bound to a shape within a group within a frame are reparented if th .pointerUp(170, 170) .setStyleForSelectedShapes(DefaultFillStyle, 'solid') .setStyleForNextShapes(DefaultFillStyle, 'solid') - const boxCId = editor.onlySelectedShape!.id + const boxCId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('select') editor.select(boxBId, boxCId) editor.groupShapes(editor.getSelectedShapeIds()) - const groupId = editor.onlySelectedShape!.id + const groupId = editor.getOnlySelectedShape()!.id editor.setCurrentTool('arrow') editor.pointerDown(115, 115).pointerMove(185, 115).pointerUp(185, 115) - const arrowId = editor.onlySelectedShape!.id + const arrowId = editor.getOnlySelectedShape()!.id expect(editor.getArrowsBoundTo(boxAId)).toHaveLength(1) expect(editor.getArrowsBoundTo(boxBId)).toHaveLength(1) @@ -740,7 +740,7 @@ describe('When dragging a shape inside a group inside a frame', () => { editor.pointerMove(100, 100).click().click() - expect(editor.onlySelectedShape?.id).toBe(ids.box1) + expect(editor.getOnlySelectedShape()?.id).toBe(ids.box1) editor.pointerMove(150, 150).pointerDown().pointerMove(140, 140) @@ -760,7 +760,7 @@ describe('When dragging a shape inside a group inside a frame', () => { editor.pointerMove(100, 100).click().click() - expect(editor.onlySelectedShape?.id).toBe(ids.box1) + expect(editor.getOnlySelectedShape()?.id).toBe(ids.box1) expect(editor.focusedGroupId).toBe(ids.group1) editor diff --git a/packages/tldraw/src/test/paste.test.ts b/packages/tldraw/src/test/paste.test.ts index 17d7c1883..47dacee33 100644 --- a/packages/tldraw/src/test/paste.test.ts +++ b/packages/tldraw/src/test/paste.test.ts @@ -475,9 +475,9 @@ describe('When pasting into frames...', () => { editor.setCamera({ x: -editor.viewportScreenBounds.w, y: -editor.viewportScreenBounds.h, z: 1 }) // paste the box editor.paste() - const boxId = editor.onlySelectedShape!.id + const boxId = editor.getOnlySelectedShape()!.id // it should be a child of the frame - expect(editor.onlySelectedShape?.parentId).toBe(ids.frame1) + expect(editor.getOnlySelectedShape()?.parentId).toBe(ids.frame1) // it should have pageBounds of 10x10 because it is not rotated relative to the viewport expect(editor.getShapePageBounds(boxId)).toMatchObject({ w: 10, h: 10 }) // it should be in the middle of the frame diff --git a/packages/tldraw/src/test/select.test.tsx b/packages/tldraw/src/test/select.test.tsx index e5652b62d..6c194ccb9 100644 --- a/packages/tldraw/src/test/select.test.tsx +++ b/packages/tldraw/src/test/select.test.tsx @@ -15,7 +15,7 @@ describe(SelectTool, () => { describe('pointer down while shape is being edited', () => { it('captures the pointer down event if it is on the shape', () => { editor.setCurrentTool('geo').pointerDown(0, 0).pointerMove(100, 100).pointerUp(100, 100) - const shapeId = editor.onlySelectedShape?.id + const shapeId = editor.getOnlySelectedShape()?.id editor._transformPointerDownSpy.mockRestore() editor._transformPointerUpSpy.mockRestore() editor.setCurrentTool('select') @@ -42,7 +42,7 @@ describe(SelectTool, () => { }) it('does not allow pressing undo to end up in the editing state', () => { editor.setCurrentTool('geo').pointerDown(0, 0).pointerMove(100, 100).pointerUp(100, 100) - const shapeId = editor.onlySelectedShape?.id + const shapeId = editor.getOnlySelectedShape()?.id editor._transformPointerDownSpy.mockRestore() editor._transformPointerUpSpy.mockRestore() editor.setCurrentTool('select') diff --git a/packages/tldraw/src/test/selection-omnibus.test.ts b/packages/tldraw/src/test/selection-omnibus.test.ts index 78a6ff2a4..4ef1901d6 100644 --- a/packages/tldraw/src/test/selection-omnibus.test.ts +++ b/packages/tldraw/src/test/selection-omnibus.test.ts @@ -75,9 +75,9 @@ describe('Hovering shapes', () => { editor.pointerMove(50, 50) editor.pointerDown() expect(editor.isIn('select.pointing_shape')).toBe(true) - expect(editor.selectedShapes.length).toBe(1) + expect(editor.getSelectedShapes().length).toBe(1) editor.pointerUp() - expect(editor.selectedShapes.length).toBe(1) + expect(editor.getSelectedShapes().length).toBe(1) expect(editor.isIn('select.idle')).toBe(true) }) @@ -85,10 +85,10 @@ describe('Hovering shapes', () => { editor.pointerMove(50, 50) editor.pointerDown() expect(editor.isIn('select.pointing_canvas')).toBe(true) - expect(editor.selectedShapes.length).toBe(0) + expect(editor.getSelectedShapes().length).toBe(0) editor.pointerUp() expect(editor.isIn('select.idle')).toBe(true) - expect(editor.selectedShapes.length).toBe(1) + expect(editor.getSelectedShapes().length).toBe(1) }) it('hovers the margins or inside of filled shapes', () => { diff --git a/packages/tldraw/src/test/translating.test.ts b/packages/tldraw/src/test/translating.test.ts index ff032bd37..cf7711e07 100644 --- a/packages/tldraw/src/test/translating.test.ts +++ b/packages/tldraw/src/test/translating.test.ts @@ -182,7 +182,7 @@ describe('When cloning...', () => { // Start cloning! editor.keyDown('Alt') expect(editor.currentPageShapeIds.size).toBe(4) - const newShape = editor.selectedShapes[0] + const newShape = editor.getSelectedShapes()[0] expect(newShape.id).not.toBe(ids.box1) editor @@ -1792,19 +1792,19 @@ it('clones a single shape simply', () => { .pointerMove(50, 50) .click() - expect(editor.onlySelectedShape).toBe(editor.currentPageShapes[0]) + expect(editor.getOnlySelectedShape()).toBe(editor.currentPageShapes[0]) expect(editor.hoveredShape).toBe(editor.currentPageShapes[0]) // click on the canvas to deselect editor.pointerMove(200, 50).click() - expect(editor.onlySelectedShape).toBe(null) + expect(editor.getOnlySelectedShape()).toBe(null) expect(editor.hoveredShape).toBe(undefined) // move back over the the shape editor.pointerMove(50, 50) - expect(editor.onlySelectedShape).toBe(null) + expect(editor.getOnlySelectedShape()).toBe(null) expect(editor.hoveredShape).toBe(editor.currentPageShapes[0]) // start dragging the shape @@ -1818,7 +1818,7 @@ it('clones a single shape simply', () => { expect(editor.currentPageShapes).toHaveLength(2) const [, sticky2] = editor.currentPageShapes - expect(editor.onlySelectedShape).toBe(sticky2) + expect(editor.getOnlySelectedShape()).toBe(sticky2) expect(editor.editingShape).toBe(undefined) expect(editor.hoveredShape).toBe(sticky2) })