From b7d9c8684cb6cf7bd710af5420135ea3516cc3bf Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Mon, 17 Jul 2023 22:22:34 +0100 Subject: [PATCH] tldraw zero - package shuffle (#1710) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR moves code between our packages so that: - @tldraw/editor is a “core” library with the engine and canvas but no shapes, tools, or other things - @tldraw/tldraw contains everything particular to the experience we’ve built for tldraw At first look, this might seem like a step away from customization and configuration, however I believe it greatly increases the configuration potential of the @tldraw/editor while also providing a more accurate reflection of what configuration options actually exist for @tldraw/tldraw. ## Library changes @tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports @tldraw/editor. - users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always only import things from @tldraw/editor. - users of @tldraw/tldraw should almost always only import things from @tldraw/tldraw. - @tldraw/polyfills is merged into @tldraw/editor - @tldraw/indices is merged into @tldraw/editor - @tldraw/primitives is merged mostly into @tldraw/editor, partially into @tldraw/tldraw - @tldraw/file-format is merged into @tldraw/tldraw - @tldraw/ui is merged into @tldraw/tldraw Many (many) utils and other code is moved from the editor to tldraw. For example, embeds now are entirely an feature of @tldraw/tldraw. The only big chunk of code left in core is related to arrow handling. ## API Changes The editor can now be used without tldraw's assets. We load them in @tldraw/tldraw instead, so feel free to use whatever fonts or images or whatever that you like with the editor. All tools and shapes (except for the `Group` shape) are moved to @tldraw/tldraw. This includes the `select` tool. You should use the editor with at least one tool, however, so you now also need to send in an `initialState` prop to the Editor / component indicating which state the editor should begin in. The `components` prop now also accepts `SelectionForeground`. The complex selection component that we use for tldraw is moved to @tldraw/tldraw. The default component is quite basic but can easily be replaced via the `components` prop. We pass down our tldraw-flavored SelectionFg via `components`. Likewise with the `Scribble` component: the `DefaultScribble` no longer uses our freehand tech and is a simple path instead. We pass down the tldraw-flavored scribble via `components`. The `ExternalContentManager` (`Editor.externalContentManager`) is removed and replaced with a mapping of types to handlers. - Register new content handlers with `Editor.registerExternalContentHandler`. - Register new asset creation handlers (for files and URLs) with `Editor.registerExternalAssetHandler` ### Change Type - [x] `major` — Breaking change ### Test Plan - [x] Unit Tests - [x] End to end tests ### Release Notes - [@tldraw/editor] lots, wip - [@tldraw/ui] gone, merged to tldraw/tldraw - [@tldraw/polyfills] gone, merged to tldraw/editor - [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw - [@tldraw/indices] gone, merged to tldraw/editor - [@tldraw/file-format] gone, merged to tldraw/tldraw --------- Co-authored-by: alex --- .eslintrc.js | 6 + README.md | 5 - apps/examples/e2e/shared-e2e.ts | 2 +- .../e2e/tests/export-snapshots.spec.ts | 29 +- apps/examples/e2e/tests/test-kbds.spec.ts | 29 + apps/examples/package.json | 3 - .../CustomComponentsExample.tsx | 38 +- .../11-user-presence/UserPresenceExample.tsx | 6 + .../src/14-persistence/PersistenceExample.tsx | 5 +- .../src/16-custom-styles/CardShape.tsx | 23 +- .../16-custom-styles/CustomStylesExample.tsx | 9 +- .../src/16-custom-styles/FilterStyleUi.tsx | 3 +- .../src/16-custom-styles/ui-overrides.ts | 2 +- .../3-custom-config/CardShape/CardShape.ts | 15 - .../CardShape/CardShapeUtil.tsx | 6 + .../CardShape/card-shape-props.ts | 3 +- .../3-custom-config/CustomConfigExample.tsx | 13 +- .../src/3-custom-config/custom-shapes.ts | 3 - .../src/3-custom-config/ui-overrides.ts | 2 +- .../src/4-custom-ui/CustomUiExample.tsx | 29 +- .../src/5-exploded/ExplodedExample.tsx | 5 +- .../8-error-boundary/ErrorBoundaryExample.tsx | 6 +- .../src/8-error-boundary/ErrorShape.ts | 5 +- apps/examples/src/index.tsx | 24 +- .../src/only-editor/MicroSelectTool.ts | 60 + .../examples/src/only-editor/MiniBoxShape.tsx | 31 + .../src/only-editor/MiniSelectTool.ts | 122 + apps/examples/src/only-editor/OnlyEditor.tsx | 51 + apps/examples/src/yjs/YjsExample.tsx | 3 +- apps/examples/src/yjs/useYjsStore.ts | 19 +- apps/vscode/editor/package.json | 5 - apps/vscode/editor/scripts/build.ts | 6 +- .../scripts}/cli.ts | 0 apps/vscode/editor/scripts/dev.ts | 8 +- .../scripts}/helpers.ts | 2 +- .../scripts}/path.ts | 0 apps/vscode/editor/src/ChangeResponder.tsx | 16 +- apps/vscode/editor/src/FileOpen.tsx | 4 +- apps/vscode/editor/src/app.tsx | 43 +- apps/vscode/editor/src/utils/bookmarks.ts | 11 +- apps/vscode/editor/src/utils/links.ts | 2 +- apps/vscode/editor/src/utils/rpc.ts | 4 +- apps/vscode/editor/tsconfig.json | 9 +- apps/vscode/extension/package.json | 4 +- apps/vscode/extension/scripts/build.ts | 4 +- apps/vscode/extension/scripts/cli.ts | 58 + apps/vscode/extension/scripts/dev.ts | 6 +- apps/vscode/extension/scripts/helpers.ts | 25 + apps/vscode/extension/scripts/path.ts | 16 + apps/vscode/extension/src/TldrawDocument.ts | 2 +- .../extension/src/TldrawWebviewManager.ts | 5 +- .../extension/src/WebViewMessageHandler.ts | 2 +- apps/vscode/extension/src/file.ts | 7 +- apps/vscode/extension/tsconfig.json | 4 +- docs/docs/shapes.mdx | 4 +- package.json | 3 + packages/editor/api-report.md | 2228 +++++++---------- packages/editor/editor.css | 11 +- packages/editor/package.json | 11 +- packages/editor/src/index.ts | 235 +- packages/editor/src/lib/TldrawEditor.tsx | 66 +- packages/editor/src/lib/components/Canvas.tsx | 24 +- .../src/lib/components/DefaultBackground.tsx | 6 - .../src/lib/components/ErrorBoundary.tsx | 12 +- .../src/lib/components/PositionedOnCanvas.tsx | 30 + .../editor/src/lib/components/SelectionBg.tsx | 135 - packages/editor/src/lib/components/Shape.tsx | 10 +- .../default-components/DefaultBackground.tsx | 8 + .../{ => default-components}/DefaultBrush.tsx | 10 +- .../DefaultCollaboratorHint.tsx | 12 +- .../DefaultCursor.tsx | 8 +- .../DefaultErrorFallback.tsx | 16 +- .../{ => default-components}/DefaultGrid.tsx | 7 +- .../DefaultHandle.tsx | 5 +- .../default-components/DefaultScribble.tsx | 36 + .../DefaultSelectionBackground.tsx | 141 ++ .../DefaultSelectionForeground.tsx | 49 + .../DefaultShapeErrorFallback.tsx | 4 +- .../DefaultShapeIndicatorErrorFallback.tsx | 6 +- .../DefaultSnapLine.tsx | 8 +- .../DefaultSpinner.tsx | 4 +- .../DefaultSvgDefs.tsx | 0 packages/editor/src/lib/components/shared.ts | 120 - .../src/lib/config/TLSessionStateSnapshot.ts | 2 +- .../src/lib/config/TLUserPreferences.ts | 2 +- .../editor/src/lib/config/createTLStore.ts | 32 +- .../editor/src/lib/config/defaultShapes.ts | 46 +- .../editor/src/lib/config/defaultTools.ts | 32 - packages/editor/src/lib/config/defineShape.ts | 26 - packages/editor/src/lib/constants.ts | 23 +- packages/editor/src/lib/editor/Editor.ts | 1144 ++++----- .../src/lib/editor/managers/ClickManager.ts | 4 +- .../editor/managers/ExternalContentManager.ts | 599 ----- .../src/lib/editor/managers/HistoryManager.ts | 2 +- .../src/lib/editor/managers/SnapManager.ts | 25 +- .../src/lib/editor/managers/TextManager.ts | 15 +- .../src/lib/editor/managers/TickManager.ts | 2 +- .../lib/editor/shapes/BaseBoxShapeUtil.tsx | 5 +- .../editor/src/lib/editor/shapes/ShapeUtil.ts | 37 +- .../src/lib/editor/shapes/arrow/ArrowShape.ts | 10 - .../editor/shapes/arrow/toolStates/Idle.ts | 18 - .../editor/shapes/bookmark/BookmarkShape.ts | 10 - .../src/lib/editor/shapes/draw/DrawShape.ts | 10 - .../lib/editor/shapes/draw/toolStates/Idle.ts | 18 - .../src/lib/editor/shapes/embed/EmbedShape.ts | 10 - .../src/lib/editor/shapes/frame/FrameShape.ts | 10 - .../lib/editor/shapes/frame/FrameShapeTool.ts | 8 - .../src/lib/editor/shapes/geo/GeoShape.ts | 10 - .../{shared => group}/DashedOutlineBox.tsx | 4 +- .../lib/editor/shapes/group/GroupShape.tsx | 10 - .../editor/shapes/group/GroupShapeUtil.tsx | 27 +- .../editor/shapes/highlight/HighlightShape.ts | 10 - .../src/lib/editor/shapes/image/ImageShape.ts | 10 - .../src/lib/editor/shapes/line/LineShape.ts | 10 - .../lib/editor/shapes/line/toolStates/Idle.ts | 22 - .../src/lib/editor/shapes/note/NoteShape.ts | 10 - .../lib/editor/shapes/note/toolStates/Idle.ts | 18 - .../{arrow => shared}/arrow/arrow-types.ts | 2 +- .../{arrow => shared}/arrow/arrowheads.ts | 5 +- .../{arrow => shared}/arrow/curved-arrow.ts | 30 +- .../shapes/{arrow => shared}/arrow/shared.ts | 12 +- .../{arrow => shared}/arrow/straight-arrow.ts | 19 +- .../src/lib/editor/shapes/shared/resizeBox.ts | 3 +- .../lib/editor/shapes/shared/resizeScaled.ts | 3 +- .../src/lib/editor/shapes/text/TextShape.ts | 10 - .../src/lib/editor/shapes/video/VideoShape.ts | 10 - .../BaseBoxShapeTool/BaseBoxShapeTool.ts | 6 +- .../tools/BaseBoxShapeTool/children/Idle.ts | 10 +- .../BaseBoxShapeTool/children/Pointing.ts | 10 +- .../lib/editor/tools/EraserTool/EraserTool.ts | 15 - .../editor/tools/EraserTool/children/Idle.ts | 10 - .../editor/tools/HandTool/children/Idle.ts | 18 - .../tools/HandTool/children/Pointing.ts | 37 - .../lib/editor/tools/LaserTool/LaserTool.ts | 15 - .../editor/tools/LaserTool/children/Idle.ts | 10 - .../editor/src/lib/editor/tools/RootState.ts | 12 +- .../children/Crop/children/PointingCrop.ts | 20 - .../editor/src/lib/editor/tools/StateNode.ts | 20 + .../src/lib/editor/tools/ZoomTool/ZoomTool.ts | 56 - .../editor/tools/ZoomTool/children/Idle.ts | 16 - .../shared => types}/SvgExportContext.tsx | 2 + .../src/lib/editor/types/event-types.ts | 2 +- .../src/lib/editor/types/external-content.ts | 35 + .../src/lib/editor/types/selection-types.ts | 2 +- .../editor/src/lib/hooks/useCanvasEvents.ts | 2 +- .../editor/src/lib/hooks/useCoarsePointer.ts | 6 +- packages/editor/src/lib/hooks/useCursor.ts | 15 +- packages/editor/src/lib/hooks/useDarkMode.ts | 6 - .../editor/src/lib/hooks/useDocumentEvents.ts | 2 +- .../src/lib/hooks/useEditorComponents.tsx | 142 +- .../editor/src/lib/hooks/useGestureEvents.ts | 4 +- .../editor/src/lib/hooks/useHandleEvents.ts | 2 +- .../editor/src/lib/hooks/useIsCropping.ts | 1 + packages/editor/src/lib/hooks/useIsEditing.ts | 1 + .../editor/src/lib/hooks/useLocalStore.ts | 2 +- packages/editor/src/lib/hooks/usePeerIds.ts | 2 +- packages/editor/src/lib/hooks/usePrevious.ts | 10 - .../src/lib/hooks/useSelectionEvents.ts | 3 +- .../editor/src/lib/hooks/useShapeEvents.ts | 2 +- packages/editor/src/lib/hooks/useTLStore.ts | 12 +- packages/editor/src/lib/hooks/useTransform.ts | 3 +- .../src/lib/primitives}/Box2d.test.ts | 0 .../src/lib/primitives}/Box2d.ts | 0 .../src/lib/primitives}/Matrix2d.test.ts | 0 .../src/lib/primitives}/Matrix2d.ts | 0 .../src/lib/primitives}/Vec2d.test.ts | 0 .../src/lib/primitives}/Vec2d.ts | 0 .../src/lib/primitives}/easings.ts | 0 .../src/lib/primitives}/intersect.ts | 0 .../src/lib/primitives}/utils.ts | 10 + .../src/lib/test/currentToolIdMask.test.ts | 47 + packages/editor/src/lib/utils/assets.ts | 273 -- packages/editor/src/lib/utils/crc.ts | 55 - packages/editor/src/lib/utils/data.ts | 106 - packages/editor/src/lib/utils/dom.ts | 52 +- .../src/lib/utils/getIncrementedName.ts | 22 + .../editor/src/lib/utils/getPointerInfo.ts | 20 + .../src/lib/utils/getSvgPathFromPoints.ts | 49 + .../{hard-reset.ts => hardResetEditor.ts} | 0 .../shared.ts => utils/normalizeWheel.ts} | 2 +- packages/editor/src/lib/utils/png.ts | 75 +- .../utils/{refresh-page.ts => refreshPage.ts} | 0 .../editor/src/lib/utils/reorderShapes.ts | 2 +- .../reordering}/dgreensp/dgreensp.test.ts | 0 .../utils/reordering}/dgreensp/dgreensp.ts | 0 .../lib/utils/reordering}/dgreensp/index.ts | 0 .../lib/utils/reordering}/reordering.test.ts | 0 .../src/lib/utils/reordering}/reordering.ts | 0 packages/editor/src/lib/utils/rotation.ts | 4 +- packages/editor/src/lib/utils/string.ts | 36 - packages/editor/src/lib/utils/svg.ts | 125 - .../lib/utils/sync/TLLocalSyncClient.test.ts | 3 +- packages/editor/src/lib/utils/uniq.ts | 14 + packages/editor/src/lib/utils/uniqueId.ts | 16 + packages/editor/src/version.ts | 1 + packages/editor/tsconfig.json | 2 - packages/file-format/CHANGELOG.md | 146 -- packages/file-format/LICENSE | 190 -- packages/file-format/README.md | 5 - packages/file-format/api-extractor.json | 4 - packages/file-format/api-report.md | 90 - packages/file-format/package.json | 66 - packages/file-format/src/index.ts | 12 - packages/file-format/src/test/file.test.ts | 127 - packages/file-format/tsconfig.json | 17 - packages/indices/CHANGELOG.md | 35 - packages/indices/LICENSE | 190 -- packages/indices/README.md | 77 - packages/indices/api-extractor.json | 4 - packages/indices/api-report.md | 35 - packages/indices/package.json | 57 - packages/indices/src/index.ts | 10 - packages/indices/tsconfig.json | 10 - packages/polyfills/CHANGELOG.md | 162 -- packages/polyfills/LICENSE | 190 -- packages/polyfills/README.md | 5 - packages/polyfills/api-extractor.json | 4 - packages/polyfills/api-report.md | 9 - packages/polyfills/package.json | 47 - packages/polyfills/src/index.ts | 1 - packages/polyfills/src/lib/polyfills.ts | 5 - packages/polyfills/tsconfig.json | 9 - packages/primitives/CHANGELOG.md | 223 -- packages/primitives/LICENSE | 190 -- packages/primitives/README.md | 5 - packages/primitives/api-extractor.json | 4 - packages/primitives/api-report.md | 857 ------- packages/primitives/package.json | 58 - packages/primitives/src/index.ts | 98 - packages/primitives/tsconfig.json | 10 - packages/state/package.json | 2 +- packages/store/package.json | 4 +- packages/store/src/lib/test/testSchema.v0.ts | 2 +- packages/tldraw/api-report.md | 818 +++++- packages/tldraw/package.json | 24 +- packages/tldraw/scripts/copy-css-files.mjs | 4 +- packages/tldraw/setupTests.js | 7 + packages/tldraw/src/index.ts | 132 +- packages/tldraw/src/lib/Tldraw.tsx | 66 +- .../src/lib/canvas}/CropHandles.tsx | 2 +- .../src/lib/canvas/TldrawScribble.tsx} | 9 +- .../lib/canvas/TldrawSelectionForeground.tsx} | 20 +- packages/tldraw/src/lib/defaultShapeTools.ts | 20 + packages/tldraw/src/lib/defaultShapeUtils.ts | 29 + packages/tldraw/src/lib/defaultTools.ts | 8 + .../lib}/shapes/arrow/ArrowShapeTool.test.ts | 37 +- .../src/lib}/shapes/arrow/ArrowShapeTool.ts | 8 +- .../lib}/shapes/arrow/ArrowShapeUtil.test.ts | 41 +- .../src/lib}/shapes/arrow/ArrowShapeUtil.tsx | 102 +- .../arrow/components/ArrowTextLabel.tsx | 6 +- .../src/lib/shapes/arrow/toolStates/Idle.ts | 17 + .../lib}/shapes/arrow/toolStates/Pointing.ts | 16 +- .../shapes/bookmark/BookmarkShapeUtil.tsx | 34 +- .../lib}/shapes/draw/DrawShapeTool.test.ts | 8 +- .../src/lib}/shapes/draw/DrawShapeTool.ts | 10 +- .../src/lib}/shapes/draw/DrawShapeUtil.tsx | 66 +- .../src/lib}/shapes/draw/getPath.ts | 12 +- .../lib}/shapes/draw/toolStates/Drawing.ts | 32 +- .../src/lib/shapes/draw/toolStates/Idle.ts | 17 + .../src/lib}/shapes/embed/EmbedShapeUtil.tsx | 32 +- .../lib}/shapes/frame/FrameShapeTool.test.ts | 36 +- .../src/lib/shapes/frame/FrameShapeTool.ts | 7 + .../src/lib}/shapes/frame/FrameShapeUtil.tsx | 32 +- .../shapes/frame/components/FrameHeading.tsx | 12 +- .../frame/components/FrameLabelInput.tsx | 7 +- .../src/lib}/shapes/geo/GeoShapeTool.test.ts | 40 +- .../src/lib}/shapes/geo/GeoShapeTool.ts | 9 +- .../src/lib}/shapes/geo/GeoShapeUtil.tsx | 70 +- .../src/lib}/shapes/geo/cloudOutline.ts | 12 +- .../shapes/geo/components/DashStyleCloud.tsx | 9 +- .../geo/components/DashStyleEllipse.tsx | 9 +- .../shapes/geo/components/DashStyleOval.tsx | 3 +- .../geo/components/DashStylePolygon.tsx | 3 +- .../shapes/geo/components/DrawStyleCloud.tsx | 2 +- .../geo/components/DrawStyleEllipse.tsx | 24 +- .../geo/components/DrawStylePolygon.tsx | 6 +- .../shapes/geo/components/SolidStyleCloud.tsx | 2 +- .../geo/components/SolidStyleEllipse.tsx | 2 +- .../shapes/geo/components/SolidStyleOval.tsx | 2 +- .../geo/components/SolidStylePolygon.tsx | 3 +- .../src/lib}/shapes/geo/helpers.ts | 2 +- .../src/lib}/shapes/geo/toolStates/Idle.ts | 20 +- .../lib}/shapes/geo/toolStates/Pointing.ts | 23 +- .../highlight/HighlightShapeTool.test.ts | 0 .../shapes/highlight/HighlightShapeTool.ts | 11 +- .../shapes/highlight/HighlightShapeUtil.tsx | 46 +- .../src/lib}/shapes/image/ImageShapeUtil.tsx | 40 +- .../lib}/shapes/line/LineShapeTool.test.ts | 45 +- .../src/lib}/shapes/line/LineShapeTool.ts | 8 +- .../lib}/shapes/line/LineShapeUtil.test.ts | 3 +- .../src/lib}/shapes/line/LineShapeUtil.tsx | 46 +- .../__snapshots__/LineShapeUtil.test.ts.snap | 2 +- .../shapes/line/components/getLinePath.ts | 21 +- .../lib}/shapes/line/components/getLineSvg.ts | 5 +- .../src/lib/shapes/line/toolStates/Idle.ts | 20 + .../lib}/shapes/line/toolStates/Pointing.ts | 27 +- .../lib}/shapes/note/NoteShapeTool.test.ts | 30 +- .../src/lib}/shapes/note/NoteShapeTool.ts | 9 +- .../src/lib}/shapes/note/NoteShapeUtil.tsx | 47 +- .../src/lib/shapes/note/toolStates/Idle.ts | 17 + .../lib}/shapes/note/toolStates/Pointing.ts | 29 +- .../lib}/shapes/shared/HyperlinkButton.tsx | 2 +- .../src/lib/shapes/shared}/ScribbleManager.ts | 4 +- .../src/lib}/shapes/shared/ShapeFill.tsx | 10 +- .../src/lib/shapes/shared}/TextHelpers.ts | 0 .../src/lib}/shapes/shared/TextLabel.tsx | 8 +- .../shared/createTextSvgElementFromSpans.ts | 11 +- .../shapes/shared/default-shape-constants.ts | 2 +- .../lib}/shapes/shared/defaultStyleDefs.tsx | 13 +- .../lib/shapes/shared}/freehand/getStroke.ts | 2 +- .../freehand/getStrokeOutlinePoints.ts | 2 +- .../shared}/freehand/getStrokePoints.ts | 2 +- .../shared}/freehand/getStrokeRadius.ts | 0 .../shared}/freehand/setStrokePointRadii.ts | 2 +- .../src/lib/shapes/shared/freehand/svg.ts | 50 + .../src/lib/shapes/shared}/freehand/types.ts | 2 +- .../shared}/getBrowserCanvasMaxSize.tsx | 0 .../lib/shapes/shared/getPerfectDashProps.ts | 96 + .../shapes/shared/getTextLabelSvgElement.ts | 6 +- .../src/lib/shapes/shared/legacyProps.ts} | 3 +- .../src/lib/shapes/shared}/polygon-helpers.ts | 3 +- .../tldraw/src/lib/shapes/shared/resizeBox.ts | 130 + .../src/lib/shapes/shared/resizeScaled.ts | 40 + .../shapes/shared/splines}/BaseSegment2d.ts | 3 +- .../shapes/shared/splines}/BaseSpline2d.ts | 3 +- .../shared/splines}/CubicSegment2d.test.ts | 0 .../shapes/shared/splines}/CubicSegment2d.ts | 2 +- .../shapes/shared/splines}/CubicSpline2d.ts | 2 +- .../shapes/shared/splines}/LineSegment2d.ts | 3 +- .../lib/shapes/shared/splines}/Polyline2d.ts | 2 +- .../src/lib}/shapes/shared/useColorSpace.tsx | 3 +- .../src/lib}/shapes/shared/useEditableText.ts | 20 +- .../src/lib}/shapes/shared/useForceSolid.ts | 3 +- .../shapes/shared/usePrefersReducedMotion.tsx | 18 + .../lib}/shapes/text/TextShapeTool.test.ts | 25 +- .../src/lib}/shapes/text/TextShapeTool.ts | 10 +- .../src/lib}/shapes/text/TextShapeUtil.tsx | 53 +- .../src/lib}/shapes/text/toolStates/Idle.ts | 32 +- .../lib}/shapes/text/toolStates/Pointing.ts | 33 +- .../src/lib}/shapes/video/VideoShapeUtil.tsx | 33 +- .../src/lib/tools/EraserTool/EraserTool.ts | 14 + .../lib}/tools/EraserTool/children/Erasing.ts | 26 +- .../src/lib/tools/EraserTool/children/Idle.ts | 9 + .../tools/EraserTool/children/Pointing.ts | 14 +- .../src/lib}/tools/HandTool/HandTool.ts | 15 +- .../lib}/tools/HandTool/children/Dragging.ts | 17 +- .../src/lib/tools/HandTool/children/Idle.ts | 17 + .../lib/tools/HandTool/children/Pointing.ts | 36 + .../src/lib/tools/LaserTool/LaserTool.ts | 13 + .../src/lib/tools/LaserTool/children/Idle.ts | 9 + .../lib}/tools/LaserTool/children/Lasering.ts | 12 +- .../tools/SelectTool}/DragAndDropManager.ts | 11 +- .../src/lib}/tools/SelectTool/SelectTool.ts | 10 +- .../tools/SelectTool/children/Brushing.ts | 46 +- .../tools/SelectTool/children/Crop/Crop.ts | 7 +- .../SelectTool/children/Crop/children/Idle.ts | 56 +- .../children/Crop/children/PointingCrop.ts | 19 + .../children/Crop/children/TranslatingCrop.ts | 27 +- .../children/Crop/children/crop_helpers.ts | 12 +- .../children/Crop/crop-constants.ts | 2 + .../tools/SelectTool/children/Cropping.ts | 38 +- .../SelectTool/children/DraggingHandle.ts | 56 +- .../tools/SelectTool/children/EditingShape.ts | 25 +- .../lib}/tools/SelectTool/children/Idle.ts | 55 +- .../SelectTool/children/PointingCanvas.ts | 64 +- .../SelectTool/children/PointingCropHandle.ts | 33 +- .../SelectTool/children/PointingHandle.ts | 20 +- .../children/PointingResizeHandle.ts | 19 +- .../children/PointingRotateHandle.ts | 18 +- .../SelectTool/children/PointingSelection.ts | 9 +- .../SelectTool/children/PointingShape.ts | 24 +- .../tools/SelectTool/children/Resizing.ts | 53 +- .../tools/SelectTool/children/Rotating.ts | 35 +- .../SelectTool/children/ScribbleBrushing.ts | 27 +- .../tools/SelectTool/children/Translating.ts | 79 +- .../tldraw/src/lib/tools/ZoomTool/ZoomTool.ts | 58 + .../src/lib/tools/ZoomTool/children/Idle.ts | 15 + .../lib}/tools/ZoomTool/children/Pointing.ts | 11 +- .../tools/ZoomTool/children/ZoomBrushing.ts | 18 +- packages/{ui => tldraw/src/lib}/ui.css | 10 + .../lib => tldraw/src/lib/ui}/TldrawUi.tsx | 3 +- .../src/lib/ui}/TldrawUiContextProvider.tsx | 2 +- .../lib => tldraw/src/lib/ui}/assetUrls.ts | 11 +- .../src/lib/ui}/components/ActionsMenu.tsx | 0 .../src/lib/ui}/components/BackToContent.tsx | 0 .../src/lib/ui}/components/ContextMenu.tsx | 3 +- .../src/lib/ui}/components/DebugPanel.tsx | 5 +- .../src/lib/ui}/components/Dialogs.tsx | 0 .../lib/ui}/components/DuplicateButton.tsx | 3 +- .../src/lib/ui}/components/EditLinkDialog.tsx | 3 +- .../src/lib/ui}/components/EmbedDialog.tsx | 5 +- .../lib/ui}/components/FollowingIndicator.tsx | 3 +- .../src/lib/ui}/components/HTMLCanvas.tsx | 3 +- .../src/lib/ui}/components/HelpMenu.tsx | 0 .../components/KeyboardShortcutsDialog.tsx | 0 .../src/lib/ui}/components/LanguageMenu.tsx | 2 +- .../src/lib/ui}/components/Menu.tsx | 0 .../src/lib/ui}/components/MenuZone.tsx | 3 +- .../lib/ui}/components/MobileStylePanel.tsx | 3 +- .../src/lib/ui}/components/MoveToPageMenu.tsx | 3 +- .../ui}/components/NavigationZone/Minimap.tsx | 30 +- .../NavigationZone/MinimapManager.ts | 12 +- .../NavigationZone/NavigationZone.tsx | 2 +- .../components/NavigationZone/ZoomMenu.tsx | 3 +- .../ui}/components/PageMenu/PageItemInput.tsx | 0 .../components/PageMenu/PageItemSubmenu.tsx | 3 +- .../lib/ui}/components/PageMenu/PageMenu.tsx | 16 +- .../components/PageMenu/edit-pages-shared.ts | 0 .../src/lib/ui}/components/PenModeToggle.tsx | 3 +- .../src/lib/ui}/components/RedoButton.tsx | 0 .../src/lib/ui}/components/Spinner.tsx | 0 .../src/lib/ui}/components/StopFollowing.tsx | 3 +- .../StylePanel/DoubleDropdownPicker.tsx | 3 +- .../components/StylePanel/DropdownPicker.tsx | 0 .../ui}/components/StylePanel/StylePanel.tsx | 4 +- .../lib/ui}/components/StylePanel/styles.tsx | 0 .../src/lib/ui}/components/Toasts.tsx | 0 .../Toolbar/ToggleToolLockedButton.tsx | 3 +- .../lib/ui}/components/Toolbar/Toolbar.tsx | 3 +- .../src/lib/ui}/components/TrashButton.tsx | 3 +- .../src/lib/ui}/components/UndoButton.tsx | 0 .../lib/ui}/components/primitives/Button.tsx | 0 .../components/primitives/ButtonPicker.tsx | 2 +- .../lib/ui}/components/primitives/Dialog.tsx | 0 .../components/primitives/DropdownMenu.tsx | 0 .../lib/ui}/components/primitives/Icon.tsx | 0 .../lib/ui}/components/primitives/Input.tsx | 0 .../src/lib/ui}/components/primitives/Kbd.tsx | 0 .../lib/ui}/components/primitives/Popover.tsx | 0 .../lib/ui}/components/primitives/Slider.tsx | 0 .../lib/ui}/components/primitives/shared.ts | 0 .../lib => tldraw/src/lib/ui}/constants.ts | 0 .../hooks/clipboard/pasteExcalidrawContent.ts | 6 +- .../src/lib/ui}/hooks/clipboard/pasteFiles.ts | 3 +- .../ui}/hooks/clipboard/pasteTldrawContent.ts | 3 +- .../src/lib/ui}/hooks/clipboard/pasteUrl.ts | 3 +- .../src/lib/ui}/hooks/menuHelpers.ts | 11 +- .../src/lib/ui}/hooks/useActions.tsx | 47 +- .../lib/ui}/hooks/useActionsMenuSchema.tsx | 3 +- .../src/lib/ui}/hooks/useAssetUrls.tsx | 0 .../src/lib/ui}/hooks/useBreakpoint.tsx | 3 +- .../src/lib/ui}/hooks/useCanRedo.ts | 3 +- .../src/lib/ui}/hooks/useCanUndo.ts | 3 +- .../src/lib/ui}/hooks/useClipboardEvents.ts | 39 +- .../lib/ui}/hooks/useContextMenuSchema.tsx | 69 +- .../src/lib/ui}/hooks/useCopyAs.ts | 10 +- .../src/lib/ui}/hooks/useDialogsProvider.tsx | 0 .../src/lib/ui}/hooks/useEditorEvents.ts | 0 .../src/lib/ui}/hooks/useEditorIsFocused.ts | 3 +- .../src/lib/ui}/hooks/useEventsProvider.tsx | 1 + .../src/lib/ui}/hooks/useExportAs.ts | 7 +- .../lib/ui}/hooks/useHasLinkShapeSelected.ts | 3 +- .../src/lib/ui}/hooks/useHelpMenuSchema.tsx | 4 +- .../src/lib/ui}/hooks/useHighDpiCanvas.ts | 0 .../src/lib/ui}/hooks/useInsertMedia.ts | 4 +- .../src/lib/ui}/hooks/useKeyboardShortcuts.ts | 12 +- .../ui}/hooks/useKeyboardShortcutsSchema.tsx | 4 +- .../src/lib/ui}/hooks/useLocalStorageState.ts | 0 .../src/lib/ui}/hooks/useMenuIsOpen.ts | 3 +- .../src/lib/ui}/hooks/useMenuSchema.tsx | 4 +- .../lib/ui}/hooks/useOnlyFlippableShape.ts | 11 +- .../src/lib/ui}/hooks/usePreloadIcons.ts | 0 .../src/lib/ui}/hooks/usePrint.ts | 0 .../src/lib/ui}/hooks/useReadonly.ts | 3 +- .../lib/ui}/hooks/useShowAutoSizeToggle.ts | 3 +- .../src/lib/ui}/hooks/useToastsProvider.tsx | 0 .../src/lib/ui}/hooks/useToolbarSchema.tsx | 4 +- .../src/lib/ui}/hooks/useTools.tsx | 27 +- .../useTranslation/TLUiTranslationKey.ts | 0 .../useTranslation/defaultTranslation.ts | 0 .../ui}/hooks/useTranslation/translations.ts | 2 +- .../ui}/hooks/useTranslation/useLanguages.tsx | 0 .../hooks/useTranslation/useTranslation.tsx | 3 +- .../lib => tldraw/src/lib/ui}/icon-types.ts | 0 .../lib => tldraw/src/lib/ui}/overrides.ts | 3 +- .../{ui/src => tldraw/src/lib/ui}/version.ts | 0 .../lib/useRegisterExternalContentHandlers.ts | 434 ++++ .../lib => tldraw/src/lib/utils}/assetUrls.ts | 5 +- packages/tldraw/src/lib/utils/assets.ts | 174 ++ packages/tldraw/src/lib/utils/blobAsString.ts | 14 + .../src/lib/utils}/buildFromV1Document.ts | 6 +- .../src/lib/utils/embeds.test.ts | 0 .../src/lib/utils/embeds.ts | 14 +- .../src/lib/utils/export.ts | 7 +- .../src/lib => tldraw/src/lib/utils}/file.ts | 72 +- .../src/lib/utils/is-gif-animated.ts | 0 .../src/lib/utils/rotated-box-shadow.ts | 29 + packages/tldraw/src/lib/utils/text.ts | 59 + .../src/lib/utils}/usePreloadAssets.ts | 2 +- .../src/test}/ClickManager.test.ts | 4 +- .../lib => tldraw/src}/test/Editor.test.tsx | 64 +- .../src/test}/EraserTool.test.ts | 38 +- .../src/test}/HandTool.test.ts | 38 +- .../src/test}/LaserTool.test.ts | 0 .../src/test}/SelectTool.test.ts | 8 +- .../src/test}/TLSessionStateSnapshot.test.ts | 8 +- .../src/test}/TLUserPreferences.test.ts | 8 +- .../src}/test/TestEditor.test.ts | 0 .../src/lib => tldraw/src}/test/TestEditor.ts | 67 +- .../src}/test/TldrawEditor.test.tsx | 196 +- .../src/test}/ZoomTool.test.ts | 4 +- .../test}/__snapshots__/groups.test.ts.snap | 0 .../test}/__snapshots__/resizing.test.ts.snap | 0 .../src/test}/arrowBindingsIndex.test.tsx | 32 +- .../utils => tldraw/src/test}/assets.test.ts | 2 +- .../src/test/bookmark-shapes.test.ts} | 6 +- .../__snapshots__/getSvg.test.ts.snap | 0 .../__snapshots__/packShapes.test.ts.snap | 0 .../__snapshots__/zoomToFit.test.ts.snap | 0 .../src}/test/commands/alignShapes.test.tsx | 5 +- .../commands/allShapesCommonBounds.test.ts | 4 +- .../src}/test/commands/animateShapes.test.ts | 0 .../src}/test/commands/animateToShape.test.ts | 0 .../src}/test/commands/animationSpeed.test.ts | 6 +- .../src}/test/commands/blur.test.ts | 0 .../src}/test/commands/cancel.test.ts | 0 .../src}/test/commands/centerOnPoint.test.ts | 0 .../src}/test/commands/clipboard.test.ts | 2 +- .../src}/test/commands/complete.test.ts | 0 .../src}/test/commands/createPage.test.ts | 3 +- .../src}/test/commands/createShapes.test.ts | 2 +- .../src}/test/commands/deletePage.test.ts | 2 +- .../src}/test/commands/deleteShapes.test.ts | 2 +- .../test/commands/distributeShapes.test.ts | 3 +- .../src}/test/commands/duplicatePage.test.ts | 2 +- .../src}/test/commands/getContent.test.ts | 0 .../commands/getInitialMetaForShape.test.ts | 2 +- .../src}/test/commands/getSvg.test.ts | 3 +- .../src}/test/commands/groupShapes.test.ts | 0 .../lib => tldraw/src}/test/commands/guide.md | 0 .../src}/test/commands/interrupt.test.ts | 0 .../src}/test/commands/lockShapes.test.ts | 3 +- .../test/commands/moveShapesToPage.test.ts | 8 +- .../src}/test/commands/nudge.test.ts | 6 +- .../src}/test/commands/packShapes.test.ts | 2 +- .../src}/test/commands/pageToScreen.test.ts | 0 .../src}/test/commands/pan.test.ts | 2 +- .../src/test/commands}/penmode.test.ts | 8 +- .../src}/test/commands/pinch.test.ts | 0 .../src}/test/commands/putContent.test.ts | 3 +- .../src}/test/commands/reorderShapes.test.ts | 2 +- .../test/commands/reparentShapesById.test.ts | 4 +- .../src}/test/commands/resetZoom.test.ts | 0 .../src}/test/commands/resizeShape.test.ts | 2 +- .../src}/test/commands/rotateShapes.test.ts | 2 +- .../src}/test/commands/screenToPage.test.ts | 0 .../src}/test/commands/setAppState.test.ts | 0 .../src}/test/commands/setBrush.test.ts | 2 +- .../src}/test/commands/setCurrentPage.test.ts | 2 +- .../src}/test/commands/setPageState.test.ts | 0 .../src}/test/commands/setSelectedIds.test.ts | 4 +- .../test/commands/setSelectedTool.test.ts | 4 +- .../src}/test/commands/setSettings.test.ts | 0 .../src}/test/commands/setStyle.test.ts | 0 .../tldraw/src/test/commands/squash.test.ts | 13 + .../src}/test/commands/stackShapes.test.ts | 2 +- .../src}/test/commands/stretch.test.tsx | 13 +- .../src}/test/commands/ungroup.test.ts | 0 .../src}/test/commands/updateShapes.test.ts | 4 +- .../commands/updateViewportPageBounds.test.ts | 0 .../src}/test/commands/zoomIn.test.ts | 2 +- .../src}/test/commands/zoomOut.test.ts | 2 +- .../src}/test/commands/zoomToBounds.test.ts | 0 .../src}/test/commands/zoomToFit.test.ts | 2 +- .../test/commands/zoomToSelection.test.ts | 4 +- .../src/test}/cropping.test.ts | 6 +- .../tools => tldraw/src/test}/drawing.test.ts | 39 +- .../lib => tldraw/src}/test/duplicate.test.ts | 2 +- .../src/test}/flipShapes.test.ts | 7 +- .../tools => tldraw/src/test}/frames.test.ts | 130 +- .../src/test}/getSnapLines.ts | 2 +- .../tools => tldraw/src/test}/groups.test.ts | 79 +- .../test/middleMouseButtonPanning.test.ts | 2 +- .../lib => tldraw/src}/test/modifiers.test.ts | 0 .../tools => tldraw/src/test}/panning.test.ts | 2 +- .../parentsToChildrenWithIndexes.test.ts | 5 +- .../src/test}/paste.test.ts | 4 +- .../src/test}/renderingShapes.test.tsx | 7 +- .../lib => tldraw/src}/test/resizeBox.test.ts | 4 +- .../src/test}/resizing.test.ts | 27 +- .../src/test}/rotating.test.ts | 5 +- .../src/test}/roundedBox.ts | 2 +- .../tools => tldraw/src/test}/select.test.tsx | 18 +- .../src/test}/shapeIdsInCurrentPage.test.ts | 4 +- .../src}/test/shapeutils.test.ts | 2 +- .../src}/test/spacebarPanning.test.ts | 2 +- .../src/test/styles2.test.tsx} | 12 +- .../src/test/styles3.test.ts} | 4 +- .../jsx.tsx => tldraw/src/test/test-jsx.tsx} | 13 +- .../tldraw/src/test/testutils/getSnapLines.ts | 25 + .../tldraw/src/test/testutils/roundedBox.ts | 13 + .../tools => tldraw/src/test}/text.test.ts | 4 +- .../src/test/translating-snapping.test.ts | 594 +++++ .../src/test}/translating.test.ts | 328 +-- packages/tldraw/tsconfig.json | 2 +- packages/tlschema/api-report.md | 2 - packages/tlschema/package.json | 4 +- packages/tlschema/src/migrations.test.ts | 38 + packages/tlschema/src/misc/TLCursor.ts | 5 +- packages/tlschema/src/records/TLInstance.ts | 22 +- packages/tlschema/src/records/TLShape.ts | 2 +- packages/ui/CHANGELOG.md | 612 ----- packages/ui/README.md | 5 - packages/ui/api-extractor.json | 4 - packages/ui/api-report.md | 694 ----- packages/ui/package.json | 101 - packages/ui/setupTests.js | 3 - packages/ui/src/index.ts | 104 - packages/ui/tsconfig.json | 12 - packages/utils/api-report.md | 3 + packages/utils/src/index.ts | 1 + packages/utils/src/lib/url.ts | 8 + packages/validate/api-report.md | 2 +- packages/validate/src/index.ts | 1 + public-yarn.lock | 1036 +++++--- scripts/check-scripts.ts | 12 - scripts/lib/publishing.ts | 9 +- scripts/refresh-assets.ts | 13 +- 618 files changed, 8939 insertions(+), 11666 deletions(-) delete mode 100644 apps/examples/src/3-custom-config/CardShape/CardShape.ts delete mode 100644 apps/examples/src/3-custom-config/custom-shapes.ts create mode 100644 apps/examples/src/only-editor/MicroSelectTool.ts create mode 100644 apps/examples/src/only-editor/MiniBoxShape.tsx create mode 100644 apps/examples/src/only-editor/MiniSelectTool.ts create mode 100644 apps/examples/src/only-editor/OnlyEditor.tsx rename apps/vscode/{vscode-script-utils => editor/scripts}/cli.ts (100%) rename apps/vscode/{vscode-script-utils => editor/scripts}/helpers.ts (92%) rename apps/vscode/{vscode-script-utils => editor/scripts}/path.ts (100%) create mode 100644 apps/vscode/extension/scripts/cli.ts create mode 100644 apps/vscode/extension/scripts/helpers.ts create mode 100644 apps/vscode/extension/scripts/path.ts delete mode 100644 packages/editor/src/lib/components/DefaultBackground.tsx create mode 100644 packages/editor/src/lib/components/PositionedOnCanvas.tsx delete mode 100644 packages/editor/src/lib/components/SelectionBg.tsx create mode 100644 packages/editor/src/lib/components/default-components/DefaultBackground.tsx rename packages/editor/src/lib/components/{ => default-components}/DefaultBrush.tsx (82%) rename packages/editor/src/lib/components/{ => default-components}/DefaultCollaboratorHint.tsx (73%) rename packages/editor/src/lib/components/{ => default-components}/DefaultCursor.tsx (86%) rename packages/editor/src/lib/components/{ => default-components}/DefaultErrorFallback.tsx (92%) rename packages/editor/src/lib/components/{ => default-components}/DefaultGrid.tsx (88%) rename packages/editor/src/lib/components/{ => default-components}/DefaultHandle.tsx (83%) create mode 100644 packages/editor/src/lib/components/default-components/DefaultScribble.tsx create mode 100644 packages/editor/src/lib/components/default-components/DefaultSelectionBackground.tsx create mode 100644 packages/editor/src/lib/components/default-components/DefaultSelectionForeground.tsx rename packages/editor/src/lib/components/{ => default-components}/DefaultShapeErrorFallback.tsx (64%) rename packages/editor/src/lib/components/{ => default-components}/DefaultShapeIndicatorErrorFallback.tsx (51%) rename packages/editor/src/lib/components/{ => default-components}/DefaultSnapLine.tsx (96%) rename packages/editor/src/lib/components/{ => default-components}/DefaultSpinner.tsx (85%) rename packages/editor/src/lib/components/{ => default-components}/DefaultSvgDefs.tsx (100%) delete mode 100644 packages/editor/src/lib/components/shared.ts delete mode 100644 packages/editor/src/lib/config/defaultTools.ts delete mode 100644 packages/editor/src/lib/config/defineShape.ts delete mode 100644 packages/editor/src/lib/editor/managers/ExternalContentManager.ts delete mode 100644 packages/editor/src/lib/editor/shapes/arrow/ArrowShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/arrow/toolStates/Idle.ts delete mode 100644 packages/editor/src/lib/editor/shapes/bookmark/BookmarkShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/draw/DrawShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/draw/toolStates/Idle.ts delete mode 100644 packages/editor/src/lib/editor/shapes/embed/EmbedShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/frame/FrameShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/frame/FrameShapeTool.ts delete mode 100644 packages/editor/src/lib/editor/shapes/geo/GeoShape.ts rename packages/editor/src/lib/editor/shapes/{shared => group}/DashedOutlineBox.tsx (85%) delete mode 100644 packages/editor/src/lib/editor/shapes/group/GroupShape.tsx delete mode 100644 packages/editor/src/lib/editor/shapes/highlight/HighlightShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/image/ImageShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/line/LineShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/line/toolStates/Idle.ts delete mode 100644 packages/editor/src/lib/editor/shapes/note/NoteShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/note/toolStates/Idle.ts rename packages/editor/src/lib/editor/shapes/{arrow => shared}/arrow/arrow-types.ts (91%) rename packages/editor/src/lib/editor/shapes/{arrow => shared}/arrow/arrowheads.ts (94%) rename packages/editor/src/lib/editor/shapes/{arrow => shared}/arrow/curved-arrow.ts (97%) rename packages/editor/src/lib/editor/shapes/{arrow => shared}/arrow/shared.ts (90%) rename packages/editor/src/lib/editor/shapes/{arrow => shared}/arrow/straight-arrow.ts (95%) delete mode 100644 packages/editor/src/lib/editor/shapes/text/TextShape.ts delete mode 100644 packages/editor/src/lib/editor/shapes/video/VideoShape.ts delete mode 100644 packages/editor/src/lib/editor/tools/EraserTool/EraserTool.ts delete mode 100644 packages/editor/src/lib/editor/tools/EraserTool/children/Idle.ts delete mode 100644 packages/editor/src/lib/editor/tools/HandTool/children/Idle.ts delete mode 100644 packages/editor/src/lib/editor/tools/HandTool/children/Pointing.ts delete mode 100644 packages/editor/src/lib/editor/tools/LaserTool/LaserTool.ts delete mode 100644 packages/editor/src/lib/editor/tools/LaserTool/children/Idle.ts delete mode 100644 packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/PointingCrop.ts delete mode 100644 packages/editor/src/lib/editor/tools/ZoomTool/ZoomTool.ts delete mode 100644 packages/editor/src/lib/editor/tools/ZoomTool/children/Idle.ts rename packages/editor/src/lib/editor/{shapes/shared => types}/SvgExportContext.tsx (93%) create mode 100644 packages/editor/src/lib/editor/types/external-content.ts delete mode 100644 packages/editor/src/lib/hooks/usePrevious.ts rename packages/{primitives/src/lib => editor/src/lib/primitives}/Box2d.test.ts (100%) rename packages/{primitives/src/lib => editor/src/lib/primitives}/Box2d.ts (100%) rename packages/{primitives/src/lib => editor/src/lib/primitives}/Matrix2d.test.ts (100%) rename packages/{primitives/src/lib => editor/src/lib/primitives}/Matrix2d.ts (100%) rename packages/{primitives/src/lib => editor/src/lib/primitives}/Vec2d.test.ts (100%) rename packages/{primitives/src/lib => editor/src/lib/primitives}/Vec2d.ts (100%) rename packages/{primitives/src/lib => editor/src/lib/primitives}/easings.ts (100%) rename packages/{primitives/src/lib => editor/src/lib/primitives}/intersect.ts (100%) rename packages/{primitives/src/lib => editor/src/lib/primitives}/utils.ts (98%) create mode 100644 packages/editor/src/lib/test/currentToolIdMask.test.ts delete mode 100644 packages/editor/src/lib/utils/crc.ts delete mode 100644 packages/editor/src/lib/utils/data.ts create mode 100644 packages/editor/src/lib/utils/getIncrementedName.ts create mode 100644 packages/editor/src/lib/utils/getPointerInfo.ts create mode 100644 packages/editor/src/lib/utils/getSvgPathFromPoints.ts rename packages/editor/src/lib/utils/{hard-reset.ts => hardResetEditor.ts} (100%) rename packages/editor/src/lib/{hooks/shared.ts => utils/normalizeWheel.ts} (97%) rename packages/editor/src/lib/utils/{refresh-page.ts => refreshPage.ts} (100%) rename packages/{indices/src/lib => editor/src/lib/utils/reordering}/dgreensp/dgreensp.test.ts (100%) rename packages/{indices/src/lib => editor/src/lib/utils/reordering}/dgreensp/dgreensp.ts (100%) rename packages/{indices/src/lib => editor/src/lib/utils/reordering}/dgreensp/index.ts (100%) rename packages/{indices/src/lib => editor/src/lib/utils/reordering}/reordering.test.ts (100%) rename packages/{indices/src/lib => editor/src/lib/utils/reordering}/reordering.ts (100%) delete mode 100644 packages/editor/src/lib/utils/string.ts delete mode 100644 packages/editor/src/lib/utils/svg.ts create mode 100644 packages/editor/src/lib/utils/uniq.ts create mode 100644 packages/editor/src/lib/utils/uniqueId.ts delete mode 100644 packages/file-format/CHANGELOG.md delete mode 100644 packages/file-format/LICENSE delete mode 100644 packages/file-format/README.md delete mode 100644 packages/file-format/api-extractor.json delete mode 100644 packages/file-format/api-report.md delete mode 100644 packages/file-format/package.json delete mode 100644 packages/file-format/src/index.ts delete mode 100644 packages/file-format/src/test/file.test.ts delete mode 100644 packages/file-format/tsconfig.json delete mode 100644 packages/indices/CHANGELOG.md delete mode 100644 packages/indices/LICENSE delete mode 100644 packages/indices/README.md delete mode 100644 packages/indices/api-extractor.json delete mode 100644 packages/indices/api-report.md delete mode 100644 packages/indices/package.json delete mode 100644 packages/indices/src/index.ts delete mode 100644 packages/indices/tsconfig.json delete mode 100644 packages/polyfills/CHANGELOG.md delete mode 100644 packages/polyfills/LICENSE delete mode 100644 packages/polyfills/README.md delete mode 100644 packages/polyfills/api-extractor.json delete mode 100644 packages/polyfills/api-report.md delete mode 100644 packages/polyfills/package.json delete mode 100644 packages/polyfills/src/index.ts delete mode 100644 packages/polyfills/src/lib/polyfills.ts delete mode 100644 packages/polyfills/tsconfig.json delete mode 100644 packages/primitives/CHANGELOG.md delete mode 100644 packages/primitives/LICENSE delete mode 100644 packages/primitives/README.md delete mode 100644 packages/primitives/api-extractor.json delete mode 100644 packages/primitives/api-report.md delete mode 100644 packages/primitives/package.json delete mode 100644 packages/primitives/src/index.ts delete mode 100644 packages/primitives/tsconfig.json rename packages/{editor/src/lib/components => tldraw/src/lib/canvas}/CropHandles.tsx (98%) rename packages/{editor/src/lib/components/DefaultScribble.tsx => tldraw/src/lib/canvas/TldrawScribble.tsx} (75%) rename packages/{editor/src/lib/components/SelectionFg.tsx => tldraw/src/lib/canvas/TldrawSelectionForeground.tsx} (97%) create mode 100644 packages/tldraw/src/lib/defaultShapeTools.ts create mode 100644 packages/tldraw/src/lib/defaultShapeUtils.ts create mode 100644 packages/tldraw/src/lib/defaultTools.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/arrow/ArrowShapeTool.test.ts (92%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/arrow/ArrowShapeTool.ts (52%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/arrow/ArrowShapeUtil.test.ts (94%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/arrow/ArrowShapeUtil.tsx (94%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/arrow/components/ArrowTextLabel.tsx (89%) create mode 100644 packages/tldraw/src/lib/shapes/arrow/toolStates/Idle.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/arrow/toolStates/Pointing.ts (86%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/bookmark/BookmarkShapeUtil.tsx (87%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/draw/DrawShapeTool.test.ts (89%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/draw/DrawShapeTool.ts (59%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/draw/DrawShapeUtil.tsx (83%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/draw/getPath.ts (93%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/draw/toolStates/Drawing.ts (96%) create mode 100644 packages/tldraw/src/lib/shapes/draw/toolStates/Idle.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/embed/EmbedShapeUtil.tsx (88%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/frame/FrameShapeTool.test.ts (86%) create mode 100644 packages/tldraw/src/lib/shapes/frame/FrameShapeTool.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/frame/FrameShapeUtil.tsx (92%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/frame/components/FrameHeading.tsx (89%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/frame/components/FrameLabelInput.tsx (89%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/GeoShapeTool.test.ts (87%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/GeoShapeTool.ts (51%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/GeoShapeUtil.tsx (95%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/cloudOutline.ts (97%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/DashStyleCloud.tsx (95%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/DashStyleEllipse.tsx (94%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/DashStyleOval.tsx (94%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/DashStylePolygon.tsx (97%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/DrawStyleCloud.tsx (99%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/DrawStyleEllipse.tsx (87%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/DrawStylePolygon.tsx (97%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/SolidStyleCloud.tsx (99%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/SolidStyleEllipse.tsx (96%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/SolidStyleOval.tsx (96%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/components/SolidStylePolygon.tsx (93%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/helpers.ts (92%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/toolStates/Idle.ts (51%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/geo/toolStates/Pointing.ts (83%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/highlight/HighlightShapeTool.test.ts (100%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/highlight/HighlightShapeTool.ts (62%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/highlight/HighlightShapeUtil.tsx (86%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/image/ImageShapeUtil.tsx (89%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/line/LineShapeTool.test.ts (85%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/line/LineShapeTool.ts (51%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/line/LineShapeUtil.test.ts (97%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/line/LineShapeUtil.tsx (89%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/line/__snapshots__/LineShapeUtil.test.ts.snap (96%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/line/components/getLinePath.ts (81%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/line/components/getLineSvg.ts (92%) create mode 100644 packages/tldraw/src/lib/shapes/line/toolStates/Idle.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/line/toolStates/Pointing.ts (85%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/note/NoteShapeTool.test.ts (87%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/note/NoteShapeTool.ts (51%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/note/NoteShapeUtil.tsx (85%) create mode 100644 packages/tldraw/src/lib/shapes/note/toolStates/Idle.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/note/toolStates/Pointing.ts (71%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/HyperlinkButton.tsx (94%) rename packages/{editor/src/lib/editor/managers => tldraw/src/lib/shapes/shared}/ScribbleManager.ts (95%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/ShapeFill.tsx (93%) rename packages/{editor/src/lib/editor/shapes/text => tldraw/src/lib/shapes/shared}/TextHelpers.ts (100%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/TextLabel.tsx (93%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/createTextSvgElementFromSpans.ts (94%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/default-shape-constants.ts (99%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/defaultStyleDefs.tsx (95%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared}/freehand/getStroke.ts (93%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared}/freehand/getStrokeOutlinePoints.ts (99%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared}/freehand/getStrokePoints.ts (99%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared}/freehand/getStrokeRadius.ts (100%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared}/freehand/setStrokePointRadii.ts (98%) create mode 100644 packages/tldraw/src/lib/shapes/shared/freehand/svg.ts rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared}/freehand/types.ts (96%) rename packages/{editor/src/lib/utils => tldraw/src/lib/shapes/shared}/getBrowserCanvasMaxSize.tsx (100%) create mode 100644 packages/tldraw/src/lib/shapes/shared/getPerfectDashProps.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/getTextLabelSvgElement.ts (83%) rename packages/{editor/src/lib/utils/legacy.ts => tldraw/src/lib/shapes/shared/legacyProps.ts} (87%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared}/polygon-helpers.ts (97%) create mode 100644 packages/tldraw/src/lib/shapes/shared/resizeBox.ts create mode 100644 packages/tldraw/src/lib/shapes/shared/resizeScaled.ts rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared/splines}/BaseSegment2d.ts (98%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared/splines}/BaseSpline2d.ts (97%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared/splines}/CubicSegment2d.test.ts (100%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared/splines}/CubicSegment2d.ts (97%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared/splines}/CubicSpline2d.ts (95%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared/splines}/LineSegment2d.ts (96%) rename packages/{primitives/src/lib => tldraw/src/lib/shapes/shared/splines}/Polyline2d.ts (94%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/useColorSpace.tsx (86%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/useEditableText.ts (92%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/shared/useForceSolid.ts (58%) create mode 100644 packages/tldraw/src/lib/shapes/shared/usePrefersReducedMotion.tsx rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/text/TextShapeTool.test.ts (90%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/text/TextShapeTool.ts (51%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/text/TextShapeUtil.tsx (89%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/text/toolStates/Idle.ts (60%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/text/toolStates/Pointing.ts (71%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/shapes/video/VideoShapeUtil.tsx (87%) create mode 100644 packages/tldraw/src/lib/tools/EraserTool/EraserTool.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/EraserTool/children/Erasing.ts (85%) create mode 100644 packages/tldraw/src/lib/tools/EraserTool/children/Idle.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/EraserTool/children/Pointing.ts (79%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/HandTool/HandTool.ts (72%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/HandTool/children/Dragging.ts (59%) create mode 100644 packages/tldraw/src/lib/tools/HandTool/children/Idle.ts create mode 100644 packages/tldraw/src/lib/tools/HandTool/children/Pointing.ts create mode 100644 packages/tldraw/src/lib/tools/LaserTool/LaserTool.ts create mode 100644 packages/tldraw/src/lib/tools/LaserTool/children/Idle.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/LaserTool/children/Lasering.ts (80%) rename packages/{editor/src/lib/editor/managers => tldraw/src/lib/tools/SelectTool}/DragAndDropManager.ts (92%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/SelectTool.ts (88%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Brushing.ts (85%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Crop/Crop.ts (60%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Crop/children/Idle.ts (69%) create mode 100644 packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/PointingCrop.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Crop/children/TranslatingCrop.ts (69%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Crop/children/crop_helpers.ts (87%) create mode 100644 packages/tldraw/src/lib/tools/SelectTool/children/Crop/crop-constants.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Cropping.ts (87%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/DraggingHandle.ts (85%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/EditingShape.ts (74%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Idle.ts (90%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/PointingCanvas.ts (74%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/PointingCropHandle.ts (67%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/PointingHandle.ts (57%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/PointingResizeHandle.ts (82%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/PointingRotateHandle.ts (74%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/PointingSelection.ts (67%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/PointingShape.ts (86%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Resizing.ts (91%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Rotating.ts (86%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/ScribbleBrushing.ts (86%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/SelectTool/children/Translating.ts (88%) create mode 100644 packages/tldraw/src/lib/tools/ZoomTool/ZoomTool.ts create mode 100644 packages/tldraw/src/lib/tools/ZoomTool/children/Idle.ts rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/ZoomTool/children/Pointing.ts (65%) rename packages/{editor/src/lib/editor => tldraw/src/lib}/tools/ZoomTool/children/ZoomBrushing.ts (72%) rename packages/{ui => tldraw/src/lib}/ui.css (99%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/TldrawUi.tsx (98%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/TldrawUiContextProvider.tsx (98%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/assetUrls.ts (89%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/ActionsMenu.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/BackToContent.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/ContextMenu.tsx (98%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/DebugPanel.tsx (98%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/Dialogs.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/DuplicateButton.tsx (89%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/EditLinkDialog.tsx (97%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/EmbedDialog.tsx (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/FollowingIndicator.tsx (83%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/HTMLCanvas.tsx (93%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/HelpMenu.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/KeyboardShortcutsDialog.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/LanguageMenu.tsx (94%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/Menu.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/MenuZone.tsx (92%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/MobileStylePanel.tsx (92%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/MoveToPageMenu.tsx (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/NavigationZone/Minimap.tsx (91%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/NavigationZone/MinimapManager.ts (98%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/NavigationZone/NavigationZone.tsx (97%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/NavigationZone/ZoomMenu.tsx (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/PageMenu/PageItemInput.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/PageMenu/PageItemSubmenu.tsx (95%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/PageMenu/PageMenu.tsx (97%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/PageMenu/edit-pages-shared.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/PenModeToggle.tsx (84%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/RedoButton.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/Spinner.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/StopFollowing.tsx (84%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/StylePanel/DoubleDropdownPicker.tsx (97%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/StylePanel/DropdownPicker.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/StylePanel/StylePanel.tsx (99%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/StylePanel/styles.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/Toasts.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/Toolbar/ToggleToolLockedButton.tsx (92%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/Toolbar/Toolbar.tsx (98%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/TrashButton.tsx (90%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/UndoButton.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/Button.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/ButtonPicker.tsx (98%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/Dialog.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/DropdownMenu.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/Icon.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/Input.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/Kbd.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/Popover.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/Slider.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/components/primitives/shared.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/constants.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/clipboard/pasteExcalidrawContent.ts (99%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/clipboard/pasteFiles.ts (89%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/clipboard/pasteTldrawContent.ts (84%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/clipboard/pasteUrl.ts (90%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/menuHelpers.ts (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useActions.tsx (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useActionsMenuSchema.tsx (97%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useAssetUrls.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useBreakpoint.tsx (92%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useCanRedo.ts (62%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useCanUndo.ts (62%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useClipboardEvents.ts (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useContextMenuSchema.tsx (85%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useCopyAs.ts (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useDialogsProvider.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useEditorEvents.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useEditorIsFocused.ts (62%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useEventsProvider.tsx (99%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useExportAs.ts (95%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useHasLinkShapeSelected.ts (79%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useHelpMenuSchema.tsx (94%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useHighDpiCanvas.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useInsertMedia.ts (86%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useKeyboardShortcuts.ts (91%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useKeyboardShortcutsSchema.tsx (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useLocalStorageState.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useMenuIsOpen.ts (95%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useMenuSchema.tsx (98%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useOnlyFlippableShape.ts (74%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/usePreloadIcons.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/usePrint.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useReadonly.ts (63%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useShowAutoSizeToggle.ts (78%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useToastsProvider.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useToolbarSchema.tsx (94%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useTools.tsx (90%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useTranslation/TLUiTranslationKey.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useTranslation/defaultTranslation.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useTranslation/translations.ts (97%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useTranslation/useLanguages.tsx (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/hooks/useTranslation/useTranslation.tsx (96%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/icon-types.ts (100%) rename packages/{ui/src/lib => tldraw/src/lib/ui}/overrides.ts (98%) rename packages/{ui/src => tldraw/src/lib/ui}/version.ts (100%) create mode 100644 packages/tldraw/src/lib/useRegisterExternalContentHandlers.ts rename packages/{editor/src/lib => tldraw/src/lib/utils}/assetUrls.ts (89%) create mode 100644 packages/tldraw/src/lib/utils/assets.ts create mode 100644 packages/tldraw/src/lib/utils/blobAsString.ts rename packages/{file-format/src/lib => tldraw/src/lib/utils}/buildFromV1Document.ts (99%) rename packages/{editor => tldraw}/src/lib/utils/embeds.test.ts (100%) rename packages/{editor => tldraw}/src/lib/utils/embeds.ts (76%) rename packages/{editor => tldraw}/src/lib/utils/export.ts (95%) rename packages/{file-format/src/lib => tldraw/src/lib/utils}/file.ts (85%) rename packages/{editor => tldraw}/src/lib/utils/is-gif-animated.ts (100%) create mode 100644 packages/tldraw/src/lib/utils/rotated-box-shadow.ts create mode 100644 packages/tldraw/src/lib/utils/text.ts rename packages/{editor/src/lib/hooks => tldraw/src/lib/utils}/usePreloadAssets.ts (98%) rename packages/{editor/src/lib/editor/managers => tldraw/src/test}/ClickManager.test.ts (98%) rename packages/{editor/src/lib => tldraw/src}/test/Editor.test.tsx (92%) rename packages/{editor/src/lib/editor/tools/EraserTool => tldraw/src/test}/EraserTool.test.ts (94%) rename packages/{editor/src/lib/editor/tools/HandTool => tldraw/src/test}/HandTool.test.ts (87%) rename packages/{editor/src/lib/editor/tools/LaserTool => tldraw/src/test}/LaserTool.test.ts (100%) rename packages/{editor/src/lib/editor/tools/SelectTool => tldraw/src/test}/SelectTool.test.ts (98%) rename packages/{editor/src/lib/config => tldraw/src/test}/TLSessionStateSnapshot.test.ts (95%) rename packages/{editor/src/lib/config => tldraw/src/test}/TLUserPreferences.test.ts (82%) rename packages/{editor/src/lib => tldraw/src}/test/TestEditor.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/TestEditor.ts (94%) rename packages/{editor/src/lib => tldraw/src}/test/TldrawEditor.test.tsx (71%) rename packages/{editor/src/lib/editor/tools/ZoomTool => tldraw/src/test}/ZoomTool.test.ts (98%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/__snapshots__/groups.test.ts.snap (100%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/__snapshots__/resizing.test.ts.snap (100%) rename packages/{editor/src/lib/editor/derivations => tldraw/src/test}/arrowBindingsIndex.test.tsx (92%) rename packages/{editor/src/lib/utils => tldraw/src/test}/assets.test.ts (95%) rename packages/{editor/src/lib/test/tools/bookmark-shapes.ts => tldraw/src/test/bookmark-shapes.test.ts} (94%) rename packages/{editor/src/lib => tldraw/src}/test/commands/__snapshots__/getSvg.test.ts.snap (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/__snapshots__/packShapes.test.ts.snap (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/__snapshots__/zoomToFit.test.ts.snap (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/alignShapes.test.tsx (98%) rename packages/{editor/src/lib => tldraw/src}/test/commands/allShapesCommonBounds.test.ts (93%) rename packages/{editor/src/lib => tldraw/src}/test/commands/animateShapes.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/animateToShape.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/animationSpeed.test.ts (90%) rename packages/{editor/src/lib => tldraw/src}/test/commands/blur.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/cancel.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/centerOnPoint.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/clipboard.test.ts (99%) rename packages/{editor/src/lib => tldraw/src}/test/commands/complete.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/createPage.test.ts (93%) rename packages/{editor/src/lib => tldraw/src}/test/commands/createShapes.test.ts (99%) rename packages/{editor/src/lib => tldraw/src}/test/commands/deletePage.test.ts (97%) rename packages/{editor/src/lib => tldraw/src}/test/commands/deleteShapes.test.ts (98%) rename packages/{editor/src/lib => tldraw/src}/test/commands/distributeShapes.test.ts (98%) rename packages/{editor/src/lib => tldraw/src}/test/commands/duplicatePage.test.ts (96%) rename packages/{editor/src/lib => tldraw/src}/test/commands/getContent.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/getInitialMetaForShape.test.ts (92%) rename packages/{editor/src/lib => tldraw/src}/test/commands/getSvg.test.ts (95%) rename packages/{editor/src/lib => tldraw/src}/test/commands/groupShapes.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/guide.md (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/interrupt.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/lockShapes.test.ts (98%) rename packages/{editor/src/lib => tldraw/src}/test/commands/moveShapesToPage.test.ts (97%) rename packages/{editor/src/lib => tldraw/src}/test/commands/nudge.test.ts (98%) rename packages/{editor/src/lib => tldraw/src}/test/commands/packShapes.test.ts (96%) rename packages/{editor/src/lib => tldraw/src}/test/commands/pageToScreen.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/pan.test.ts (94%) rename packages/{editor/src/lib/test => tldraw/src/test/commands}/penmode.test.ts (73%) rename packages/{editor/src/lib => tldraw/src}/test/commands/pinch.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/putContent.test.ts (98%) rename packages/{editor/src/lib => tldraw/src}/test/commands/reorderShapes.test.ts (99%) rename packages/{editor/src/lib => tldraw/src}/test/commands/reparentShapesById.test.ts (97%) rename packages/{editor/src/lib => tldraw/src}/test/commands/resetZoom.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/resizeShape.test.ts (96%) rename packages/{editor/src/lib => tldraw/src}/test/commands/rotateShapes.test.ts (96%) rename packages/{editor/src/lib => tldraw/src}/test/commands/screenToPage.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/setAppState.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/setBrush.test.ts (90%) rename packages/{editor/src/lib => tldraw/src}/test/commands/setCurrentPage.test.ts (99%) rename packages/{editor/src/lib => tldraw/src}/test/commands/setPageState.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/setSelectedIds.test.ts (93%) rename packages/{editor/src/lib => tldraw/src}/test/commands/setSelectedTool.test.ts (83%) rename packages/{editor/src/lib => tldraw/src}/test/commands/setSettings.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/setStyle.test.ts (100%) create mode 100644 packages/tldraw/src/test/commands/squash.test.ts rename packages/{editor/src/lib => tldraw/src}/test/commands/stackShapes.test.ts (98%) rename packages/{editor/src/lib => tldraw/src}/test/commands/stretch.test.tsx (96%) rename packages/{editor/src/lib => tldraw/src}/test/commands/ungroup.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/updateShapes.test.ts (95%) rename packages/{editor/src/lib => tldraw/src}/test/commands/updateViewportPageBounds.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/zoomIn.test.ts (96%) rename packages/{editor/src/lib => tldraw/src}/test/commands/zoomOut.test.ts (95%) rename packages/{editor/src/lib => tldraw/src}/test/commands/zoomToBounds.test.ts (100%) rename packages/{editor/src/lib => tldraw/src}/test/commands/zoomToFit.test.ts (87%) rename packages/{editor/src/lib => tldraw/src}/test/commands/zoomToSelection.test.ts (90%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/cropping.test.ts (99%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/drawing.test.ts (87%) rename packages/{editor/src/lib => tldraw/src}/test/duplicate.test.ts (98%) rename packages/{editor/src/lib/test/commands => tldraw/src/test}/flipShapes.test.ts (99%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/frames.test.ts (90%) rename packages/{editor/src/lib/test/testutils => tldraw/src/test}/getSnapLines.ts (92%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/groups.test.ts (97%) rename packages/{editor/src/lib => tldraw/src}/test/middleMouseButtonPanning.test.ts (93%) rename packages/{editor/src/lib => tldraw/src}/test/modifiers.test.ts (100%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/panning.test.ts (92%) rename packages/{editor/src/lib/editor/derivations => tldraw/src/test}/parentsToChildrenWithIndexes.test.ts (94%) rename packages/{editor/src/lib/test/commands => tldraw/src/test}/paste.test.ts (98%) rename packages/{editor/src/lib/test/commands => tldraw/src/test}/renderingShapes.test.tsx (97%) rename packages/{editor/src/lib => tldraw/src}/test/resizeBox.test.ts (91%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/resizing.test.ts (99%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/rotating.test.ts (98%) rename packages/{editor/src/lib/test/testutils => tldraw/src/test}/roundedBox.ts (91%) rename packages/{editor/src/lib/test/tools => tldraw/src/test}/select.test.tsx (92%) rename packages/{editor/src/lib/editor/derivations => tldraw/src/test}/shapeIdsInCurrentPage.test.ts (94%) rename packages/{editor/src/lib => tldraw/src}/test/shapeutils.test.ts (98%) rename packages/{editor/src/lib => tldraw/src}/test/spacebarPanning.test.ts (96%) rename packages/{editor/src/lib/test/styles.test.tsx => tldraw/src/test/styles2.test.tsx} (97%) rename packages/{editor/src/lib/test/tools/styles.test.ts => tldraw/src/test/styles3.test.ts} (91%) rename packages/{editor/src/lib/test/jsx.tsx => tldraw/src/test/test-jsx.tsx} (93%) create mode 100644 packages/tldraw/src/test/testutils/getSnapLines.ts create mode 100644 packages/tldraw/src/test/testutils/roundedBox.ts rename packages/{editor/src/lib/test/tools => tldraw/src/test}/text.test.ts (98%) create mode 100644 packages/tldraw/src/test/translating-snapping.test.ts rename packages/{editor/src/lib/test/tools => tldraw/src/test}/translating.test.ts (85%) delete mode 100644 packages/ui/CHANGELOG.md delete mode 100644 packages/ui/README.md delete mode 100644 packages/ui/api-extractor.json delete mode 100644 packages/ui/api-report.md delete mode 100644 packages/ui/package.json delete mode 100644 packages/ui/setupTests.js delete mode 100644 packages/ui/src/index.ts delete mode 100644 packages/ui/tsconfig.json create mode 100644 packages/utils/src/lib/url.ts diff --git a/.eslintrc.js b/.eslintrc.js index d11890109..c0d260acd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -76,5 +76,11 @@ module.exports = { 'import/no-internal-modules': 'off', }, }, + // { + // files: ['packages/tldraw/src/test/**/*'], + // rules: { + // 'import/no-internal-modules': 'off', + // }, + // }, ], } diff --git a/README.md b/README.md index 4e6da79ee..48be447bc 100644 --- a/README.md +++ b/README.md @@ -82,15 +82,10 @@ This repository's contents is divided across four primary sections: - `assets`: a library for working with tldraw's fonts and translations - `editor`: the tldraw editor -- `file-format`: a library for working with tldraw's `.tldr` file format -- `indices`: a library for working with tldraw's indices -- `polyfills`: a collection of polyfills used by tldraw -- `primitives`: low-level primitives for working with vectors and geometry - `state`: a signals library, also known as signia - `store`: an in-memory reactive database - `tldraw`: the main tldraw package containing both the editor and the UI - `tlschema`: shape definitions and migrations -- `ui`: the editor's user interface - `utils`: low-level data utilities shared by other libraries - `validate`: a validation library used for run-time validation diff --git a/apps/examples/e2e/shared-e2e.ts b/apps/examples/e2e/shared-e2e.ts index 515584285..da70b03ed 100644 --- a/apps/examples/e2e/shared-e2e.ts +++ b/apps/examples/e2e/shared-e2e.ts @@ -36,7 +36,7 @@ export async function setupPage(page: PlaywrightTestArgs['page']) { await page.goto('http://localhost:5420/end-to-end') await page.waitForSelector('.tl-canvas') await page.evaluate(() => { - editor.setAnimationSpeed(0) + editor.animationSpeed = 0 }) } diff --git a/apps/examples/e2e/tests/export-snapshots.spec.ts b/apps/examples/e2e/tests/export-snapshots.spec.ts index 8547a97fd..7a9a08e97 100644 --- a/apps/examples/e2e/tests/export-snapshots.spec.ts +++ b/apps/examples/e2e/tests/export-snapshots.spec.ts @@ -1,20 +1,11 @@ -import test, { Page, expect } from '@playwright/test' +import test, { expect } from '@playwright/test' import { Editor, TLShapeId, TLShapePartial } from '@tldraw/tldraw' -import { assert } from '@tldraw/utils' import { rename, writeFile } from 'fs/promises' import { setupPage } from '../shared-e2e' -let page: Page declare const editor: Editor test.describe('Export snapshots', () => { - test.beforeAll(async ({ browser }) => { - page = await browser.newPage() - }) - test.beforeEach(async () => { - await setupPage(page) - }) - const snapshots = {} as Record for (const fill of ['none', 'semi', 'solid', 'pattern']) { @@ -172,7 +163,9 @@ test.describe('Export snapshots', () => { } for (const [name, shapes] of Object.entries(snapshots)) { - test(`Exports with ${name}`, async () => { + test(`Exports with ${name}`, async ({ browser }) => { + const page = await browser.newPage() + await setupPage(page) await page.evaluate((shapes) => { editor .updateInstanceState({ exportBackground: false }) @@ -188,17 +181,17 @@ test.describe('Export snapshots', () => { await page.click('[data-testid="menu-item.export-as-svg"]') const download = await downloadEvent - const path = await download.path() - assert(path) + const path = (await download.path()) as string + // assert(path) await rename(path, path + '.svg') await writeFile( path + '.html', ` - - - - - `, + + + + + `, 'utf-8' ) diff --git a/apps/examples/e2e/tests/test-kbds.spec.ts b/apps/examples/e2e/tests/test-kbds.spec.ts index 08d029ea2..92ca3e63d 100644 --- a/apps/examples/e2e/tests/test-kbds.spec.ts +++ b/apps/examples/e2e/tests/test-kbds.spec.ts @@ -327,3 +327,32 @@ test.describe('Keyboard Shortcuts', () => { }) }) }) + +test.describe('Delete bug', () => { + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + await setupPage(page) + }) + + test('delete bug without drag', async () => { + await page.keyboard.press('r') + await page.mouse.click(100, 100) + await page.keyboard.press('Backspace') + expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({ + name: 'delete-shapes', + data: { source: 'kbd' }, + }) + }) + + test('delete bug with drag', async () => { + await page.keyboard.press('r') + await page.mouse.move(100, 100) + await page.mouse.down() + await page.mouse.up() + await page.keyboard.press('Backspace') + expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({ + name: 'delete-shapes', + data: { source: 'kbd' }, + }) + }) +}) diff --git a/apps/examples/package.json b/apps/examples/package.json index 968e46adb..9ec588bb3 100644 --- a/apps/examples/package.json +++ b/apps/examples/package.json @@ -37,10 +37,7 @@ "@babel/plugin-proposal-decorators": "^7.21.0", "@playwright/test": "^1.35.1", "@tldraw/assets": "workspace:*", - "@tldraw/state": "workspace:*", "@tldraw/tldraw": "workspace:*", - "@tldraw/utils": "workspace:*", - "@tldraw/validate": "workspace:*", "@vercel/analytics": "^1.0.1", "lazyrepo": "0.0.0-alpha.27", "react": "^18.2.0", diff --git a/apps/examples/src/10-custom-components/CustomComponentsExample.tsx b/apps/examples/src/10-custom-components/CustomComponentsExample.tsx index 4af1f1b55..073ce4992 100644 --- a/apps/examples/src/10-custom-components/CustomComponentsExample.tsx +++ b/apps/examples/src/10-custom-components/CustomComponentsExample.tsx @@ -2,24 +2,30 @@ import { Tldraw, TLEditorComponents } from '@tldraw/tldraw' import '@tldraw/tldraw/tldraw.css' const components: Partial = { - Brush: ({ brush }) => ( - - ), + Brush: function MyBrush({ brush }) { + return ( + + + + ) + }, Scribble: ({ scribble, opacity, color }) => { return ( - `${p.x},${p.y}`).join(' ')} - stroke={color ?? 'black'} - opacity={opacity ?? '1'} - fill="none" - /> + + `${p.x},${p.y}`).join(' ')} + stroke={color ?? 'black'} + opacity={opacity ?? '1'} + fill="none" + /> + ) }, SnapLine: null, diff --git a/apps/examples/src/11-user-presence/UserPresenceExample.tsx b/apps/examples/src/11-user-presence/UserPresenceExample.tsx index 1cb29f327..6f3f237d8 100644 --- a/apps/examples/src/11-user-presence/UserPresenceExample.tsx +++ b/apps/examples/src/11-user-presence/UserPresenceExample.tsx @@ -8,6 +8,12 @@ const MOVING_CURSOR_SPEED = 0.25 // 0 is stopped, 1 is full send const MOVING_CURSOR_RADIUS = 100 const CURSOR_CHAT_MESSAGE = 'Hey, I think this is just great.' +// Note: +// Almost all of the information below is calculated automatically by helpers in the editor. +// For a more realistic implementation, see the yjs example in this examples folder. If anything, +// this example should be used to understand the data model and test designs, not as a reference +// for how to implement user presence. + export default function UserPresenceExample() { const rRaf = useRef(-1) return ( diff --git a/apps/examples/src/14-persistence/PersistenceExample.tsx b/apps/examples/src/14-persistence/PersistenceExample.tsx index fc7325435..9ce87df7b 100644 --- a/apps/examples/src/14-persistence/PersistenceExample.tsx +++ b/apps/examples/src/14-persistence/PersistenceExample.tsx @@ -1,12 +1,11 @@ -import { Tldraw, createTLStore, defaultShapes } from '@tldraw/tldraw' +import { Tldraw, createTLStore, defaultShapeUtils, throttle } from '@tldraw/tldraw' import '@tldraw/tldraw/tldraw.css' -import { throttle } from '@tldraw/utils' import { useLayoutEffect, useState } from 'react' const PERSISTENCE_KEY = 'example-3' export default function PersistenceExample() { - const [store] = useState(() => createTLStore({ shapes: defaultShapes })) + const [store] = useState(() => createTLStore({ shapeUtils: defaultShapeUtils })) const [loadingState, setLoadingState] = useState< { status: 'loading' } | { status: 'ready' } | { status: 'error'; error: string } >({ diff --git a/apps/examples/src/16-custom-styles/CardShape.tsx b/apps/examples/src/16-custom-styles/CardShape.tsx index f7557b357..73092fbad 100644 --- a/apps/examples/src/16-custom-styles/CardShape.tsx +++ b/apps/examples/src/16-custom-styles/CardShape.tsx @@ -4,12 +4,11 @@ import { DefaultColorStyle, HTMLContainer, StyleProp, + T, TLBaseShape, TLDefaultColorStyle, - defineShape, getDefaultColorTheme, } from '@tldraw/tldraw' -import { T } from '@tldraw/validate' // Define a style that can be used across multiple shapes. // The ID (myApp:filter) must be globally unique, so we recommend prefixing it with a namespace. @@ -33,6 +32,15 @@ export type CardShape = TLBaseShape< export class CardShapeUtil extends BaseBoxShapeUtil { static override type = 'card' as const + static override props = { + w: T.number, + h: T.number, + // You can re-use tldraw built-in styles... + color: DefaultColorStyle, + // ...or your own custom styles. + filter: MyFilterStyle, + } + override isAspectRatioLocked = (_shape: CardShape) => false override canResize = (_shape: CardShape) => true override canBind = (_shape: CardShape) => true @@ -87,17 +95,12 @@ export class CardShapeTool extends BaseBoxShapeTool { static override id = 'card' static override initial = 'idle' override shapeType = 'card' -} - -export const CardShape = defineShape('card', { - util: CardShapeUtil, - // to use a style prop, you need to describe all the props in your shape. - props: { + props = { w: T.number, h: T.number, // You can re-use tldraw built-in styles... color: DefaultColorStyle, // ...or your own custom styles. filter: MyFilterStyle, - }, -}) + } +} diff --git a/apps/examples/src/16-custom-styles/CustomStylesExample.tsx b/apps/examples/src/16-custom-styles/CustomStylesExample.tsx index 1be89d15c..1753d4bd4 100644 --- a/apps/examples/src/16-custom-styles/CustomStylesExample.tsx +++ b/apps/examples/src/16-custom-styles/CustomStylesExample.tsx @@ -1,10 +1,11 @@ import { Tldraw } from '@tldraw/tldraw' import '@tldraw/tldraw/tldraw.css' -import { CardShape, CardShapeTool } from './CardShape' +import { CardShapeTool, CardShapeUtil } from './CardShape' import { FilterStyleUi } from './FilterStyleUi' import { uiOverrides } from './ui-overrides' -const shapes = [CardShape] +const customShapeUtils = [CardShapeUtil] +const customTools = [CardShapeTool] export default function CustomStylesExample() { return ( @@ -12,8 +13,8 @@ export default function CustomStylesExample() { diff --git a/apps/examples/src/16-custom-styles/FilterStyleUi.tsx b/apps/examples/src/16-custom-styles/FilterStyleUi.tsx index 4fe28d3a0..1dacd8b22 100644 --- a/apps/examples/src/16-custom-styles/FilterStyleUi.tsx +++ b/apps/examples/src/16-custom-styles/FilterStyleUi.tsx @@ -1,5 +1,4 @@ -import { track } from '@tldraw/state' -import { useEditor } from '@tldraw/tldraw' +import { track, useEditor } from '@tldraw/tldraw' import { MyFilterStyle } from './CardShape' export const FilterStyleUi = track(function FilterStyleUi() { diff --git a/apps/examples/src/16-custom-styles/ui-overrides.ts b/apps/examples/src/16-custom-styles/ui-overrides.ts index ec1efd780..4dc56398b 100644 --- a/apps/examples/src/16-custom-styles/ui-overrides.ts +++ b/apps/examples/src/16-custom-styles/ui-overrides.ts @@ -9,7 +9,7 @@ export const uiOverrides: TLUiOverrides = { kbd: 'c', readonlyOk: false, onSelect: () => { - editor.setSelectedTool('card') + editor.setCurrentTool('card') }, } return tools diff --git a/apps/examples/src/3-custom-config/CardShape/CardShape.ts b/apps/examples/src/3-custom-config/CardShape/CardShape.ts deleted file mode 100644 index 54258db78..000000000 --- a/apps/examples/src/3-custom-config/CardShape/CardShape.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { defineShape } from '@tldraw/tldraw' -import { CardShapeUtil } from './CardShapeUtil' -import { cardShapeMigrations } from './card-shape-migrations' -import { cardShapeProps } from './card-shape-props' - -// A custom shape is a bundle of a shape util, a tool, and props -export const CardShape = defineShape('card', { - // A utility class - util: CardShapeUtil, - // A tool that is used to create and edit the shape (optional) - // A validation schema for the shape's props (optional) - props: cardShapeProps, - // Migrations for upgrading shapes (optional) - migrations: cardShapeMigrations, -}) diff --git a/apps/examples/src/3-custom-config/CardShape/CardShapeUtil.tsx b/apps/examples/src/3-custom-config/CardShape/CardShapeUtil.tsx index a9989085a..a2cf4f819 100644 --- a/apps/examples/src/3-custom-config/CardShape/CardShapeUtil.tsx +++ b/apps/examples/src/3-custom-config/CardShape/CardShapeUtil.tsx @@ -7,6 +7,8 @@ import { resizeBox, } from '@tldraw/tldraw' import { useState } from 'react' +import { cardShapeMigrations } from './card-shape-migrations' +import { cardShapeProps } from './card-shape-props' import { ICardShape } from './card-shape-types' // A utility class for the card shape. This is where you define @@ -15,6 +17,10 @@ import { ICardShape } from './card-shape-types' export class CardShapeUtil extends ShapeUtil { static override type = 'card' as const + // A validation schema for the shape's props (optional) + static override props = cardShapeProps + // Migrations for upgrading shapes (optional) + static override migrations = cardShapeMigrations // Flags override isAspectRatioLocked = (_shape: ICardShape) => false diff --git a/apps/examples/src/3-custom-config/CardShape/card-shape-props.ts b/apps/examples/src/3-custom-config/CardShape/card-shape-props.ts index e472a27fd..776b67d1a 100644 --- a/apps/examples/src/3-custom-config/CardShape/card-shape-props.ts +++ b/apps/examples/src/3-custom-config/CardShape/card-shape-props.ts @@ -1,5 +1,4 @@ -import { DefaultColorStyle, ShapeProps, StyleProp } from '@tldraw/tldraw' -import { T } from '@tldraw/validate' +import { DefaultColorStyle, ShapeProps, StyleProp, T } from '@tldraw/tldraw' import { ICardShape } from './card-shape-types' export const WeightStyle = StyleProp.defineEnum('myApp:weight', { diff --git a/apps/examples/src/3-custom-config/CustomConfigExample.tsx b/apps/examples/src/3-custom-config/CustomConfigExample.tsx index ac3665db3..0c9347f90 100644 --- a/apps/examples/src/3-custom-config/CustomConfigExample.tsx +++ b/apps/examples/src/3-custom-config/CustomConfigExample.tsx @@ -1,18 +1,21 @@ import { Tldraw } from '@tldraw/tldraw' import '@tldraw/tldraw/tldraw.css' import { CardShapeTool } from './CardShape/CardShapeTool' -import { customShapes } from './custom-shapes' +import { CardShapeUtil } from './CardShape/CardShapeUtil' import { uiOverrides } from './ui-overrides' +const customShapeUtils = [CardShapeUtil] +const customTools = [CardShapeTool] + export default function CustomConfigExample() { return (
diff --git a/apps/examples/src/3-custom-config/custom-shapes.ts b/apps/examples/src/3-custom-config/custom-shapes.ts deleted file mode 100644 index 36f91797f..000000000 --- a/apps/examples/src/3-custom-config/custom-shapes.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { CardShape } from './CardShape/CardShape' - -export const customShapes = [CardShape] diff --git a/apps/examples/src/3-custom-config/ui-overrides.ts b/apps/examples/src/3-custom-config/ui-overrides.ts index 6e9f5cde6..8f2181b29 100644 --- a/apps/examples/src/3-custom-config/ui-overrides.ts +++ b/apps/examples/src/3-custom-config/ui-overrides.ts @@ -12,7 +12,7 @@ export const uiOverrides: TLUiOverrides = { kbd: 'c', readonlyOk: false, onSelect: () => { - editor.setSelectedTool('card') + editor.setCurrentTool('card') }, } return tools diff --git a/apps/examples/src/4-custom-ui/CustomUiExample.tsx b/apps/examples/src/4-custom-ui/CustomUiExample.tsx index 7bd248812..7280537f3 100644 --- a/apps/examples/src/4-custom-ui/CustomUiExample.tsx +++ b/apps/examples/src/4-custom-ui/CustomUiExample.tsx @@ -1,5 +1,4 @@ -import { track } from '@tldraw/state' -import { Canvas, TldrawEditor, defaultShapes, defaultTools, useEditor } from '@tldraw/tldraw' +import { Canvas, Tldraw, track, useEditor } from '@tldraw/tldraw' import '@tldraw/tldraw/tldraw.css' import { useEffect } from 'react' import './custom-ui.css' @@ -7,10 +6,10 @@ import './custom-ui.css' export default function CustomUiExample() { return (
- + - +
) } @@ -24,6 +23,22 @@ const CustomUi = track(() => { case 'Delete': case 'Backspace': { editor.deleteShapes() + break + } + case 'v': { + editor.setCurrentTool('select') + break + } + case 'e': { + editor.setCurrentTool('eraser') + break + } + case 'x': + case 'p': + case 'b': + case 'd': { + editor.setCurrentTool('draw') + break } } } @@ -40,21 +55,21 @@ const CustomUi = track(() => { diff --git a/apps/examples/src/5-exploded/ExplodedExample.tsx b/apps/examples/src/5-exploded/ExplodedExample.tsx index b7602478b..8f58e1717 100644 --- a/apps/examples/src/5-exploded/ExplodedExample.tsx +++ b/apps/examples/src/5-exploded/ExplodedExample.tsx @@ -3,7 +3,7 @@ import { ContextMenu, TldrawEditor, TldrawUi, - defaultShapes, + defaultShapeUtils, defaultTools, } from '@tldraw/tldraw' import '@tldraw/tldraw/tldraw.css' @@ -12,7 +12,8 @@ export default function ExplodedExample() { return (
Shape error! {String(error)}
, // use a custom error fallback for shapes diff --git a/apps/examples/src/8-error-boundary/ErrorShape.ts b/apps/examples/src/8-error-boundary/ErrorShape.ts index 0e2929d6e..684238b21 100644 --- a/apps/examples/src/8-error-boundary/ErrorShape.ts +++ b/apps/examples/src/8-error-boundary/ErrorShape.ts @@ -1,10 +1,9 @@ -import { BaseBoxShapeUtil, TLBaseShape, defineShape } from '@tldraw/tldraw' +import { BaseBoxShapeUtil, TLBaseShape } from '@tldraw/tldraw' export type ErrorShape = TLBaseShape<'error', { w: number; h: number; message: string }> export class ErrorShapeUtil extends BaseBoxShapeUtil { static override type = 'error' as const - override type = 'error' as const getDefaultProps() { return { message: 'Error!', w: 100, h: 100 } @@ -16,5 +15,3 @@ export class ErrorShapeUtil extends BaseBoxShapeUtil { throw new Error(`Error shape indicator!`) } } - -export const ErrorShape = defineShape('error', { util: ErrorShapeUtil }) diff --git a/apps/examples/src/index.tsx b/apps/examples/src/index.tsx index f0983cbd5..618edb0a5 100644 --- a/apps/examples/src/index.tsx +++ b/apps/examples/src/index.tsx @@ -1,10 +1,6 @@ import { getAssetUrlsByMetaUrl } from '@tldraw/assets/urls' -import { - DefaultErrorFallback, - ErrorBoundary, - setDefaultEditorAssetUrls, - setDefaultUiAssetUrls, -} from '@tldraw/tldraw' +import { DefaultErrorFallback, ErrorBoundary, setDefaultUiAssetUrls } from '@tldraw/tldraw' +import { setDefaultEditorAssetUrls } from '@tldraw/tldraw/src/lib/utils/assetUrls' import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { RouterProvider, createBrowserRouter } from 'react-router-dom' @@ -28,6 +24,7 @@ import HideUiExample from './9-hide-ui/HideUiExample' import ExamplesTldrawLogo from './ExamplesTldrawLogo' import { ListLink } from './components/ListLink' import EndToEnd from './end-to-end/end-to-end' +import OnlyEditorExample from './only-editor/OnlyEditor' import YjsExample from './yjs/YjsExample' // This example is only used for end to end tests @@ -50,6 +47,11 @@ export const allExamples: Example[] = [ path: '/develop', element: , }, + { + title: 'Collaboration (with Yjs)', + path: '/yjs', + element: , + }, { title: 'Editor API', path: '/api', @@ -120,11 +122,6 @@ export const allExamples: Example[] = [ path: '/persistence', element: , }, - { - title: 'Custom styles', - path: '/yjs', - element: , - }, { title: 'Custom styles', path: '/custom-styles', @@ -135,6 +132,11 @@ export const allExamples: Example[] = [ path: '/shape-meta', element: , }, + { + title: 'Only editor', + path: '/only-editor', + element: , + }, // not listed { path: '/end-to-end', diff --git a/apps/examples/src/only-editor/MicroSelectTool.ts b/apps/examples/src/only-editor/MicroSelectTool.ts new file mode 100644 index 000000000..446804544 --- /dev/null +++ b/apps/examples/src/only-editor/MicroSelectTool.ts @@ -0,0 +1,60 @@ +import { StateNode, TLEventHandlers, createShapeId } from '@tldraw/tldraw' + +/* +This is a very small example of a state node that implements a "select" tool. + +The state handles two events: onPointerDown and onDoubleClick. + +When the user points down on the canvas, it deselects all shapes; and when +they point a shape it selects that shape. When the user double clicks on the +canvas, it creates a new shape; and when they double click on a shape, it +deletes that shape. +*/ + +export class MicroSelectTool extends StateNode { + static override id = 'select' + + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + const { editor } = this + + switch (info.target) { + case 'canvas': { + editor.selectNone() + break + } + case 'shape': { + editor.select(info.shape.id) + break + } + } + } + + override onDoubleClick: TLEventHandlers['onDoubleClick'] = (info) => { + const { editor } = this + + if (info.phase !== 'up') return + + switch (info.target) { + case 'canvas': { + const { currentPagePoint } = editor.inputs + editor.createShapes([ + { + id: createShapeId(), + type: 'box', + x: currentPagePoint.x - 50, + y: currentPagePoint.y - 50, + props: { + w: 100, + h: 100, + }, + }, + ]) + break + } + case 'shape': { + editor.deleteShapes([info.shape.id]) + break + } + } + } +} diff --git a/apps/examples/src/only-editor/MiniBoxShape.tsx b/apps/examples/src/only-editor/MiniBoxShape.tsx new file mode 100644 index 000000000..902d29d06 --- /dev/null +++ b/apps/examples/src/only-editor/MiniBoxShape.tsx @@ -0,0 +1,31 @@ +import { BaseBoxShapeUtil, HTMLContainer, TLBaseShape } from '@tldraw/tldraw' + +export type MiniBoxShape = TLBaseShape<'box', { w: number; h: number; color: string }> + +export class MiniBoxShapeUtil extends BaseBoxShapeUtil { + static override type = 'box' + + override getDefaultProps(): MiniBoxShape['props'] { + return { w: 100, h: 100, color: '#efefef' } + } + + component(shape: MiniBoxShape) { + return ( + +
+ + ) + } + + indicator(shape: MiniBoxShape) { + return + } +} diff --git a/apps/examples/src/only-editor/MiniSelectTool.ts b/apps/examples/src/only-editor/MiniSelectTool.ts new file mode 100644 index 000000000..0affc2d06 --- /dev/null +++ b/apps/examples/src/only-editor/MiniSelectTool.ts @@ -0,0 +1,122 @@ +import { StateNode, TLEventHandlers, TLUnknownShape, createShapeId } from '@tldraw/tldraw' + +/* +This is a bigger example of a state node that implements a "select" tool. + +The state has three children: idle, pointing, and dragging. Only one child +state can be "active" at a time. The parent state's initial active state is +"idle". Certain events received by the child states will cause the parent +state to transition to another child state, making that state active instead. + +Note that when `transition()` is called, the parent state will call the new +active state(s)'s `onEnter` method with the second argument passed to the +transition method. This is useful for passing data between states. +*/ + +class IdleState extends StateNode { + static override id = 'idle' + + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + const { editor } = this + + switch (info.target) { + case 'canvas': { + editor.selectNone() + break + } + case 'selection': { + this.parent.transition('pointing', info) + break + } + case 'shape': { + if (editor.inputs.shiftKey) { + editor.select(...editor.selectedIds, info.shape.id) + } else { + if (!editor.isSelected(info.shape.id)) { + editor.select(info.shape.id) + } + this.parent.transition('pointing', info) + } + break + } + } + } + + override onDoubleClick: TLEventHandlers['onDoubleClick'] = (info) => { + const { editor } = this + + if (info.phase !== 'up') return + + switch (info.target) { + case 'canvas': { + const { currentPagePoint } = editor.inputs + editor.createShapes([ + { + id: createShapeId(), + type: 'box', + x: currentPagePoint.x - 50, + y: currentPagePoint.y - 50, + props: { + w: 100, + h: 100, + }, + }, + ]) + break + } + case 'shape': { + editor.deleteShapes([info.shape.id]) + break + } + } + } +} + +class PointingState extends StateNode { + static override id = 'pointing' + + override onPointerUp: TLEventHandlers['onPointerUp'] = (info) => { + this.parent.transition('idle', info) + } + + override onPointerMove: TLEventHandlers['onPointerUp'] = () => { + if (this.editor.inputs.isDragging) { + this.parent.transition('dragging', { shapes: [...this.editor.selectedShapes] }) + } + } +} + +class DraggingState extends StateNode { + static override id = 'dragging' + + private initialDraggingShapes = [] as TLUnknownShape[] + + override onEnter = (info: { shapes: TLUnknownShape[] }) => { + this.initialDraggingShapes = info.shapes + } + + override onPointerUp: TLEventHandlers['onPointerUp'] = (info) => { + this.parent.transition('idle', info) + } + + override onPointerMove: TLEventHandlers['onPointerUp'] = () => { + const { initialDraggingShapes } = this + const { originPagePoint, currentPagePoint } = this.editor.inputs + + this.editor.updateShapes( + initialDraggingShapes.map((shape) => { + return { + ...shape, + x: shape.x + (currentPagePoint.x - originPagePoint.x), + y: shape.y + (currentPagePoint.y - originPagePoint.y), + } + }) + ) + } +} + +export class MiniSelectTool extends StateNode { + static override id = 'select' + static override children = () => [IdleState, PointingState, DraggingState] + static override initial = 'idle' +} diff --git a/apps/examples/src/only-editor/OnlyEditor.tsx b/apps/examples/src/only-editor/OnlyEditor.tsx new file mode 100644 index 000000000..a82728db7 --- /dev/null +++ b/apps/examples/src/only-editor/OnlyEditor.tsx @@ -0,0 +1,51 @@ +/* eslint-disable import/no-extraneous-dependencies */ + +import { Editor, PositionedOnCanvas, TldrawEditor, createShapeId, track } from '@tldraw/editor' +import '@tldraw/editor/editor.css' +import { MiniBoxShapeUtil } from './MiniBoxShape' +import { MiniSelectTool } from './MiniSelectTool' + +const myTools = [MiniSelectTool] +const myShapeUtils = [MiniBoxShapeUtil] + +export default function OnlyEditorExample() { + return ( +
+ { + editor + .selectAll() + .deleteShapes() + .createShapes([ + { + id: createShapeId(), + type: 'box', + x: 100, + y: 100, + }, + ]) + }} + components={{ + Background: BackgroundComponent, + }} + /> +
+ ) +} + +/** + * This one will move with the camera, just like shapes do. + */ +const BackgroundComponent = track(() => { + return ( + +

Double click to create shapes.

+

Click or Shift+Click to select shapes.

+

Click and drag to move shapes.

+
+ ) +}) diff --git a/apps/examples/src/yjs/YjsExample.tsx b/apps/examples/src/yjs/YjsExample.tsx index c8f884d5c..6e7185805 100644 --- a/apps/examples/src/yjs/YjsExample.tsx +++ b/apps/examples/src/yjs/YjsExample.tsx @@ -1,5 +1,4 @@ -import { track } from '@tldraw/state' -import { Tldraw, useEditor } from '@tldraw/tldraw' +import { Tldraw, track, useEditor } from '@tldraw/tldraw' import '@tldraw/tldraw/tldraw.css' import { useYjsStore } from './useYjsStore' diff --git a/apps/examples/src/yjs/useYjsStore.ts b/apps/examples/src/yjs/useYjsStore.ts index cc6036441..ced24d408 100644 --- a/apps/examples/src/yjs/useYjsStore.ts +++ b/apps/examples/src/yjs/useYjsStore.ts @@ -1,18 +1,19 @@ -import { computed, react, transact } from '@tldraw/state' import { DocumentRecordType, InstancePresenceRecordType, PageRecordType, + TLAnyShapeUtilConstructor, TLDocument, TLInstancePresence, TLPageId, TLRecord, - TLShapeInfo, TLStoreWithStatus, + computed, createPresenceStateDerivation, createTLStore, - defaultShapes, + defaultShapeUtils, getUserPreferences, + react, } from '@tldraw/tldraw' import { useEffect, useMemo, useState } from 'react' import { WebsocketProvider } from 'y-websocket' @@ -21,14 +22,16 @@ import * as Y from 'yjs' export function useYjsStore({ roomId = 'example', hostUrl = process.env.NODE_ENV === 'development' ? 'ws://localhost:1234' : 'wss://demos.yjs.dev', - shapes = [], + shapeUtils = [], }: Partial<{ hostUrl: string roomId: string version: number - shapes?: TLShapeInfo[] + shapeUtils: TLAnyShapeUtilConstructor[] }>) { - const [store] = useState(() => createTLStore({ shapes: [...defaultShapes, ...shapes] })) + const [store] = useState(() => + createTLStore({ shapeUtils: [...defaultShapeUtils, ...shapeUtils] }) + ) const [storeWithStatus, setStoreWithStatus] = useState({ status: 'loading' }) const { doc, room, yRecords } = useMemo(() => { @@ -75,7 +78,7 @@ export function useYjsStore({ // is empty, initialize the yjs doc with the default store records. if (yRecords.size === 0) { // Create the initial store records - transact(() => { + Y.transact(doc, () => { store.clear() store.put([ DocumentRecordType.create({ @@ -97,7 +100,7 @@ export function useYjsStore({ }) } else { // Replace the store records with the yjs doc records - transact(() => { + Y.transact(doc, () => { store.clear() store.put([...yRecords.values()]) }) diff --git a/apps/vscode/editor/package.json b/apps/vscode/editor/package.json index a6f9de40b..ea4c12142 100644 --- a/apps/vscode/editor/package.json +++ b/apps/vscode/editor/package.json @@ -34,11 +34,7 @@ }, "devDependencies": { "@tldraw/assets": "workspace:*", - "@tldraw/editor": "workspace:*", - "@tldraw/file-format": "workspace:*", "@tldraw/tldraw": "workspace:*", - "@tldraw/ui": "workspace:*", - "@tldraw/utils": "workspace:*", "@types/fs-extra": "^11.0.1", "@types/node": "^17.0.14", "@types/react": "^18.0.24", @@ -50,7 +46,6 @@ "esbuild": "^0.18.3", "fs-extra": "^11.1.0", "lazyrepo": "0.0.0-alpha.27", - "nanoid": "4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", "tslib": "^2.4.0" diff --git a/apps/vscode/editor/scripts/build.ts b/apps/vscode/editor/scripts/build.ts index a29ac0fbd..508491d0a 100644 --- a/apps/vscode/editor/scripts/build.ts +++ b/apps/vscode/editor/scripts/build.ts @@ -1,9 +1,9 @@ import esbuild from 'esbuild' import fs from 'fs' -import fse from 'fs-extra' +import fse, { exists } from 'fs-extra' import path from 'path' -import { logEnv } from '../../vscode-script-utils/cli' -import { exists, getDirname } from '../../vscode-script-utils/path' +import { logEnv } from './cli' +import { getDirname } from './path' const rootDir = getDirname(import.meta.url, '../') const log = logEnv('editor') diff --git a/apps/vscode/vscode-script-utils/cli.ts b/apps/vscode/editor/scripts/cli.ts similarity index 100% rename from apps/vscode/vscode-script-utils/cli.ts rename to apps/vscode/editor/scripts/cli.ts diff --git a/apps/vscode/editor/scripts/dev.ts b/apps/vscode/editor/scripts/dev.ts index cc6870890..fff6fd247 100644 --- a/apps/vscode/editor/scripts/dev.ts +++ b/apps/vscode/editor/scripts/dev.ts @@ -1,11 +1,11 @@ import dotenv from 'dotenv' import esbuild from 'esbuild' import fs from 'fs' -import fse from 'fs-extra' +import fse, { exists } from 'fs-extra' import path from 'path' -import { logEnv } from '../../vscode-script-utils/cli' -import { copyEditor } from '../../vscode-script-utils/helpers' -import { exists, getDirname } from '../../vscode-script-utils/path' +import { logEnv } from './cli' +import { copyEditor } from './helpers' +import { getDirname } from './path' dotenv.config() const rootDir = getDirname(import.meta.url, '../') diff --git a/apps/vscode/vscode-script-utils/helpers.ts b/apps/vscode/editor/scripts/helpers.ts similarity index 92% rename from apps/vscode/vscode-script-utils/helpers.ts rename to apps/vscode/editor/scripts/helpers.ts index 0ffa408f3..a10255e3f 100644 --- a/apps/vscode/vscode-script-utils/helpers.ts +++ b/apps/vscode/editor/scripts/helpers.ts @@ -3,7 +3,7 @@ import fse from 'fs-extra' import { join } from 'path' import { exists, getDirname } from './path' -const vscodeDir = getDirname(import.meta.url, '../') +const vscodeDir = getDirname(import.meta.url, '../../') export async function copyEditor({ log }: { log: (opts: any) => void }) { const editorRoot = join(vscodeDir, 'editor') diff --git a/apps/vscode/vscode-script-utils/path.ts b/apps/vscode/editor/scripts/path.ts similarity index 100% rename from apps/vscode/vscode-script-utils/path.ts rename to apps/vscode/editor/scripts/path.ts diff --git a/apps/vscode/editor/src/ChangeResponder.tsx b/apps/vscode/editor/src/ChangeResponder.tsx index 68c3b9ad1..4e004067f 100644 --- a/apps/vscode/editor/src/ChangeResponder.tsx +++ b/apps/vscode/editor/src/ChangeResponder.tsx @@ -1,13 +1,15 @@ -import { useEditor } from '@tldraw/editor' -import { parseAndLoadDocument, serializeTldrawJson } from '@tldraw/file-format' -import { useDefaultHelpers } from '@tldraw/ui' -import { debounce } from '@tldraw/utils' +import { + debounce, + parseAndLoadDocument, + serializeTldrawJson, + useDefaultHelpers, + useEditor, +} from '@tldraw/tldraw' import React from 'react' -import '../public/index.css' -import { vscode } from './utils/vscode' - // @ts-ignore import type { VscodeMessage } from '../../messages' +import '../public/index.css' +import { vscode } from './utils/vscode' export const ChangeResponder = () => { const editor = useEditor() diff --git a/apps/vscode/editor/src/FileOpen.tsx b/apps/vscode/editor/src/FileOpen.tsx index fa371873c..352c822e8 100644 --- a/apps/vscode/editor/src/FileOpen.tsx +++ b/apps/vscode/editor/src/FileOpen.tsx @@ -1,6 +1,4 @@ -import { useEditor } from '@tldraw/editor' -import { parseAndLoadDocument } from '@tldraw/file-format' -import { useDefaultHelpers } from '@tldraw/ui' +import { parseAndLoadDocument, useDefaultHelpers, useEditor } from '@tldraw/tldraw' import React from 'react' import { vscode } from './utils/vscode' diff --git a/apps/vscode/editor/src/app.tsx b/apps/vscode/editor/src/app.tsx index 1fd2e95b2..7356e683f 100644 --- a/apps/vscode/editor/src/app.tsx +++ b/apps/vscode/editor/src/app.tsx @@ -1,20 +1,17 @@ -import { - Canvas, - Editor, - ErrorBoundary, - TldrawEditor, - defaultShapes, - defaultTools, - setRuntimeOverrides, -} from '@tldraw/editor' import { linksUiOverrides } from './utils/links' // eslint-disable-next-line import/no-internal-modules -import '@tldraw/editor/editor.css' -import { ContextMenu, TLUiMenuSchema, TldrawUi } from '@tldraw/ui' -// eslint-disable-next-line import/no-internal-modules -import '@tldraw/ui/ui.css' +import '@tldraw/tldraw/tldraw.css' // eslint-disable-next-line import/no-internal-modules import { getAssetUrlsByImport } from '@tldraw/assets/imports' +import { + Editor, + ErrorBoundary, + TLUiMenuSchema, + Tldraw, + defaultShapeTools, + defaultShapeUtils, + setRuntimeOverrides, +} from '@tldraw/tldraw' import { useCallback, useEffect, useMemo, useState } from 'react' import { VscodeMessage } from '../../messages' import '../public/index.css' @@ -128,26 +125,22 @@ function TldrawInner({ uri, assetSrc, isDarkMode, fileContents }: TLDrawInnerPro const assetUrls = useMemo(() => getAssetUrlsByImport({ baseUrl: assetSrc }), [assetSrc]) const handleMount = useCallback((editor: Editor) => { - editor.externalContentManager.createAssetFromUrl = onCreateAssetFromUrl + editor.registerExternalAssetHandler('url', onCreateAssetFromUrl) }, []) return ( - {/* */} - - - - - - - - + + + ) } diff --git a/apps/vscode/editor/src/utils/bookmarks.ts b/apps/vscode/editor/src/utils/bookmarks.ts index df6098fc1..e47f68600 100644 --- a/apps/vscode/editor/src/utils/bookmarks.ts +++ b/apps/vscode/editor/src/utils/bookmarks.ts @@ -1,8 +1,13 @@ -import { AssetRecordType, Editor, TLAsset, truncateStringWithEllipsis } from '@tldraw/editor' -import { getHashForString } from '@tldraw/utils' +import { AssetRecordType, TLAsset, TLExternalAssetContent, getHashForString } from '@tldraw/tldraw' import { rpc } from './rpc' -export async function onCreateAssetFromUrl(editor: Editor, url: string): Promise { +export const truncateStringWithEllipsis = (str: string, maxLength: number) => { + return str.length <= maxLength ? str : str.substring(0, maxLength - 3) + '...' +} + +export async function onCreateAssetFromUrl({ + url, +}: TLExternalAssetContent & { type: 'url' }): Promise { try { // First, try to get the data from vscode const meta = await rpc('vscode:bookmark', { url }) diff --git a/apps/vscode/editor/src/utils/links.ts b/apps/vscode/editor/src/utils/links.ts index 8fecce09d..a00bdc6f5 100644 --- a/apps/vscode/editor/src/utils/links.ts +++ b/apps/vscode/editor/src/utils/links.ts @@ -1,4 +1,4 @@ -import { menuGroup, menuItem, TLUiOverrides } from '@tldraw/ui' +import { menuGroup, menuItem, TLUiOverrides } from '@tldraw/tldraw' import { openUrl } from './openUrl' export const GITHUB_URL = 'https://github.com/tldraw/tldraw' diff --git a/apps/vscode/editor/src/utils/rpc.ts b/apps/vscode/editor/src/utils/rpc.ts index eaa06171b..3a24921b6 100644 --- a/apps/vscode/editor/src/utils/rpc.ts +++ b/apps/vscode/editor/src/utils/rpc.ts @@ -1,4 +1,4 @@ -import { nanoid } from 'nanoid' +import { uniqueId } from '@tldraw/tldraw' import type { VscodeMessagePairs } from '../../../messages' import { vscode } from './vscode' @@ -26,7 +26,7 @@ export function rpc( type ErrorType = VscodeMessagePairs[typeof id]['error'] const type = (id + '/request') as RequestType['type'] - const uuid = nanoid() + const uuid = uniqueId() return new Promise((resolve, reject) => { const inMessage = { uuid, diff --git a/apps/vscode/editor/tsconfig.json b/apps/vscode/editor/tsconfig.json index d02045103..626f28365 100644 --- a/apps/vscode/editor/tsconfig.json +++ b/apps/vscode/editor/tsconfig.json @@ -22,11 +22,6 @@ "skipDefaultLibCheck": true, "experimentalDecorators": true }, - "include": ["src", "../messages", "scripts", "../vscode-script-utils"], - "references": [ - { "path": "../../../packages/file-format" }, - { "path": "../../../packages/ui" }, - { "path": "../../../packages/editor" }, - { "path": "../../../packages/utils" } - ] + "include": ["src", "../messages", "scripts"], + "references": [{ "path": "../../../packages/tldraw" }] } diff --git a/apps/vscode/extension/package.json b/apps/vscode/extension/package.json index d68d768df..b7218d656 100644 --- a/apps/vscode/extension/package.json +++ b/apps/vscode/extension/package.json @@ -131,8 +131,7 @@ }, "devDependencies": { "@tldraw/editor": "workspace:*", - "@tldraw/file-format": "workspace:*", - "@tldraw/store": "workspace:*", + "@tldraw/tldraw": "workspace:*", "@types/fs-extra": "^11.0.1", "@types/node-fetch": "^2.6.2", "@types/vscode": "^1.75.1", @@ -152,7 +151,6 @@ }, "gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296", "dependencies": { - "nanoid": "4.0.2", "node-fetch": "^2.0.0" } } diff --git a/apps/vscode/extension/scripts/build.ts b/apps/vscode/extension/scripts/build.ts index d546ae92c..7568c7fa7 100644 --- a/apps/vscode/extension/scripts/build.ts +++ b/apps/vscode/extension/scripts/build.ts @@ -1,6 +1,6 @@ import esbuild from 'esbuild' -import { logEnv } from '../../vscode-script-utils/cli' -import { copyEditor, removeDistDirectory } from '../../vscode-script-utils/helpers' +import { logEnv } from './cli' +import { copyEditor, removeDistDirectory } from './helpers' const log = logEnv('extension') diff --git a/apps/vscode/extension/scripts/cli.ts b/apps/vscode/extension/scripts/cli.ts new file mode 100644 index 000000000..c56f32be5 --- /dev/null +++ b/apps/vscode/extension/scripts/cli.ts @@ -0,0 +1,58 @@ +import path from 'path' + +const displayRelative = (from: string, to: string) => { + const outpath = path.relative(from, to) + if (!outpath.match(/^\./)) { + return `./${outpath}` + } + return outpath +} + +type LogDef = + | { cmd: 'remove'; env: string; args: { target: string } } + | { cmd: 'copy'; env: string; args: { source: string; dest: string } } + | { cmd: 'esbuild'; env: string; args: { entryPoints: string[] } } + | { cmd: 'esbuild:success'; env: string; args: any } + | { cmd: 'esbuild:error'; env: string; args: { error: string } } + | { cmd: 'esbuild:serve'; env: string; args: { host: string; port: number | string } } + +export function log(def: LogDef) { + const printStderr = (icon: string, cmd: string, ...args: unknown[]) => { + console.error(`${icon} [${def.env ?? 'unknown'}/${cmd}]`, ...args) + } + + if (def.cmd === 'remove') { + const { target } = def.args + printStderr('🗑 ', 'remove', displayRelative(process.cwd(), target)) + } else if (def.cmd === 'copy') { + const { source, dest } = def.args + printStderr( + '🏠', + 'copy', + `${displayRelative(process.cwd(), source)} -> ${displayRelative(process.cwd(), dest)}` + ) + } else if (def.cmd === 'esbuild') { + printStderr( + '🤖', + 'esbuild', + `${def.args.entryPoints.map((pathname) => displayRelative(process.cwd(), pathname))}` + ) + } else if (def.cmd === 'esbuild:success') { + printStderr('✅', `esbuild`, `build successful (${new Date().toISOString()})`) + } else if (def.cmd === 'esbuild:error') { + printStderr(`❌`, `esbuild`, `error`) + console.error(def.args.error) + } else if (def.cmd === 'esbuild:serve') { + const { host = 'localhost', port } = def.args + printStderr(`🌎`, `esbuild`, `serving `) + } else { + // @ts-ignore + printStderr(`❓`, def.cmd, JSON.stringify(def.args)) + } +} + +export function logEnv(env: string) { + return (opts: any) => { + log({ ...opts, env }) + } +} diff --git a/apps/vscode/extension/scripts/dev.ts b/apps/vscode/extension/scripts/dev.ts index 71b642967..f75615776 100644 --- a/apps/vscode/extension/scripts/dev.ts +++ b/apps/vscode/extension/scripts/dev.ts @@ -1,8 +1,8 @@ import esbuild from 'esbuild' import { join } from 'path' -import { logEnv } from '../../vscode-script-utils/cli' -import { copyEditor, removeDistDirectory } from '../../vscode-script-utils/helpers' -import { getDirname } from '../../vscode-script-utils/path' +import { logEnv } from './cli' +import { copyEditor, removeDistDirectory } from './helpers' +import { getDirname } from './path' const rootDir = getDirname(import.meta.url, '../') const log = logEnv('extension') diff --git a/apps/vscode/extension/scripts/helpers.ts b/apps/vscode/extension/scripts/helpers.ts new file mode 100644 index 000000000..a10255e3f --- /dev/null +++ b/apps/vscode/extension/scripts/helpers.ts @@ -0,0 +1,25 @@ +import fs from 'fs' +import fse from 'fs-extra' +import { join } from 'path' +import { exists, getDirname } from './path' + +const vscodeDir = getDirname(import.meta.url, '../../') + +export async function copyEditor({ log }: { log: (opts: any) => void }) { + const editorRoot = join(vscodeDir, 'editor') + const extensionRoot = join(vscodeDir, 'extension') + + const source = join(editorRoot, 'dist') + const dest = join(extensionRoot, 'editor') + + log({ cmd: 'copy', args: { source, dest } }) + await fse.copy(source, dest) +} + +export async function removeDistDirectory({ log }: { log: (opts: any) => void }) { + const target = join(vscodeDir, 'extension', 'dist') + if (await exists(target)) { + log({ cmd: 'remove', args: { target } }) + await fs.promises.rm(target, { recursive: true }) + } +} diff --git a/apps/vscode/extension/scripts/path.ts b/apps/vscode/extension/scripts/path.ts new file mode 100644 index 000000000..3e7736189 --- /dev/null +++ b/apps/vscode/extension/scripts/path.ts @@ -0,0 +1,16 @@ +import fs from 'fs' +import path from 'path' + +export function getDirname(metaUrl: string, targetPath: string) { + const dirname = path.dirname(metaUrl.replace('file://', '')) + return path.normalize(path.join(dirname, targetPath)) +} + +export async function exists(targetFolder: string) { + try { + await fs.promises.access(targetFolder) + return true + } catch (err) { + return false + } +} diff --git a/apps/vscode/extension/src/TldrawDocument.ts b/apps/vscode/extension/src/TldrawDocument.ts index 7c6ee50fc..a4aca24fa 100644 --- a/apps/vscode/extension/src/TldrawDocument.ts +++ b/apps/vscode/extension/src/TldrawDocument.ts @@ -1,4 +1,4 @@ -import { TldrawFile } from '@tldraw/file-format' +import { TldrawFile } from '@tldraw/tldraw' import * as vscode from 'vscode' import { defaultFileContents, fileExists, loadFile } from './file' import { nicelog } from './utils' diff --git a/apps/vscode/extension/src/TldrawWebviewManager.ts b/apps/vscode/extension/src/TldrawWebviewManager.ts index e8105aa0d..b65ac6ba8 100644 --- a/apps/vscode/extension/src/TldrawWebviewManager.ts +++ b/apps/vscode/extension/src/TldrawWebviewManager.ts @@ -1,8 +1,7 @@ -import { nanoid } from 'nanoid' +import { uniqueId } from '@tldraw/tldraw' import * as vscode from 'vscode' import { TLDrawDocument } from './TldrawDocument' import { GlobalStateKeys, WebViewMessageHandler } from './WebViewMessageHandler' -// @ts-ignore export class TldrawWebviewManager { private disposables: vscode.Disposable[] = [] @@ -15,7 +14,7 @@ export class TldrawWebviewManager { ) { let userId = context.globalState.get(GlobalStateKeys.UserId) if (!userId) { - userId = 'user:' + nanoid() + userId = 'user:' + uniqueId() context.globalState.update(GlobalStateKeys.UserId, userId) } diff --git a/apps/vscode/extension/src/WebViewMessageHandler.ts b/apps/vscode/extension/src/WebViewMessageHandler.ts index 6d2d7b0f0..9bbf1d8f7 100644 --- a/apps/vscode/extension/src/WebViewMessageHandler.ts +++ b/apps/vscode/extension/src/WebViewMessageHandler.ts @@ -1,10 +1,10 @@ -import { UnknownRecord } from '@tldraw/store' import { isEqual } from 'lodash' import fetch from 'node-fetch' import * as vscode from 'vscode' import { TLDrawDocument } from './TldrawDocument' import { loadFile } from './file' +import { UnknownRecord } from '@tldraw/tldraw' // @ts-ignore import type { VscodeMessage } from '../../messages' import { nicelog } from './utils' diff --git a/apps/vscode/extension/src/file.ts b/apps/vscode/extension/src/file.ts index 03fb69473..c1a5faf19 100644 --- a/apps/vscode/extension/src/file.ts +++ b/apps/vscode/extension/src/file.ts @@ -1,17 +1,16 @@ -import { createTLStore, defaultShapes } from '@tldraw/editor' -import { TldrawFile } from '@tldraw/file-format' +import { TldrawFile, createTLStore, defaultShapeUtils } from '@tldraw/tldraw' import * as vscode from 'vscode' import { nicelog } from './utils' export const defaultFileContents: TldrawFile = { tldrawFileFormatVersion: 1, - schema: createTLStore({ shapes: defaultShapes }).schema.serialize(), + schema: createTLStore({ shapeUtils: defaultShapeUtils }).schema.serialize(), records: [], } export const fileContentWithErrors: TldrawFile = { tldrawFileFormatVersion: 1, - schema: createTLStore({ shapes: defaultShapes }).schema.serialize(), + schema: createTLStore({ shapeUtils: defaultShapeUtils }).schema.serialize(), records: [{ typeName: 'shape', id: null } as any], } diff --git a/apps/vscode/extension/tsconfig.json b/apps/vscode/extension/tsconfig.json index 864e1c5c6..626f28365 100644 --- a/apps/vscode/extension/tsconfig.json +++ b/apps/vscode/extension/tsconfig.json @@ -22,6 +22,6 @@ "skipDefaultLibCheck": true, "experimentalDecorators": true }, - "include": ["src", "../messages", "scripts", "../vscode-script-utils"], - "references": [{ "path": "../../../packages/file-format" }] + "include": ["src", "../messages", "scripts"], + "references": [{ "path": "../../../packages/tldraw" }] } diff --git a/docs/docs/shapes.mdx b/docs/docs/shapes.mdx index 838c051ba..70042c33a 100644 --- a/docs/docs/shapes.mdx +++ b/docs/docs/shapes.mdx @@ -89,7 +89,7 @@ const MyCustomShapes = [MyCardShape] export default function () { return (
- +
) } @@ -101,7 +101,7 @@ The [`defineShape`](/gen/editor/defineShape) function can also be used to includ export default function () { return (
- { + { editor.createShapes([{ type: "card" }]) }}/>
diff --git a/package.json b/package.json index 41de38e39..e3924a5e8 100644 --- a/package.json +++ b/package.json @@ -94,5 +94,8 @@ }, "resolutions": { "@microsoft/api-extractor@^7.35.4": "patch:@microsoft/api-extractor@npm%3A7.35.4#./.yarn/patches/@microsoft-api-extractor-npm-7.35.4-5f4f0357b4.patch" + }, + "dependencies": { + "svgo": "^3.0.2" } } diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index c62066b44..1d840d69d 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -8,38 +8,28 @@ import { Atom } from '@tldraw/state'; import { atom } from '@tldraw/state'; -import { Box2d } from '@tldraw/primitives'; import { Box2dModel } from '@tldraw/tlschema'; +import { ComponentType } from 'react'; import { Computed } from '@tldraw/state'; import { computed } from '@tldraw/state'; import { ComputedCache } from '@tldraw/store'; -import { CubicSpline2d } from '@tldraw/primitives'; -import { defineMigrations } from '@tldraw/store'; -import { EASINGS } from '@tldraw/primitives'; import { EmbedDefinition } from '@tldraw/tlschema'; -import { EventEmitter } from 'eventemitter3'; -import { getHashForString } from '@tldraw/utils'; +import { EMPTY_ARRAY } from '@tldraw/state'; +import EventEmitter from 'eventemitter3'; import { HistoryEntry } from '@tldraw/store'; +import { HTMLProps } from 'react'; import { JsonObject } from '@tldraw/utils'; -import { MatLike } from '@tldraw/primitives'; -import { Matrix2d } from '@tldraw/primitives'; -import { Matrix2dModel } from '@tldraw/primitives'; +import { MemoExoticComponent } from 'react'; import { Migrations } from '@tldraw/store'; -import { Polyline2d } from '@tldraw/primitives'; +import { PointerEventHandler } from 'react'; import { react } from '@tldraw/state'; import { default as React_2 } from 'react'; import * as React_3 from 'react'; -import { RecursivePartial } from '@tldraw/utils'; -import { RotateCorner } from '@tldraw/primitives'; -import { SelectionCorner } from '@tldraw/primitives'; -import { SelectionEdge } from '@tldraw/primitives'; -import { SelectionHandle } from '@tldraw/primitives'; import { SerializedSchema } from '@tldraw/store'; import { SerializedStore } from '@tldraw/store'; import { ShapeProps } from '@tldraw/tlschema'; import { Signal } from '@tldraw/state'; import { StoreSchema } from '@tldraw/store'; -import { StrokePoint } from '@tldraw/primitives'; import { StyleProp } from '@tldraw/tlschema'; import { TLArrowShape } from '@tldraw/tlschema'; import { TLArrowShapeArrowheadStyle } from '@tldraw/tlschema'; @@ -48,25 +38,17 @@ import { TLAssetId } from '@tldraw/tlschema'; import { TLAssetPartial } from '@tldraw/tlschema'; import { TLBaseShape } from '@tldraw/tlschema'; import { TLBookmarkAsset } from '@tldraw/tlschema'; -import { TLBookmarkShape } from '@tldraw/tlschema'; import { TLCamera } from '@tldraw/tlschema'; import { TLCursor } from '@tldraw/tlschema'; +import { TLCursorType } from '@tldraw/tlschema'; import { TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'; import { TLDocument } from '@tldraw/tlschema'; -import { TLDrawShape } from '@tldraw/tlschema'; -import { TLEmbedShape } from '@tldraw/tlschema'; -import { TLFrameShape } from '@tldraw/tlschema'; -import { TLGeoShape } from '@tldraw/tlschema'; import { TLGroupShape } from '@tldraw/tlschema'; import { TLHandle } from '@tldraw/tlschema'; -import { TLHighlightShape } from '@tldraw/tlschema'; import { TLImageAsset } from '@tldraw/tlschema'; -import { TLImageShape } from '@tldraw/tlschema'; import { TLInstance } from '@tldraw/tlschema'; import { TLInstancePageState } from '@tldraw/tlschema'; import { TLInstancePresence } from '@tldraw/tlschema'; -import { TLLineShape } from '@tldraw/tlschema'; -import { TLNoteShape } from '@tldraw/tlschema'; import { TLPage } from '@tldraw/tlschema'; import { TLPageId } from '@tldraw/tlschema'; import { TLParentId } from '@tldraw/tlschema'; @@ -77,29 +59,21 @@ import { TLShapeId } from '@tldraw/tlschema'; import { TLShapePartial } from '@tldraw/tlschema'; import { TLStore } from '@tldraw/tlschema'; import { TLStoreProps } from '@tldraw/tlschema'; -import { TLTextShape } from '@tldraw/tlschema'; import { TLUnknownShape } from '@tldraw/tlschema'; import { TLVideoAsset } from '@tldraw/tlschema'; -import { TLVideoShape } from '@tldraw/tlschema'; import { track } from '@tldraw/state'; +import { transact } from '@tldraw/state'; +import { transaction } from '@tldraw/state'; import { UnknownRecord } from '@tldraw/store'; import { useComputed } from '@tldraw/state'; import { useQuickReactor } from '@tldraw/state'; import { useReactor } from '@tldraw/state'; import { useValue } from '@tldraw/state'; -import { Vec2d } from '@tldraw/primitives'; import { Vec2dModel } from '@tldraw/tlschema'; -import { VecLike } from '@tldraw/primitives'; import { whyAmIRunning } from '@tldraw/state'; -// @public (undocumented) -export const ACCEPTED_ASSET_TYPE: string; - -// @public (undocumented) -export const ACCEPTED_IMG_TYPE: string[]; - -// @public (undocumented) -export const ACCEPTED_VID_TYPE: string[]; +// @public +export function angleDelta(a0: number, a1: number): number; // @internal (undocumented) export const ANIMATION_MEDIUM_MS = 320; @@ -107,77 +81,31 @@ export const ANIMATION_MEDIUM_MS = 320; // @internal (undocumented) export const ANIMATION_SHORT_MS = 80; -// @public (undocumented) -export const ArrowShape: TLShapeInfo; +// @internal (undocumented) +export function applyRotationToSnapshotShapes({ delta, editor, snapshot, stage, }: { + delta: number; + snapshot: TLRotationSnapshot; + editor: Editor; + stage: 'end' | 'one-off' | 'start' | 'update'; +}): void; -// @public (undocumented) -export class ArrowShapeUtil extends ShapeUtil { - // (undocumented) - canBind: () => boolean; - // (undocumented) - canEdit: () => boolean; - // (undocumented) - canSnap: () => boolean; - // (undocumented) - component(shape: TLArrowShape): JSX.Element | null; - // (undocumented) - getBounds(shape: TLArrowShape): Box2d; - // (undocumented) - getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[]; - // (undocumented) - getCenter(shape: TLArrowShape): Vec2d; - // (undocumented) - getDefaultProps(): TLArrowShape['props']; - // (undocumented) - getHandles(shape: TLArrowShape): TLHandle[]; - // (undocumented) - getLabelBounds(shape: TLArrowShape): Box2d | null; - // (undocumented) - getOutline(shape: TLArrowShape): Vec2d[]; - // (undocumented) - getOutlineWithoutLabel(shape: TLArrowShape): Vec2d[]; - // (undocumented) - hideResizeHandles: TLShapeUtilFlag; - // (undocumented) - hideRotateHandle: TLShapeUtilFlag; - // (undocumented) - hideSelectionBoundsBg: TLShapeUtilFlag; - // (undocumented) - hideSelectionBoundsFg: TLShapeUtilFlag; - // (undocumented) - hitTestLineSegment(shape: TLArrowShape, A: VecLike, B: VecLike): boolean; - // (undocumented) - hitTestPoint(shape: TLArrowShape, point: VecLike): boolean; - // (undocumented) - indicator(shape: TLArrowShape): JSX.Element | null; - // (undocumented) - isClosed: () => boolean; - // (undocumented) - get labelBoundsCache(): ComputedCache; - // (undocumented) - onDoubleClickHandle: (shape: TLArrowShape, handle: TLHandle) => TLShapePartial | void; - // (undocumented) - onEditEnd: TLOnEditEndHandler; - // (undocumented) - onHandleChange: TLOnHandleChangeHandler; - // (undocumented) - onResize: TLOnResizeHandler; - // (undocumented) - onTranslateStart: TLOnTranslateStartHandler; - // (undocumented) - snapPoints(_shape: TLArrowShape): Vec2d[]; - // (undocumented) - toSvg(shape: TLArrowShape, ctx: SvgExportContext): SVGGElement; - // (undocumented) - static type: "arrow"; -} +// @public +export function approximately(a: number, b: number, precision?: number): boolean; + +// @public +export function areAnglesCompatible(a: number, b: number): boolean; + +export { Atom } export { atom } +// @public (undocumented) +export function average(A: VecLike, B: VecLike): string; + // @public (undocumented) export abstract class BaseBoxShapeTool extends StateNode { // (undocumented) - static children: () => (typeof Idle_4 | typeof Pointing_2)[]; + static children: () => (typeof Idle | typeof Pointing)[]; // (undocumented) static id: string; // (undocumented) @@ -203,49 +131,140 @@ export abstract class BaseBoxShapeUtil extends Sha } // @public (undocumented) -export function blobAsString(blob: Blob): Promise; - -// @public (undocumented) -export const BookmarkShape: TLShapeInfo; - -// @public (undocumented) -export class BookmarkShapeUtil extends BaseBoxShapeUtil { +export class Box2d { + constructor(x?: number, y?: number, w?: number, h?: number); // (undocumented) - canResize: () => boolean; + get aspectRatio(): number; // (undocumented) - component(shape: TLBookmarkShape): JSX.Element; + get center(): Vec2d; + set center(v: Vec2d); // (undocumented) - getDefaultProps(): TLBookmarkShape['props']; + clone(): Box2d; // (undocumented) - hideSelectionBoundsBg: () => boolean; + static Collides: (A: Box2d, B: Box2d) => boolean; // (undocumented) - hideSelectionBoundsFg: () => boolean; + collides(B: Box2d): boolean; // (undocumented) - indicator(shape: TLBookmarkShape): JSX.Element; + static Common: (boxes: Box2d[]) => Box2d; // (undocumented) - onBeforeCreate?: TLOnBeforeCreateHandler; + static Contains: (A: Box2d, B: Box2d) => boolean; // (undocumented) - onBeforeUpdate?: TLOnBeforeUpdateHandler; + contains(B: Box2d): boolean; // (undocumented) - static type: "bookmark"; + static ContainsPoint: (A: Box2d, B: number | VecLike, y?: number) => boolean; + // (undocumented) + containsPoint(V: number | VecLike, y?: number): boolean; + // (undocumented) + get corners(): Vec2d[]; + // (undocumented) + static Equals(a: Box2d | Box2dModel, b: Box2d | Box2dModel): boolean; + // (undocumented) + equals(other: Box2d | Box2dModel): boolean; + // (undocumented) + static Expand(A: Box2d, B: Box2d): Box2d; + // (undocumented) + expand(A: Box2d): this; + // (undocumented) + static ExpandBy(A: Box2d, n: number): Box2d; + // (undocumented) + expandBy(n: number): this; + // (undocumented) + static From(box: Box2dModel): Box2d; + // (undocumented) + static FromPoints(points: VecLike[]): Box2d; + // (undocumented) + getHandlePoint(handle: SelectionCorner | SelectionEdge): Vec2d; + // (undocumented) + h: number; + // (undocumented) + get height(): number; + set height(n: number); + // (undocumented) + static Includes: (A: Box2d, B: Box2d) => boolean; + // (undocumented) + includes(B: Box2d): boolean; + // (undocumented) + get maxX(): number; + // (undocumented) + get maxY(): number; + // (undocumented) + get midX(): number; + // (undocumented) + get midY(): number; + // (undocumented) + get minX(): number; + set minX(n: number); + // (undocumented) + get minY(): number; + set minY(n: number); + // (undocumented) + get point(): Vec2d; + set point(val: Vec2d); + // (undocumented) + static Resize(box: Box2d, handle: SelectionCorner | SelectionEdge | string, dx: number, dy: number, isAspectRatioLocked?: boolean): { + box: Box2d; + scaleX: number; + scaleY: number; + }; + // (undocumented) + resize(handle: SelectionCorner | SelectionEdge | string, dx: number, dy: number): void; + // (undocumented) + scale(n: number): this; + // (undocumented) + set(x?: number, y?: number, w?: number, h?: number): this; + // (undocumented) + setTo(B: Box2d): this; + // (undocumented) + static Sides: (A: Box2d, inset?: number) => Vec2d[][]; + // (undocumented) + get sides(): Array<[Vec2d, Vec2d]>; + // (undocumented) + get size(): Vec2d; + // (undocumented) + get snapPoints(): Vec2d[]; + // (undocumented) + snapToGrid(size: number): void; + // (undocumented) + toFixed(): this; + // (undocumented) + toJson(): Box2dModel; + // (undocumented) + translate(delta: VecLike): this; + // (undocumented) + union(box: Box2dModel): this; + // (undocumented) + w: number; + // (undocumented) + get width(): number; + set width(n: number); + // (undocumented) + x: number; + // (undocumented) + y: number; } +// @internal (undocumented) +export const CAMERA_SLIDE_FRICTION = 0.09; + +// @public (undocumented) +export function canonicalizeRotation(a: number): number; + // @public (undocumented) export const Canvas: React_2.MemoExoticComponent<() => JSX.Element>; -// @public (undocumented) -export const checkFlag: (flag: (() => boolean) | boolean | undefined) => boolean | undefined; +// @public +export function clamp(n: number, min: number): number; + +// @public +export function clamp(n: number, min: number, max: number): number; + +// @public +export function clampRadians(r: number): number; export { computed } -// @public -export function containBoxSize(originalSize: BoxWidthHeight, containBoxSize: BoxWidthHeight): BoxWidthHeight; - // @public (undocumented) -export const coreShapes: readonly [TLShapeInfo, TLShapeInfo, TLShapeInfo, TLShapeInfo, TLShapeInfo]; - -// @public (undocumented) -export function correctSpacesToNbsp(input: string): string; +export const coreShapes: readonly [typeof GroupShapeUtil]; // @public export function createSessionStateSnapshotSignal(store: TLStore): Signal; @@ -260,9 +279,6 @@ export function createTLUser(opts?: { setUserPreferences?: ((userPreferences: TLUserPreferences) => void) | undefined; }): TLUser; -// @public (undocumented) -export function dataTransferItemAsString(item: DataTransferItem): Promise; - // @public (undocumented) export function dataUrlToFile(url: string, filename: string, mimeType: string): Promise; @@ -290,81 +306,44 @@ export const DEFAULT_ANIMATION_OPTIONS: { easing: (t: number) => number; }; -// @public (undocumented) -export let defaultEditorAssetUrls: TLEditorAssetUrls; - -// @public (undocumented) -export function defaultEmptyAs(str: string, dflt: string): string; - // @internal (undocumented) export const DefaultErrorFallback: TLErrorFallbackComponent; -// @public (undocumented) -export const defaultShapes: readonly [TLShapeInfo, TLShapeInfo, TLShapeInfo, TLShapeInfo, TLShapeInfo, TLShapeInfo, TLShapeInfo, TLShapeInfo]; - -// @public (undocumented) -export const defaultTools: TLStateNodeConstructor[]; - -export { defineMigrations } - -// @public (undocumented) -export function defineShape(type: T['type'], opts: Omit, 'type'>): TLShapeInfo; +// @public +export function degreesToRadians(d: number): number; // @internal (undocumented) export const DOUBLE_CLICK_DURATION = 450; -// @public (undocumented) -export function downloadDataURLAsFile(dataUrl: string, filename: string): void; - // @internal (undocumented) export const DRAG_DISTANCE = 4; // @public (undocumented) -export const DrawShape: TLShapeInfo; - -// @public (undocumented) -export class DrawShapeUtil extends ShapeUtil { - // (undocumented) - component(shape: TLDrawShape): JSX.Element; - // (undocumented) - expandSelectionOutlinePx(shape: TLDrawShape): number; - // (undocumented) - getBounds(shape: TLDrawShape): Box2d; - // (undocumented) - getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[]; - // (undocumented) - getCenter(shape: TLDrawShape): Vec2d; - // (undocumented) - getDefaultProps(): TLDrawShape['props']; - // (undocumented) - getOutline(shape: TLDrawShape): Vec2d[]; - // (undocumented) - hideResizeHandles: (shape: TLDrawShape) => boolean; - // (undocumented) - hideRotateHandle: (shape: TLDrawShape) => boolean; - // (undocumented) - hideSelectionBoundsBg: (shape: TLDrawShape) => boolean; - // (undocumented) - hideSelectionBoundsFg: (shape: TLDrawShape) => boolean; - // (undocumented) - hitTestLineSegment(shape: TLDrawShape, A: VecLike, B: VecLike): boolean; - // (undocumented) - hitTestPoint(shape: TLDrawShape, point: VecLike): boolean; - // (undocumented) - indicator(shape: TLDrawShape): JSX.Element; - // (undocumented) - isClosed: (shape: TLDrawShape) => boolean; - // (undocumented) - onResize: TLOnResizeHandler; - // (undocumented) - toSvg(shape: TLDrawShape, ctx: SvgExportContext): SVGGElement; - // (undocumented) - static type: "draw"; -} +export const EASINGS: { + readonly linear: (t: number) => number; + readonly easeInQuad: (t: number) => number; + readonly easeOutQuad: (t: number) => number; + readonly easeInOutQuad: (t: number) => number; + readonly easeInCubic: (t: number) => number; + readonly easeOutCubic: (t: number) => number; + readonly easeInOutCubic: (t: number) => number; + readonly easeInQuart: (t: number) => number; + readonly easeOutQuart: (t: number) => number; + readonly easeInOutQuart: (t: number) => number; + readonly easeInQuint: (t: number) => number; + readonly easeOutQuint: (t: number) => number; + readonly easeInOutQuint: (t: number) => number; + readonly easeInSine: (t: number) => number; + readonly easeOutSine: (t: number) => number; + readonly easeInOutSine: (t: number) => number; + readonly easeInExpo: (t: number) => number; + readonly easeOutExpo: (t: number) => number; + readonly easeInOutExpo: (t: number) => number; +}; // @public (undocumented) export class Editor extends EventEmitter { - constructor({ store, user, shapes, tools, getContainer }: TLEditorOptions); + constructor({ store, user, shapeUtils, tools, getContainer, initialState }: TLEditorOptions); addOpenMenu(id: string): this; alignShapes(operation: 'bottom' | 'center-horizontal' | 'center-vertical' | 'left' | 'right' | 'top', ids?: TLShapeId[]): this; get allShapesCommonBounds(): Box2d | null; @@ -376,6 +355,7 @@ export class Editor extends EventEmitter { animateToShape(shapeId: TLShapeId, opts?: TLAnimationOptions): this; animateToUser(userId: string): void; get animationSpeed(): number; + set animationSpeed(animationSpeed: number); // @internal (undocumented) annotateError(error: unknown, { origin, willCrashApp, tags, extras, }: { origin: string; @@ -387,10 +367,12 @@ export class Editor extends EventEmitter { bail(): this; bailToMark(id: string): this; batch(fn: () => void): this; - blur(): this; + // (undocumented) + blur: () => boolean; bringForward(ids?: TLShapeId[]): this; bringToFront(ids?: TLShapeId[]): this; get brush(): Box2dModel | null; + set brush(brush: Box2dModel | null); get camera(): TLCamera; get cameraState(): "idle" | "moving"; cancel(): this; @@ -426,17 +408,21 @@ export class Editor extends EventEmitter { createPage(title: string, id?: TLPageId, belowPageIndex?: string): this; createShapes(partials: TLShapePartial[], select?: boolean): this; get croppingId(): null | TLShapeId; + set croppingId(id: null | TLShapeId); get currentPage(): TLPage; get currentPageId(): TLPageId; get currentPageShapeIds(): Set; + get currentTool(): StateNode | undefined; get currentToolId(): string; get cursor(): TLCursor; + set cursor(cursor: TLCursor); deleteAssets(ids: TLAssetId[]): this; deleteOpenMenu(id: string): this; deletePage(id: TLPageId): void; deleteShapes(ids?: TLShapeId[]): this; deselect(...ids: TLShapeId[]): this; get devicePixelRatio(): number; + set devicePixelRatio(dpr: number); dispatch(info: TLEventInfo): this; readonly disposables: Set<() => void>; dispose(): void; @@ -445,18 +431,33 @@ export class Editor extends EventEmitter { duplicatePage(id?: TLPageId, createId?: TLPageId): this; duplicateShapes(ids?: TLShapeId[], offset?: VecLike): this; get editingId(): null | TLShapeId; - // (undocumented) - get editingShape(): null | TLUnknownShape; + set editingId(id: null | TLShapeId); get erasingIds(): TLShapeId[]; + set erasingIds(ids: TLShapeId[]); get erasingIdsSet(): Set; - // (undocumented) - externalContentManager: PlopManager; + // @internal (undocumented) + externalAssetContentHandlers: { + [K in TLExternalAssetContent_2['type']]: { + [Key in K]: ((info: TLExternalAssetContent_2 & { + type: Key; + }) => Promise) | null; + }[K]; + }; + // @internal (undocumented) + externalContentHandlers: { + [K in TLExternalContent_2['type']]: { + [Key in K]: ((info: TLExternalContent_2 & { + type: Key; + }) => void) | null; + }[K]; + }; findAncestor(shape: TLShape, predicate: (parent: TLShape) => boolean): TLShape | undefined; findCommonAncestor(shapes: TLShape[], predicate?: (shape: TLShape) => boolean): TLShapeId | undefined; flipShapes(operation: 'horizontal' | 'vertical', ids?: TLShapeId[]): this; - focus(): this; + // (undocumented) + focus: () => boolean; get focusLayerId(): TLPageId | TLShapeId; - get focusLayerShape(): TLShape | undefined; + set focusLayerId(next: TLPageId | TLShapeId); getAncestorPageId(shape?: TLShape): TLPageId | undefined; getAncestors(shape: TLShape, acc?: TLShape[]): TLShape[]; getAncestorsById(id: TLShapeId, acc?: TLShape[]): TLShape[]; @@ -468,6 +469,7 @@ export class Editor extends EventEmitter { }[]; getAssetById(id: TLAssetId): TLAsset | undefined; getAssetBySrc(src: string): TLBookmarkAsset | TLImageAsset | TLVideoAsset | undefined; + getAssetForExternalContent(info: TLExternalAssetContent_2): Promise; getBounds(shape: T): Box2d; getBoundsById(id: T['id']): Box2d | undefined; getClipPathById(id: TLShapeId): string | undefined; @@ -529,13 +531,13 @@ export class Editor extends EventEmitter { preserveAspectRatio: React.SVGAttributes['preserveAspectRatio']; }>): Promise; getTransform(shape: TLShape): Matrix2d; - get gridSize(): number; groupShapes(ids?: TLShapeId[], groupId?: TLShapeId): this; hasAncestor(shape: TLShape | undefined, ancestorId: TLShapeId): boolean; get hintingIds(): TLShapeId[]; + set hintingIds(ids: TLShapeId[]); readonly history: HistoryManager; get hoveredId(): null | TLShapeId; - get hoveredShape(): null | TLUnknownShape; + set hoveredId(id: null | TLShapeId); inputs: { originPagePoint: Vec2d; originScreenPoint: Vec2d; @@ -558,6 +560,7 @@ export class Editor extends EventEmitter { }; get instanceState(): TLInstance; interrupt(): this; + isAncestorSelected(id: TLShapeId): boolean; readonly isAndroid: boolean; get isChangingStyle(): boolean; set isChangingStyle(v: boolean); @@ -565,27 +568,35 @@ export class Editor extends EventEmitter { get isCoarsePointer(): boolean; set isCoarsePointer(v: boolean); get isDarkMode(): boolean; + set isDarkMode(isDarkMode: boolean); readonly isFirefox: boolean; get isFocused(): boolean; + set isFocused(isFocused: boolean); get isFocusMode(): boolean; + set isFocusMode(isFocusMode: boolean); get isGridMode(): boolean; + set isGridMode(isGridMode: boolean); isIn(path: string): boolean; isInAny(...paths: string[]): boolean; readonly isIos: boolean; get isMenuOpen(): boolean; get isPenMode(): boolean; + set isPenMode(isPenMode: boolean); isPointInShape(point: VecLike, shape: TLShape): boolean; get isReadOnly(): boolean; + set isReadOnly(isReadOnly: boolean); readonly isSafari: boolean; isSelected(id: TLShapeId): boolean; isShapeInPage(shape: TLShape, pageId?: TLPageId): boolean; isShapeOfType(shape: TLUnknownShape, type: T['type']): shape is T; isShapeOrAncestorLocked(shape?: TLShape): boolean; get isSnapMode(): boolean; + set isSnapMode(isSnapMode: boolean); get isToolLocked(): boolean; - isWithinSelection(id: TLShapeId): boolean; + set isToolLocked(isToolLocked: boolean); get locale(): string; - mark(reason?: string, onUndo?: boolean, onRedo?: boolean): string; + set locale(locale: string); + mark(markId?: string, onUndo?: boolean, onRedo?: boolean): string; moveShapesToPage(ids: TLShapeId[], pageId: TLPageId): this; nudgeShapes(ids: TLShapeId[], direction: Vec2dModel, major?: boolean, ephemeral?: boolean): this; get onlySelectedShape(): null | TLShape; @@ -603,21 +614,24 @@ export class Editor extends EventEmitter { popFocusLayer(): this; // @internal (undocumented) get projectName(): string; + set projectName(name: string); putContent(content: TLContent, options?: { point?: VecLike; select?: boolean; preservePosition?: boolean; preserveIds?: boolean; }): this; - putExternalContent(info: TLExternalContent): Promise; + putExternalContent(info: TLExternalContent_2): Promise; redo(): this; + registerExternalAssetHandler(type: T, handler: ((info: TLExternalAssetContent_2 & { + type: T; + }) => Promise) | null): this; + registerExternalContentHandler(type: T, handler: ((info: T extends TLExternalContent_2['type'] ? TLExternalContent_2 & { + type: T; + } : TLExternalContent_2) => void) | null): this; renamePage(id: TLPageId, name: string, squashing?: boolean): this; get renderingBounds(): Box2d; - // @internal (undocumented) - readonly _renderingBounds: Atom; get renderingBoundsExpanded(): Box2d; - // @internal (undocumented) - readonly _renderingBoundsExpanded: Atom; get renderingShapes(): { id: TLShapeId; index: number; @@ -628,7 +642,6 @@ export class Editor extends EventEmitter { maskedPageBounds: Box2d | undefined; }[]; reparentShapesById(ids: TLShapeId[], parentId: TLParentId, insertIndex?: string): this; - replaceStoreContentsWithRecordsForOtherDocument(records: TLRecord[]): void; resetZoom(point?: Vec2d, opts?: TLAnimationOptions): this; resizeShape(id: TLShapeId, scale: VecLike, options?: { initialBounds?: Box2d; @@ -647,6 +660,7 @@ export class Editor extends EventEmitter { z: number; }; get scribble(): null | TLScribble; + set scribble(scribble: null | TLScribble); select(...ids: TLShapeId[]): this; selectAll(): this; get selectedIds(): TLShapeId[]; @@ -659,35 +673,13 @@ export class Editor extends EventEmitter { selectNone(): this; sendBackward(ids?: TLShapeId[]): this; sendToBack(ids?: TLShapeId[]): this; - setAnimationSpeed(animationSpeed: number): this; - setBrush(brush?: Box2dModel | null): this; setCamera(x: number, y: number, z?: number, { stopFollowing }?: TLViewportOptions): this; - setCroppingId(id: null | TLShapeId): this; setCurrentPageId(pageId: TLPageId, { stopFollowing }?: TLViewportOptions): this; - setCursor(cursor: Partial): this; - setDarkMode(isDarkMode: boolean): this; - setDevicePixelRatio(dpr: number): this; - setEditingId(id: null | TLShapeId): this; - setErasingIds(ids?: TLShapeId[]): this; - setFocusLayer(next: null | TLShapeId): this; - setFocusMode(isFocusMode: boolean): this; - setGridMode(isGridMode: boolean): this; - setHintingIds(ids: TLShapeId[]): this; - setHoveredId(id?: null | TLShapeId): this; - setLocale(locale: string): void; + setCurrentTool(id: string, info?: {}): this; setOpacity(opacity: number, ephemeral?: boolean, squashing?: boolean): this; setPageState(partial: Partial, ephemeral?: boolean): void; - setPenMode(isPenMode: boolean): this; - // @internal (undocumented) - setProjectName(name: string): void; - setReadOnly(isReadOnly: boolean): this; - setScribble(scribble?: null | TLScribble): this; setSelectedIds(ids: TLShapeId[], squashing?: boolean): this; - setSelectedTool(id: string, info?: {}): this; - setSnapMode(isSnapMode: boolean): this; setStyle(style: StyleProp, value: T, ephemeral?: boolean, squashing?: boolean): this; - setToolLocked(isToolLocked: boolean): this; - setZoomBrush(zoomBrush?: Box2dModel | null): this; get shapesArray(): TLShape[]; shapeUtils: { readonly [K in string]?: ShapeUtil; @@ -708,12 +700,16 @@ export class Editor extends EventEmitter { stopFollowingUser(): this; readonly store: TLStore; stretchShapes(operation: 'horizontal' | 'vertical', ids?: TLShapeId[]): this; + // (undocumented) + styleProps: { + [key: string]: Map, string>; + }; readonly textMeasure: TextManager; toggleLock(ids?: TLShapeId[]): this; undo(): HistoryManager; ungroupShapes(ids?: TLShapeId[]): this; updateAssets(assets: TLAssetPartial[]): this; - updateDocumentSettings(settings: Partial): void; + updateDocumentSettings(settings: Partial): this; updateInstanceState(partial: Partial>, ephemeral?: boolean, squashing?: boolean): this; updatePage(partial: RequiredKeys, squashing?: boolean): this; // @internal @@ -727,6 +723,7 @@ export class Editor extends EventEmitter { get viewportScreenCenter(): Vec2d; visitDescendants(parentId: TLParentId, visitor: (id: TLShapeId) => false | void): void; get zoomBrush(): Box2dModel | null; + set zoomBrush(zoomBrush: Box2dModel | null); zoomIn(point?: Vec2d, opts?: TLAnimationOptions): this; get zoomLevel(): number; zoomOut(point?: Vec2d, opts?: TLAnimationOptions): this; @@ -736,34 +733,10 @@ export class Editor extends EventEmitter { zoomToSelection(opts?: TLAnimationOptions): this; } -// @public (undocumented) -export const EmbedShape: TLShapeInfo; +export { EMPTY_ARRAY } // @public (undocumented) -export class EmbedShapeUtil extends BaseBoxShapeUtil { - // (undocumented) - canEdit: TLShapeUtilFlag; - // (undocumented) - canResize: (shape: TLEmbedShape) => boolean; - // (undocumented) - canUnmount: TLShapeUtilFlag; - // (undocumented) - component(shape: TLEmbedShape): JSX.Element; - // (undocumented) - getDefaultProps(): TLEmbedShape['props']; - // (undocumented) - hideSelectionBoundsBg: TLShapeUtilFlag; - // (undocumented) - hideSelectionBoundsFg: TLShapeUtilFlag; - // (undocumented) - indicator(shape: TLEmbedShape): JSX.Element; - // (undocumented) - isAspectRatioLocked: TLShapeUtilFlag; - // (undocumented) - onResize: TLOnResizeHandler; - // (undocumented) - static type: "embed"; -} +export const EPSILON: number; // @public (undocumented) export class ErrorBoundary extends React_3.Component>, TLErrorBoundaryState> { @@ -796,200 +769,65 @@ export const featureFlags: { highlighterTool: DebugFlag; }; -// @public -export function fileToBase64(file: Blob): Promise; - // @public (undocumented) -export const FrameShape: TLShapeInfo; - -// @public (undocumented) -export class FrameShapeUtil extends BaseBoxShapeUtil { - // (undocumented) - canBind: () => boolean; - // (undocumented) - canDropShapes: (shape: TLFrameShape, _shapes: TLShape[]) => boolean; - // (undocumented) - canEdit: () => boolean; - // (undocumented) - canReceiveNewChildrenOfType: (shape: TLShape, _type: TLShape['type']) => boolean; - // (undocumented) - component(shape: TLFrameShape): JSX.Element; - // (undocumented) - getDefaultProps(): TLFrameShape['props']; - // (undocumented) - indicator(shape: TLFrameShape): JSX.Element; - // (undocumented) - onDragShapesOut: (_shape: TLFrameShape, shapes: TLShape[]) => void; - // (undocumented) - onDragShapesOver: (frame: TLFrameShape, shapes: TLShape[]) => { - shouldHint: boolean; - }; - // (undocumented) - onResizeEnd: TLOnResizeEndHandler; - // (undocumented) - providesBackgroundForChildren(): boolean; - // (undocumented) - toSvg(shape: TLFrameShape): Promise | SVGElement; - // (undocumented) - static type: "frame"; -} - -// @public (undocumented) -export const GeoShape: TLShapeInfo; - -// @public (undocumented) -export class GeoShapeUtil extends BaseBoxShapeUtil { - // (undocumented) - canEdit: () => boolean; - // (undocumented) - component(shape: TLGeoShape): JSX.Element; - // (undocumented) - getBounds(shape: TLGeoShape): Box2d; - // (undocumented) - getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[]; - // (undocumented) - getCenter(shape: TLGeoShape): Vec2d; - // (undocumented) - getDefaultProps(): TLGeoShape['props']; - // (undocumented) - getOutline(shape: TLGeoShape): Vec2d[]; - // (undocumented) - hitTestLineSegment(shape: TLGeoShape, A: VecLike, B: VecLike): boolean; - // (undocumented) - hitTestPoint(shape: TLGeoShape, point: VecLike): boolean; - // (undocumented) - indicator(shape: TLGeoShape): JSX.Element; - // (undocumented) - onBeforeCreate: (shape: TLGeoShape) => { - props: { - growY: number; - geo: "arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "cloud" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box"; - labelColor: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; - color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; - fill: "none" | "pattern" | "semi" | "solid"; - dash: "dashed" | "dotted" | "draw" | "solid"; - size: "l" | "m" | "s" | "xl"; - font: "draw" | "mono" | "sans" | "serif"; - align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start"; - verticalAlign: "end" | "middle" | "start"; - url: string; - w: number; - h: number; - text: string; - }; - type: "geo"; - x: number; - y: number; - rotation: number; - index: string; - parentId: TLParentId; - isLocked: boolean; - opacity: number; - meta: JsonObject; - id: TLShapeId; - typeName: "shape"; - } | undefined; - // (undocumented) - onBeforeUpdate: (prev: TLGeoShape, next: TLGeoShape) => { - props: { - growY: number; - geo: "arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "cloud" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box"; - labelColor: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; - color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; - fill: "none" | "pattern" | "semi" | "solid"; - dash: "dashed" | "dotted" | "draw" | "solid"; - size: "l" | "m" | "s" | "xl"; - font: "draw" | "mono" | "sans" | "serif"; - align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start"; - verticalAlign: "end" | "middle" | "start"; - url: string; - w: number; - h: number; - text: string; - }; - type: "geo"; - x: number; - y: number; - rotation: number; - index: string; - parentId: TLParentId; - isLocked: boolean; - opacity: number; - meta: JsonObject; - id: TLShapeId; - typeName: "shape"; - } | undefined; - // (undocumented) - onDoubleClick: (shape: TLGeoShape) => { - props: { - geo: "check-box"; - }; - type: "geo"; - x: number; - y: number; - rotation: number; - index: string; - parentId: TLParentId; - isLocked: boolean; - opacity: number; - meta: JsonObject; - id: TLShapeId; - typeName: "shape"; - } | { - props: { - geo: "rectangle"; - }; - type: "geo"; - x: number; - y: number; - rotation: number; - index: string; - parentId: TLParentId; - isLocked: boolean; - opacity: number; - meta: JsonObject; - id: TLShapeId; - typeName: "shape"; - } | undefined; - // (undocumented) - onEditEnd: TLOnEditEndHandler; - // (undocumented) - onResize: TLOnResizeHandler; - // (undocumented) - toSvg(shape: TLGeoShape, ctx: SvgExportContext): SVGElement; - // (undocumented) - static type: "geo"; -} +export type GapsSnapLine = { + id: string; + type: 'gaps'; + direction: 'horizontal' | 'vertical'; + gaps: Array<{ + startEdge: [VecLike, VecLike]; + endEdge: [VecLike, VecLike]; + }>; +}; // @public -export function getEmbedInfo(inputUrl: string): TLEmbedResult; +export function getArcLength(C: VecLike, r: number, A: VecLike, B: VecLike): number; + +// @public (undocumented) +export function getArrowheadPathForType(info: ArrowInfo, side: 'end' | 'start', strokeWidth: number): string | undefined; + +// @public (undocumented) +export function getArrowTerminalsInArrowSpace(editor: Editor, shape: TLArrowShape): { + start: Vec2d; + end: Vec2d; +}; + +// @public (undocumented) +export function getCursor(cursor: TLCursorType, rotation?: number, color?: string): string; // @public -export function getEmbedInfoUnsafely(inputUrl: string): TLEmbedResult; - -// @public -export function getFileMetaData(file: File): Promise<{ - isAnimated: boolean; -}>; +export function getCurvedArrowHandlePath(info: ArrowInfo & { + isStraight: false; +}): string; // @public (undocumented) export function getFreshUserPreferences(): TLUserPreferences; -export { getHashForString } - -// @public -export function getImageSizeFromSrc(dataURL: string): Promise<{ - w: number; - h: number; -}>; - // @public export function getIncrementedName(name: string, others: string[]): string; // @public -export function getMediaAssetFromFile(file: File): Promise; +export function getIndexAbove(below: string): string; -// @internal (undocumented) +// @public +export function getIndexBelow(above: string): string; + +// @public +export function getIndexBetween(below: string, above?: string): string; + +// @public +export function getIndices(n: number, start?: string): string[]; + +// @public +export function getIndicesAbove(below: string, n: number): string[]; + +// @public +export function getIndicesBelow(above: string, n: number): string[]; + +// @public +export function getIndicesBetween(below: string | undefined, above: string | undefined, n: number): string[]; + +// @public (undocumented) export function getPointerInfo(e: PointerEvent | React.PointerEvent, container: HTMLElement): { point: { x: number; @@ -1005,51 +843,43 @@ export function getPointerInfo(e: PointerEvent | React.PointerEvent, container: }; // @public -export function getResizedImageDataUrl(dataURLForImage: string, width: number, height: number): Promise; +export function getPointOnCircle(cx: number, cy: number, r: number, a: number): Vec2d; // @public (undocumented) -export function getRotatedBoxShadow(rotation: number): string; +export function getPolygonVertices(width: number, height: number, sides: number): Vec2d[]; -// @public (undocumented) -export function getSplineForLineShape(shape: TLLineShape): NonNullable; - -// @public (undocumented) -export function getSvgAsDataUrl(svg: SVGElement): Promise; - -// @public (undocumented) -export function getSvgAsDataUrlSync(node: SVGElement): string; - -// @public (undocumented) -export function getSvgAsImage(svg: SVGElement, options: { - type: TLCopyType | TLExportType; - quality: number; - scale: number; -}): Promise; - -// @public (undocumented) -export function getSvgAsString(svg: SVGElement): string; +// @internal (undocumented) +export function getRotationSnapshot({ editor }: { + editor: Editor; +}): null | TLRotationSnapshot; // @public -export function getSvgPathFromStroke(points: Vec2d[], closed?: boolean): string; - -// @public -export function getSvgPathFromStrokePoints(points: StrokePoint[], closed?: boolean): string; +export function getSolidCurvedArrowPath(info: ArrowInfo & { + isStraight: false; +}): string; // @public (undocumented) -export function getTextBoundingBox(text: SVGTextElement): DOMRect; +export function getSolidStraightArrowPath(info: ArrowInfo & { + isStraight: true; +}): string; + +// @public +export const getStarBounds: (sides: number, w: number, h: number) => Box2d; + +// @public (undocumented) +export function getStraightArrowHandlePath(info: ArrowInfo & { + isStraight: true; +}): string; + +// @public +export function getSvgPathFromPoints(points: VecLike[], closed?: boolean): string; + +// @public +export function getSweep(C: VecLike, A: VecLike, B: VecLike): number; // @public (undocumented) export function getUserPreferences(): TLUserPreferences; -// @public (undocumented) -export const getValidHttpURLList: (url: string) => string[] | undefined; - -// @public -export function getVideoSizeFromSrc(src: string): Promise<{ - w: number; - h: number; -}>; - // @internal (undocumented) export const GRID_INCREMENT = 5; @@ -1060,9 +890,6 @@ export const GRID_STEPS: { step: number; }[]; -// @public (undocumented) -export const GroupShape: TLShapeInfo; - // @public (undocumented) export class GroupShapeUtil extends ShapeUtil { // (undocumented) @@ -1072,28 +899,23 @@ export class GroupShapeUtil extends ShapeUtil { // (undocumented) getBounds(shape: TLGroupShape): Box2d; // (undocumented) - getCenter(shape: TLGroupShape): Vec2d; - // (undocumented) getDefaultProps(): TLGroupShape['props']; // (undocumented) - getOutline(shape: TLGroupShape): Vec2d[]; - // (undocumented) hideSelectionBoundsBg: () => boolean; // (undocumented) hideSelectionBoundsFg: () => boolean; // (undocumented) indicator(shape: TLGroupShape): JSX.Element; // (undocumented) + static migrations: Migrations; + // (undocumented) onChildrenChange: TLOnChildrenChangeHandler; // (undocumented) - static type: "group"; + static props: ShapeProps; // (undocumented) - type: "group"; + static type: "group"; } -// @internal (undocumented) -export const HAND_TOOL_FRICTION = 0.09; - // @public export function hardReset({ shouldReload }?: { shouldReload?: boolean | undefined; @@ -1105,146 +927,32 @@ export function hardResetEditor(): void; // @internal (undocumented) export const HASH_PATTERN_ZOOM_NAMES: Record; -// @public (undocumented) -export const HighlightShape: TLShapeInfo; - -// @public (undocumented) -export class HighlightShapeUtil extends ShapeUtil { - // (undocumented) - backgroundComponent(shape: TLHighlightShape): JSX.Element; - // (undocumented) - component(shape: TLHighlightShape): JSX.Element; - // (undocumented) - expandSelectionOutlinePx(shape: TLHighlightShape): number; - // (undocumented) - getBounds(shape: TLHighlightShape): Box2d; - // (undocumented) - getCenter(shape: TLHighlightShape): Vec2d; - // (undocumented) - getDefaultProps(): TLHighlightShape['props']; - // (undocumented) - getOutline(shape: TLHighlightShape): Vec2d[]; - // (undocumented) - hideResizeHandles: (shape: TLHighlightShape) => boolean; - // (undocumented) - hideRotateHandle: (shape: TLHighlightShape) => boolean; - // (undocumented) - hideSelectionBoundsBg: (shape: TLHighlightShape) => boolean; - // (undocumented) - hideSelectionBoundsFg: (shape: TLHighlightShape) => boolean; - // (undocumented) - hitTestLineSegment(shape: TLHighlightShape, A: VecLike, B: VecLike): boolean; - // (undocumented) - hitTestPoint(shape: TLHighlightShape, point: VecLike): boolean; - // (undocumented) - indicator(shape: TLHighlightShape): JSX.Element; - // (undocumented) - onResize: TLOnResizeHandler; - // (undocumented) - toBackgroundSvg(shape: TLHighlightShape): SVGPathElement; - // (undocumented) - toSvg(shape: TLHighlightShape): SVGPathElement; - // (undocumented) - static type: "highlight"; -} - // @public (undocumented) export function HTMLContainer({ children, className, ...rest }: HTMLContainerProps): JSX.Element; // @public (undocumented) export type HTMLContainerProps = React_3.HTMLAttributes; -// @public (undocumented) -export const ImageShape: TLShapeInfo; - -// @public (undocumented) -export class ImageShapeUtil extends BaseBoxShapeUtil { - // (undocumented) - canCrop: () => boolean; - // (undocumented) - component(shape: TLImageShape): JSX.Element; - // (undocumented) - getDefaultProps(): TLImageShape['props']; - // (undocumented) - indicator(shape: TLImageShape): JSX.Element | null; - // (undocumented) - isAspectRatioLocked: () => boolean; - // (undocumented) - onDoubleClick: (shape: TLImageShape) => void; - // (undocumented) - onDoubleClickEdge: TLOnDoubleClickHandler; - // (undocumented) - toSvg(shape: TLImageShape): Promise; - // (undocumented) - static type: "image"; -} - -// @public (undocumented) -export const INDENT = " "; +// @public +export function intersectLineSegmentPolygon(a1: VecLike, a2: VecLike, points: VecLike[]): null | VecLike[]; // @public -export function isAnimated(buffer: ArrayBuffer): boolean; +export function intersectLineSegmentPolyline(a1: VecLike, a2: VecLike, points: VecLike[]): null | VecLike[]; // @public -export function isGIF(buffer: ArrayBuffer): boolean; - -// @public (undocumented) -export const isImage: (ext: string) => boolean; +export function intersectPolygonPolygon(polygonA: VecLike[], polygonB: VecLike[]): null | VecLike[]; // @public -export function isSerializable(value: any): boolean; +export function isAngleBetween(a: number, b: number, c: number): boolean; + +// @public +export const isSafeFloat: (n: number) => boolean; + +// @public +export function lerpAngles(a0: number, a1: number, t: number): number; // @public (undocumented) -export const isSvgText: (text: string) => boolean; - -// @public (undocumented) -export const isValidHttpURL: (url: string) => boolean; - -// @public (undocumented) -export function isValidUrl(url: string): boolean; - -// @public (undocumented) -export const LineShape: TLShapeInfo; - -// @public (undocumented) -export class LineShapeUtil extends ShapeUtil { - // (undocumented) - component(shape: TLLineShape): JSX.Element | undefined; - // (undocumented) - getBounds(shape: TLLineShape): Box2d; - // (undocumented) - getDefaultProps(): TLLineShape['props']; - // (undocumented) - getHandles(shape: TLLineShape): TLHandle[]; - // (undocumented) - getOutline(shape: TLLineShape): Vec2d[]; - // (undocumented) - getOutlineSegments(shape: TLLineShape): Vec2d[][]; - // (undocumented) - hideResizeHandles: () => boolean; - // (undocumented) - hideRotateHandle: () => boolean; - // (undocumented) - hideSelectionBoundsBg: () => boolean; - // (undocumented) - hideSelectionBoundsFg: () => boolean; - // (undocumented) - hitTestLineSegment(shape: TLLineShape, A: VecLike, B: VecLike): boolean; - // (undocumented) - hitTestPoint(shape: TLLineShape, point: Vec2d): boolean; - // (undocumented) - indicator(shape: TLLineShape): JSX.Element; - // (undocumented) - isClosed: () => boolean; - // (undocumented) - onHandleChange: TLOnHandleChangeHandler; - // (undocumented) - onResize: TLOnResizeHandler; - // (undocumented) - toSvg(shape: TLLineShape): SVGGElement; - // (undocumented) - static type: "line"; -} +export function linesIntersect(A: VecLike, B: VecLike, C: VecLike, D: VecLike): boolean; // @public (undocumented) export function LoadingScreen({ children }: { @@ -1254,6 +962,9 @@ export function LoadingScreen({ children }: { // @public export function loadSessionStateSnapshotIntoStore(store: TLStore, snapshot: TLSessionStateSnapshot): void; +// @public +export function longAngleDist(a0: number, a1: number): number; + // @public (undocumented) export function loopToHtmlElement(elm: Element): HTMLElement; @@ -1261,386 +972,100 @@ export function loopToHtmlElement(elm: Element): HTMLElement; export const MAJOR_NUDGE_FACTOR = 10; // @public (undocumented) -export function matchEmbedUrl(url: string): { - definition: { - readonly type: "codepen"; - readonly title: "Codepen"; - readonly hostnames: readonly ["codepen.io"]; - readonly minWidth: 300; - readonly minHeight: 300; - readonly width: 520; - readonly height: 400; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "codesandbox"; - readonly title: "CodeSandbox"; - readonly hostnames: readonly ["codesandbox.io"]; - readonly minWidth: 300; - readonly minHeight: 300; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "excalidraw"; - readonly title: "Excalidraw"; - readonly hostnames: readonly ["excalidraw.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly isAspectRatioLocked: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "felt"; - readonly title: "Felt"; - readonly hostnames: readonly ["felt.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "figma"; - readonly title: "Figma"; - readonly hostnames: readonly ["figma.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "github_gist"; - readonly title: "GitHub Gist"; - readonly hostnames: readonly ["gist.github.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "google_calendar"; - readonly title: "Google Calendar"; - readonly hostnames: readonly ["calendar.google.*"]; - readonly width: 720; - readonly height: 500; - readonly minWidth: 460; - readonly minHeight: 360; - readonly doesResize: true; - readonly canUnmount: false; - readonly instructionLink: "https://support.google.com/calendar/answer/41207?hl=en"; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "google_maps"; - readonly title: "Google Maps"; - readonly hostnames: readonly ["google.*"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "google_slides"; - readonly title: "Google Slides"; - readonly hostnames: readonly ["docs.google.*"]; - readonly width: 720; - readonly height: 500; - readonly minWidth: 460; - readonly minHeight: 360; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "observable"; - readonly title: "Observable"; - readonly hostnames: readonly ["observablehq.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly isAspectRatioLocked: false; - readonly backgroundColor: "#fff"; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "replit"; - readonly title: "Replit"; - readonly hostnames: readonly ["replit.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "scratch"; - readonly title: "Scratch"; - readonly hostnames: readonly ["scratch.mit.edu"]; - readonly width: 520; - readonly height: 400; - readonly doesResize: false; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "spotify"; - readonly title: "Spotify"; - readonly hostnames: readonly ["open.spotify.com"]; - readonly width: 720; - readonly height: 500; - readonly minHeight: 500; - readonly overrideOutlineRadius: 12; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "tldraw"; - readonly title: "tldraw"; - readonly hostnames: readonly ["beta.tldraw.com", "lite.tldraw.com", "www.tldraw.com"]; - readonly minWidth: 300; - readonly minHeight: 300; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "vimeo"; - readonly title: "Vimeo"; - readonly hostnames: readonly ["vimeo.com", "player.vimeo.com"]; - readonly width: 640; - readonly height: 360; - readonly doesResize: true; - readonly canUnmount: false; - readonly isAspectRatioLocked: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "youtube"; - readonly title: "YouTube"; - readonly hostnames: readonly ["*.youtube.com", "youtube.com", "youtu.be"]; - readonly width: 800; - readonly height: 450; - readonly doesResize: true; - readonly canUnmount: false; - readonly overridePermissions: { - readonly 'allow-presentation': true; - }; - readonly isAspectRatioLocked: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; +export class Matrix2d { + constructor(a: number, b: number, c: number, d: number, e: number, f: number); + // (undocumented) + a: number; + // (undocumented) + static Absolute(m: MatLike): Matrix2dModel; + // (undocumented) + static applyToBounds(m: MatLike, box: Box2d): Box2d; + // (undocumented) + applyToPoint(point: VecLike): Vec2d; + // (undocumented) + static applyToPoint(m: MatLike, point: VecLike): Vec2d; + // (undocumented) + applyToPoints(points: VecLike[]): Vec2d[]; + // (undocumented) + static applyToPoints(m: MatLike, points: VecLike[]): Vec2d[]; + // (undocumented) + static applyToXY(m: MatLike, x: number, y: number): number[]; + // (undocumented) + b: number; + // (undocumented) + c: number; + // (undocumented) + clone(): Matrix2d; + // (undocumented) + static Compose(...matrices: MatLike[]): Matrix2d; + // (undocumented) + d: number; + // (undocumented) + static Decompose(m: MatLike): MatrixInfo; + // (undocumented) + decompose(): MatrixInfo; + // (undocumented) + decomposed(): MatrixInfo; + // (undocumented) + e: number; + // (undocumented) + equals(m: Matrix2d | Matrix2dModel): boolean; + // (undocumented) + f: number; + // (undocumented) + static From(m: MatLike): Matrix2d; + // (undocumented) + static Identity(): Matrix2d; + // (undocumented) + identity(): this; + // (undocumented) + static Inverse(m: Matrix2dModel): Matrix2dModel; + // (undocumented) + invert(): this; + // (undocumented) + static Multiply(m1: Matrix2dModel, m2: Matrix2dModel): Matrix2dModel; + // (undocumented) + multiply(m: Matrix2d | Matrix2dModel): this; + // (undocumented) + static Rotate(r: number, cx?: number, cy?: number): Matrix2d; + // (undocumented) + rotate(r: number, cx?: number, cy?: number): Matrix2d; + // (undocumented) + static Scale: { + (x: number, y: number): Matrix2dModel; + (x: number, y: number, cx: number, cy: number): Matrix2dModel; }; - url: string; - embedUrl: string; -} | undefined; + // (undocumented) + scale(x: number, y: number): this; + // (undocumented) + setTo(model: Matrix2dModel): this; + // (undocumented) + static Smooth(m: MatLike, precision?: number): MatLike; + // (undocumented) + toCssString(): string; + // (undocumented) + static toCssString(m: MatLike): string; + // (undocumented) + static Translate(x: number, y: number): Matrix2d; + // (undocumented) + translate(x: number, y: number): Matrix2d; +} // @public (undocumented) -export function matchUrl(url: string): { - definition: { - readonly type: "codepen"; - readonly title: "Codepen"; - readonly hostnames: readonly ["codepen.io"]; - readonly minWidth: 300; - readonly minHeight: 300; - readonly width: 520; - readonly height: 400; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "codesandbox"; - readonly title: "CodeSandbox"; - readonly hostnames: readonly ["codesandbox.io"]; - readonly minWidth: 300; - readonly minHeight: 300; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "excalidraw"; - readonly title: "Excalidraw"; - readonly hostnames: readonly ["excalidraw.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly isAspectRatioLocked: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "felt"; - readonly title: "Felt"; - readonly hostnames: readonly ["felt.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "figma"; - readonly title: "Figma"; - readonly hostnames: readonly ["figma.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "github_gist"; - readonly title: "GitHub Gist"; - readonly hostnames: readonly ["gist.github.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "google_calendar"; - readonly title: "Google Calendar"; - readonly hostnames: readonly ["calendar.google.*"]; - readonly width: 720; - readonly height: 500; - readonly minWidth: 460; - readonly minHeight: 360; - readonly doesResize: true; - readonly canUnmount: false; - readonly instructionLink: "https://support.google.com/calendar/answer/41207?hl=en"; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "google_maps"; - readonly title: "Google Maps"; - readonly hostnames: readonly ["google.*"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "google_slides"; - readonly title: "Google Slides"; - readonly hostnames: readonly ["docs.google.*"]; - readonly width: 720; - readonly height: 500; - readonly minWidth: 460; - readonly minHeight: 360; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "observable"; - readonly title: "Observable"; - readonly hostnames: readonly ["observablehq.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly isAspectRatioLocked: false; - readonly backgroundColor: "#fff"; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "replit"; - readonly title: "Replit"; - readonly hostnames: readonly ["replit.com"]; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "scratch"; - readonly title: "Scratch"; - readonly hostnames: readonly ["scratch.mit.edu"]; - readonly width: 520; - readonly height: 400; - readonly doesResize: false; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "spotify"; - readonly title: "Spotify"; - readonly hostnames: readonly ["open.spotify.com"]; - readonly width: 720; - readonly height: 500; - readonly minHeight: 500; - readonly overrideOutlineRadius: 12; - readonly doesResize: true; - readonly canUnmount: false; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "tldraw"; - readonly title: "tldraw"; - readonly hostnames: readonly ["beta.tldraw.com", "lite.tldraw.com", "www.tldraw.com"]; - readonly minWidth: 300; - readonly minHeight: 300; - readonly width: 720; - readonly height: 500; - readonly doesResize: true; - readonly canUnmount: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "vimeo"; - readonly title: "Vimeo"; - readonly hostnames: readonly ["vimeo.com", "player.vimeo.com"]; - readonly width: 640; - readonly height: 360; - readonly doesResize: true; - readonly canUnmount: false; - readonly isAspectRatioLocked: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - } | { - readonly type: "youtube"; - readonly title: "YouTube"; - readonly hostnames: readonly ["*.youtube.com", "youtube.com", "youtu.be"]; - readonly width: 800; - readonly height: 450; - readonly doesResize: true; - readonly canUnmount: false; - readonly overridePermissions: { - readonly 'allow-presentation': true; - }; - readonly isAspectRatioLocked: true; - readonly toEmbedUrl: (url: string) => string | undefined; - readonly fromEmbedUrl: (url: string) => string | undefined; - }; - embedUrl: string; - url: string; -} | undefined; - -// @internal (undocumented) -export const MAX_ASSET_HEIGHT = 1000; - -// @internal (undocumented) -export const MAX_ASSET_WIDTH = 1000; +export interface Matrix2dModel { + // (undocumented) + a: number; + // (undocumented) + b: number; + // (undocumented) + c: number; + // (undocumented) + d: number; + // (undocumented) + e: number; + // (undocumented) + f: number; +} // @internal (undocumented) export const MAX_PAGES = 40; @@ -1660,96 +1085,13 @@ export const MINOR_NUDGE_FACTOR = 1; // @internal (undocumented) export const MULTI_CLICK_DURATION = 200; -// @public (undocumented) +// @internal (undocumented) export function normalizeWheel(event: React.WheelEvent | WheelEvent): { x: number; y: number; z: number; }; -// @public (undocumented) -export const NoteShape: TLShapeInfo; - -// @public (undocumented) -export class NoteShapeUtil extends ShapeUtil { - // (undocumented) - canEdit: () => boolean; - // (undocumented) - component(shape: TLNoteShape): JSX.Element; - // (undocumented) - getBounds(shape: TLNoteShape): Box2d; - // (undocumented) - getCenter(_shape: TLNoteShape): Vec2d; - // (undocumented) - getDefaultProps(): TLNoteShape['props']; - // (undocumented) - getHeight(shape: TLNoteShape): number; - // (undocumented) - getOutline(shape: TLNoteShape): Vec2d[]; - // (undocumented) - hideResizeHandles: () => boolean; - // (undocumented) - hideSelectionBoundsBg: () => boolean; - // (undocumented) - hideSelectionBoundsFg: () => boolean; - // (undocumented) - indicator(shape: TLNoteShape): JSX.Element; - // (undocumented) - onBeforeCreate: (next: TLNoteShape) => { - props: { - growY: number; - color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; - size: "l" | "m" | "s" | "xl"; - font: "draw" | "mono" | "sans" | "serif"; - align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start"; - verticalAlign: "end" | "middle" | "start"; - url: string; - text: string; - }; - type: "note"; - x: number; - y: number; - rotation: number; - index: string; - parentId: TLParentId; - isLocked: boolean; - opacity: number; - meta: JsonObject; - id: TLShapeId; - typeName: "shape"; - } | undefined; - // (undocumented) - onBeforeUpdate: (prev: TLNoteShape, next: TLNoteShape) => { - props: { - growY: number; - color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; - size: "l" | "m" | "s" | "xl"; - font: "draw" | "mono" | "sans" | "serif"; - align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start"; - verticalAlign: "end" | "middle" | "start"; - url: string; - text: string; - }; - type: "note"; - x: number; - y: number; - rotation: number; - index: string; - parentId: TLParentId; - isLocked: boolean; - opacity: number; - meta: JsonObject; - id: TLShapeId; - typeName: "shape"; - } | undefined; - // (undocumented) - onEditEnd: TLOnEditEndHandler; - // (undocumented) - toSvg(shape: TLNoteShape, ctx: SvgExportContext): SVGGElement; - // (undocumented) - static type: "note"; -} - // @public (undocumented) export function openWindow(url: string, target?: string): void; @@ -1758,37 +1100,77 @@ export function OptionalErrorBoundary({ children, fallback, ...props }: Omit; - createAssetFromUrl(_editor: Editor, url: string): Promise; - // (undocumented) - createShapesForAssets(editor: Editor, assets: TLAsset[], position: VecLike): Promise; - // (undocumented) - editor: Editor; - // (undocumented) - handleContent: (info: TLExternalContent) => Promise; - handleEmbed(editor: Editor, { point, url, embed }: Extract): Promise; - handleFiles(editor: Editor, { point, files }: Extract): Promise; - handleSvgText(editor: Editor, { point, text }: Extract): Promise; - handleText(editor: Editor, { point, text }: Extract): Promise; - handleUrl: (editor: Editor, { point, url }: Extract) => Promise; -} +export const PI: number; + +// @public (undocumented) +export const PI2: number; + +// @public (undocumented) +export const png: { + isPng: typeof isPng; + readChunks: typeof readChunks; + parsePhys: typeof parsePhys; + findChunk: typeof findChunk; + setPhysChunk: typeof setPhysChunk; +}; + +// @public +export function pointInBounds(A: VecLike, b: Box2d): boolean; + +// @public +export function pointInCircle(A: VecLike, C: VecLike, r: number): boolean; + +// @public +export function pointInEllipse(A: VecLike, C: VecLike, rx: number, ry: number, rotation?: number): boolean; + +// @public +export function pointInPolygon(A: VecLike, points: VecLike[]): boolean; + +// @public +export function pointInPolyline(A: VecLike, points: VecLike[], distance?: number): boolean; + +// @public +export function pointInRect(A: VecLike, point: VecLike, size: VecLike): boolean; + +// @public +export function pointNearToLineSegment(A: VecLike, p1: VecLike, p2: VecLike, distance?: number): boolean; + +// @public +export function pointNearToPolyline(A: VecLike, points: VecLike[], distance?: number): boolean; + +// @public (undocumented) +export type PointsSnapLine = { + id: string; + type: 'points'; + points: VecLike[]; +}; + +// @public (undocumented) +export function polygonsIntersect(a: VecLike[], b: VecLike[]): boolean; + +// @public (undocumented) +export const PositionedOnCanvas: MemoExoticComponent<({ x: offsetX, y: offsetY, rotation, ...rest }: { +x?: number | undefined; +y?: number | undefined; +rotation?: number | undefined; +} & HTMLProps) => JSX.Element>; + +// @public (undocumented) +export function precise(A: VecLike): string; // @public export function preventDefault(event: Event | React_2.BaseSyntheticEvent): void; +// @public +export function radiansToDegrees(r: number): number; + +// @public +export function rangeIntersection(a0: number, a1: number, b0: number, b1: number): [number, number] | null; + export { react } // @public @@ -1820,9 +1202,6 @@ export function refreshPage(): void; // @public (undocumented) export function releasePointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent): void; -// @internal (undocumented) -export const REMOVE_SYMBOL: unique symbol; - // @public (undocumented) export type RequiredKeys = Pick & Partial; @@ -1857,8 +1236,20 @@ export type ResizeBoxOptions = Partial<{ maxHeight: number; }>; -// @internal (undocumented) -export const RICH_TYPES: Record; +// @public (undocumented) +export const ROTATE_CORNER_TO_SELECTION_CORNER: { + readonly top_left_rotate: "top_left"; + readonly top_right_rotate: "top_right"; + readonly bottom_right_rotate: "bottom_right"; + readonly bottom_left_rotate: "bottom_left"; + readonly mobile_rotate: "top_left"; +}; + +// @public (undocumented) +export type RotateCorner = 'bottom_left_rotate' | 'bottom_right_rotate' | 'mobile_rotate' | 'top_left_rotate' | 'top_right_rotate'; + +// @public (undocumented) +export function rotateSelectionHandle(handle: SelectionHandle, rotation: number): SelectionHandle; // @public (undocumented) export const runtime: { @@ -1868,44 +1259,13 @@ export const runtime: { }; // @public (undocumented) -export class ScribbleManager implements TLScribble { - constructor(opts: { - onUpdate: (scribble: TLScribble) => void; - onComplete: () => void; - size?: TLScribble['size']; - color?: TLScribble['color']; - opacity?: TLScribble['opacity']; - delay?: TLScribble['delay']; - }); - addPoint: (x: number, y: number) => void; - // (undocumented) - color: "accent" | "black" | "laser" | "muted-1" | "selection-fill" | "selection-stroke" | "white"; - // (undocumented) - delay: number; - // (undocumented) - delayRemaining: number; - getScribble(): TLScribble; - // (undocumented) - opacity: number; - // (undocumented) - pause: () => void; - // (undocumented) - points: Vec2dModel[]; - // (undocumented) - resume: () => void; - // (undocumented) - size: number; - // (undocumented) - state: "active" | "paused" | "starting" | "stopping"; - stop: () => void; - // (undocumented) - tick: TLTickEvent; - // (undocumented) - timeoutMs: number; -} +export type SelectionCorner = 'bottom_left' | 'bottom_right' | 'top_left' | 'top_right'; -// @internal (undocumented) -export function setDefaultEditorAssetUrls(assetUrls: TLEditorAssetUrls): void; +// @public (undocumented) +export type SelectionEdge = 'bottom' | 'left' | 'right' | 'top'; + +// @public (undocumented) +export type SelectionHandle = SelectionCorner | SelectionEdge; // @public (undocumented) export function setPointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent): void; @@ -1918,7 +1278,7 @@ export function setUserPreferences(user: TLUserPreferences): void; // @public (undocumented) export abstract class ShapeUtil { - constructor(editor: Editor, type: Shape['type'], styleProps: ReadonlyMap, string>); + constructor(editor: Editor); // @internal backgroundComponent?(shape: Shape): any; canBind: (_shape: Shape, _otherShape?: K | undefined) => boolean; @@ -1952,6 +1312,8 @@ export abstract class ShapeUtil { abstract indicator(shape: Shape): any; isAspectRatioLocked: TLShapeUtilFlag; isClosed: TLShapeUtilFlag; + // (undocumented) + static migrations?: Migrations; onBeforeCreate?: TLOnBeforeCreateHandler; onBeforeUpdate?: TLOnBeforeUpdateHandler; // @internal @@ -1977,17 +1339,13 @@ export abstract class ShapeUtil { onTranslate?: TLOnTranslateHandler; onTranslateEnd?: TLOnTranslateEndHandler; onTranslateStart?: TLOnTranslateStartHandler; + // (undocumented) + static props?: ShapeProps; // @internal providesBackgroundForChildren(shape: Shape): boolean; - // (undocumented) - setStyleInPartial(style: StyleProp, shape: TLShapePartial, value: T): TLShapePartial; snapPoints(shape: Shape): Vec2d[]; - // (undocumented) - readonly styleProps: ReadonlyMap, string>; toBackgroundSvg?(shape: Shape, ctx: SvgExportContext): null | Promise | SVGElement; toSvg?(shape: Shape, ctx: SvgExportContext): Promise | SVGElement; - // (undocumented) - readonly type: Shape['type']; static type: string; } @@ -2007,8 +1365,86 @@ export class SharedStyleMap extends ReadonlySharedStyleMap { set(prop: StyleProp, value: SharedStyle): void; } +// @public +export function shortAngleDist(a0: number, a1: number): number; + +export { Signal } + // @public (undocumented) -export function snapToGrid(n: number, gridSize: number): number; +export const SIN: (x: number) => number; + +// @public +export function snapAngle(r: number, segments: number): number; + +// @public (undocumented) +export type SnapLine = GapsSnapLine | PointsSnapLine; + +// @public (undocumented) +export class SnapManager { + constructor(editor: Editor); + // (undocumented) + clear(): void; + // (undocumented) + get currentCommonAncestor(): TLShapeId | undefined; + // (undocumented) + readonly editor: Editor; + // (undocumented) + getSnappingHandleDelta({ handlePoint, additionalSegments, }: { + handlePoint: Vec2d; + additionalSegments: Vec2d[][]; + }): null | Vec2d; + // (undocumented) + get lines(): SnapLine[]; + // (undocumented) + get outlinesInPageSpace(): Vec2d[][]; + // (undocumented) + setLines(lines: SnapLine[]): void; + // (undocumented) + get snappablePoints(): SnapPoint[]; + // (undocumented) + get snappableShapes(): GapNode[]; + // (undocumented) + get snapPointsCache(): ComputedCache; + // (undocumented) + snapResize({ initialSelectionPageBounds, dragDelta, handle: originalHandle, isAspectRatioLocked, isResizingFromCenter, }: { + initialSelectionPageBounds: Box2d; + dragDelta: Vec2d; + handle: SelectionCorner | SelectionEdge; + isAspectRatioLocked: boolean; + isResizingFromCenter: boolean; + }): SnapData; + // (undocumented) + get snapThreshold(): number; + // (undocumented) + snapTranslate({ lockedAxis, initialSelectionPageBounds, initialSelectionSnapPoints, dragDelta, }: { + lockedAxis: 'x' | 'y' | null; + initialSelectionSnapPoints: SnapPoint[]; + initialSelectionPageBounds: Box2d; + dragDelta: Vec2d; + }): SnapData; + // (undocumented) + get visibleGaps(): { + horizontal: Gap[]; + vertical: Gap[]; + }; +} + +// @public (undocumented) +export interface SnapPoint { + // (undocumented) + handle?: SelectionCorner; + // (undocumented) + id: string; + // (undocumented) + x: number; + // (undocumented) + y: number; +} + +// @public +export function sortByIndex(a: T, b: T): -1 | 0 | 1; // @public (undocumented) export abstract class StateNode implements Partial { @@ -2020,6 +1456,10 @@ export abstract class StateNode implements Partial { // (undocumented) current: Atom; // (undocumented) + get currentToolIdMask(): string | undefined; + set currentToolIdMask(id: string | undefined); + _currentToolIdMask: Atom; + // (undocumented) editor: Editor; // (undocumented) enter(info: any, from: string): void; @@ -2087,6 +1527,9 @@ export abstract class StateNode implements Partial { type: TLStateNodeType; } +// @public (undocumented) +export const stopEventPropagation: (e: any) => any; + // @internal (undocumented) export const SVG_PADDING = 32; @@ -2096,106 +1539,24 @@ export function SVGContainer({ children, className, ...rest }: SVGContainerProps // @public (undocumented) export type SVGContainerProps = React_3.HTMLAttributes; +// @public (undocumented) +export interface SvgExportContext { + addExportDef(def: SvgExportDef): void; +} + +// @public (undocumented) +export interface SvgExportDef { + // (undocumented) + getElement: () => null | Promise | SVGElement | SVGElement[]; + // (undocumented) + key: string; +} + // @public export const TAB_ID: string; // @public (undocumented) -export const TextShape: TLShapeInfo; - -// @public (undocumented) -export class TextShapeUtil extends ShapeUtil { - // (undocumented) - canEdit: () => boolean; - // (undocumented) - component(shape: TLTextShape): JSX.Element; - // (undocumented) - getBounds(shape: TLTextShape): Box2d; - // (undocumented) - getDefaultProps(): TLTextShape['props']; - // (undocumented) - getMinDimensions(shape: TLTextShape): { - height: number; - width: number; - }; - // (undocumented) - getOutline(shape: TLTextShape): Vec2d[]; - // (undocumented) - indicator(shape: TLTextShape): JSX.Element; - // (undocumented) - isAspectRatioLocked: TLShapeUtilFlag; - // (undocumented) - onBeforeCreate: (shape: TLTextShape) => { - x: number; - y: number; - type: "text"; - rotation: number; - index: string; - parentId: TLParentId; - isLocked: boolean; - opacity: number; - props: { - color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; - size: "l" | "m" | "s" | "xl"; - font: "draw" | "mono" | "sans" | "serif"; - align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start"; - w: number; - text: string; - scale: number; - autoSize: boolean; - }; - meta: JsonObject; - id: TLShapeId; - typeName: "shape"; - } | undefined; - // (undocumented) - onBeforeUpdate: (prev: TLTextShape, next: TLTextShape) => { - x: number; - y: number; - props: { - w: number; - color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow"; - size: "l" | "m" | "s" | "xl"; - font: "draw" | "mono" | "sans" | "serif"; - align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start"; - text: string; - scale: number; - autoSize: boolean; - }; - type: "text"; - rotation: number; - index: string; - parentId: TLParentId; - isLocked: boolean; - opacity: number; - meta: JsonObject; - id: TLShapeId; - typeName: "shape"; - } | undefined; - // (undocumented) - onDoubleClickEdge: (shape: TLTextShape) => { - id: TLShapeId; - type: "text"; - props: { - autoSize: boolean; - scale?: undefined; - }; - } | { - id: TLShapeId; - type: "text"; - props: { - scale: number; - autoSize?: undefined; - }; - } | undefined; - // (undocumented) - onEditEnd: TLOnEditEndHandler; - // (undocumented) - onResize: TLOnResizeHandler; - // (undocumented) - toSvg(shape: TLTextShape, ctx: SvgExportContext): SVGGElement; - // (undocumented) - static type: "text"; -} +export const TAU: number; // @public (undocumented) export type TLAnimationOptions = Partial<{ @@ -2203,6 +1564,9 @@ export type TLAnimationOptions = Partial<{ easing: typeof EASINGS.easeInOutCubic; }>; +// @public (undocumented) +export type TLAnyShapeUtilConstructor = TLShapeUtilConstructor; + // @public (undocumented) export type TLBaseBoxShape = TLBaseShape; // @public export interface TldrawEditorBaseProps { - assetUrls?: RecursivePartial; autoFocus?: boolean; children?: any; components?: Partial; + initialState?: string; onMount?: TLOnMountHandler; - shapes?: readonly AnyTLShapeInfo[]; + shapeUtils?: readonly TLAnyShapeUtilConstructor[]; tools?: readonly TLStateNodeConstructor[]; } @@ -2313,71 +1674,25 @@ export type TldrawEditorProps = TldrawEditorBaseProps & ({ }); // @public (undocumented) -export type TLEditorAssetUrls = { - fonts: { - monospace: string; - serif: string; - sansSerif: string; - draw: string; - }; -}; - -// @public (undocumented) -export interface TLEditorComponents { - // (undocumented) - Background: null | TLBackgroundComponent; - // (undocumented) - Brush: null | TLBrushComponent; - // (undocumented) - CollaboratorBrush: null | TLBrushComponent; - // (undocumented) - CollaboratorCursor: null | TLCursorComponent; - // (undocumented) - CollaboratorHint: null | TLCollaboratorHintComponent; - // (undocumented) - CollaboratorScribble: null | TLScribbleComponent; - // (undocumented) - CollaboratorShapeIndicator: null | TLShapeIndicatorComponent; - // (undocumented) - Cursor: null | TLCursorComponent; - // (undocumented) +export type TLEditorComponents = { + [K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null; +} & { ErrorFallback: TLErrorFallbackComponent; - // (undocumented) - Grid: null | TLGridComponent; - // (undocumented) - Handle: null | TLHandleComponent; - // (undocumented) - Scribble: null | TLScribbleComponent; - // (undocumented) ShapeErrorFallback: TLShapeErrorFallbackComponent; - // (undocumented) - ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallback; - // (undocumented) - SnapLine: null | TLSnapLineComponent; - // (undocumented) - Spinner: null | TLSpinnerComponent; - // (undocumented) - SvgDefs: null | TLSvgDefsComponent; - // (undocumented) - ZoomBrush: null | TLBrushComponent; -} + ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallbackComponent; +}; // @public (undocumented) export interface TLEditorOptions { getContainer: () => HTMLElement; - shapes: readonly AnyTLShapeInfo[]; + // (undocumented) + initialState?: string; + shapeUtils: readonly TLShapeUtilConstructor[]; store: TLStore; tools: readonly TLStateNodeConstructor[]; user?: TLUser; } -// @public (undocumented) -export type TLEmbedResult = { - definition: EmbedDefinition; - url: string; - embedUrl: string; -} | undefined; - // @public (undocumented) export type TLEnterEventHandler = (info: any, from: string) => void; @@ -2386,9 +1701,7 @@ export interface TLErrorBoundaryProps { // (undocumented) children: React_3.ReactNode; // (undocumented) - fallback: (props: { - error: unknown; - }) => any; + fallback: TLErrorFallbackComponent; // (undocumented) onError?: ((error: unknown) => void) | null; } @@ -2485,7 +1798,13 @@ export type TLEventName = 'cancel' | 'complete' | 'interrupt' | 'wheel' | TLCLic export type TLExitEventHandler = (info: any, to: string) => void; // @public (undocumented) -export type TLExportType = 'jpeg' | 'json' | 'png' | 'svg' | 'webp'; +export type TLExternalAssetContent = { + type: 'file'; + file: File; +} | { + type: 'url'; + url: string; +}; // @public (undocumented) export type TLExternalContent = { @@ -2673,6 +1992,17 @@ export type TLResizeInfo = { // @public export type TLResizeMode = 'resize_bounds' | 'scale_shape'; +// @public +export type TLRotationSnapshot = { + selectionPageCenter: Vec2d; + initialCursorAngle: number; + initialSelectionRotation: number; + shapeSnapshots: { + shape: TLShape; + initialPagePoint: Vec2d; + }[]; +}; + // @public (undocumented) export type TLSelectionHandle = RotateCorner | SelectionCorner | SelectionEdge; @@ -2706,17 +2036,21 @@ export interface TLSessionStateSnapshot { } // @public (undocumented) -export type TLShapeInfo = { - type: T['type']; - util: TLShapeUtilConstructor; - props?: ShapeProps; - migrations?: Migrations; -}; +export interface TLShapeUtilCanvasSvgDef { + // (undocumented) + component: React.ComponentType; + // (undocumented) + key: string; +} // @public (undocumented) export interface TLShapeUtilConstructor = ShapeUtil> { // (undocumented) - new (editor: Editor, type: T['type'], styleProps: ReadonlyMap, string>): U; + new (editor: Editor): U; + // (undocumented) + migrations?: Migrations; + // (undocumented) + props?: ShapeProps; // (undocumented) type: T['type']; } @@ -2746,7 +2080,7 @@ export type TLStoreOptions = { } & ({ schema: StoreSchema; } | { - shapes: readonly AnyTLShapeInfo[]; + shapeUtils: readonly TLAnyShapeUtilConstructor[]; }); // @public (undocumented) @@ -2804,10 +2138,20 @@ export type TLWheelEventInfo = TLBaseEventInfo & { delta: Vec2dModel; }; -export { track } +// @public +export function toDomPrecision(v: number): number; // @public (undocumented) -export const truncateStringWithEllipsis: (str: string, maxLength: number) => string; +export function toFixed(v: number): number; + +// @public +export function toPrecision(n: number, precision?: number): number; + +export { track } + +export { transact } + +export { transaction } // @public (undocumented) export type UiEvent = TLCancelEvent | TLClickEvent | TLCompleteEvent | TLKeyboardEvent | TLPinchEvent | TLPointerEvent; @@ -2815,6 +2159,12 @@ export type UiEvent = TLCancelEvent | TLClickEvent | TLCompleteEvent | TLKeyboar // @public (undocumented) export type UiEventType = 'click' | 'keyboard' | 'pinch' | 'pointer' | 'wheel' | 'zoom'; +// @public (undocumented) +export function uniq(array: { + readonly length: number; + readonly [n: number]: T; +} | null | undefined): T[]; + // @public export function uniqueId(): string; @@ -2826,6 +2176,12 @@ export function useContainer(): HTMLDivElement; // @public (undocumented) export const useEditor: () => Editor; +// @public (undocumented) +export function useIsCropping(shapeId: TLShapeId): boolean; + +// @public (undocumented) +export function useIsEditing(shapeId: TLShapeId): boolean; + // @internal (undocumented) export function useLocalStore({ persistenceKey, sessionId, ...rest }: { persistenceKey?: string; @@ -2835,9 +2191,6 @@ export function useLocalStore({ persistenceKey, sessionId, ...rest }: { // @internal (undocumented) export function usePeerIds(): string[]; -// @public (undocumented) -export function usePrefersReducedMotion(): boolean; - // @internal (undocumented) export function usePresence(userId: string): null | TLInstancePresence; @@ -2848,31 +2201,232 @@ export const USER_COLORS: readonly ["#FF802B", "#EC5E41", "#F2555A", "#F04F88", export { useReactor } +// @public (undocumented) +export function useSelectionEvents(handle: TLSelectionHandle): { + onPointerDown: PointerEventHandler; + onPointerMove: (e: React.PointerEvent) => void; + onPointerUp: PointerEventHandler; +}; + // @public (undocumented) export function useTLStore(opts: TLStoreOptions): TLStore; +// @public (undocumented) +export function useTransform(ref: React.RefObject, x?: number, y?: number, scale?: number, rotate?: number, additionalOffset?: VecLike): void; + export { useValue } // @public (undocumented) -export const VideoShape: TLShapeInfo; +export class Vec2d { + constructor(x?: number, y?: number, z?: number); + // (undocumented) + static Abs(A: VecLike): Vec2d; + // (undocumented) + abs(): this; + // (undocumented) + static Add(A: VecLike, B: VecLike): Vec2d; + // (undocumented) + add(V: VecLike): this; + // (undocumented) + static AddScalar(A: VecLike, n: number): Vec2d; + // (undocumented) + addScalar(n: number): this; + // (undocumented) + static AddXY(A: VecLike, x: number, y: number): Vec2d; + // (undocumented) + addXY(x: number, y: number): this; + // (undocumented) + static Angle(A: VecLike, B: VecLike): number; + // (undocumented) + angle(B: VecLike): number; + // (undocumented) + static Average(arr: VecLike[]): Vec2d; + // (undocumented) + static Cast(A: VecLike): Vec2d; + // (undocumented) + static Clamp(A: Vec2d, min: number, max?: number): Vec2d; + // (undocumented) + clamp(min: number, max?: number): this; + // (undocumented) + static Clockwise(A: VecLike, B: VecLike, C: VecLike): boolean; + // (undocumented) + clone(): Vec2d; + // (undocumented) + static Cpr(A: VecLike, B: VecLike): number; + // (undocumented) + cpr(V: VecLike): number; + // (undocumented) + static Cross(A: VecLike, V: VecLike): Vec2d; + // (undocumented) + cross(V: VecLike): this; + // (undocumented) + static Dist(A: VecLike, B: VecLike): number; + // (undocumented) + dist(V: VecLike): number; + // (undocumented) + static Dist2(A: VecLike, B: VecLike): number; + // (undocumented) + static DistanceToLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp?: boolean): number; + // (undocumented) + distanceToLineSegment(A: VecLike, B: VecLike): number; + // (undocumented) + static DistanceToLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): number; + // (undocumented) + static Div(A: VecLike, t: number): Vec2d; + // (undocumented) + div(t: number): this; + // (undocumented) + static DivV(A: VecLike, B: VecLike): Vec2d; + // (undocumented) + divV(V: VecLike): this; + // (undocumented) + static Dpr(A: VecLike, B: VecLike): number; + // (undocumented) + dpr(V: VecLike): number; + // (undocumented) + static Equals(A: VecLike, B: VecLike): boolean; + // (undocumented) + equals(B: VecLike): boolean; + // (undocumented) + static EqualsXY(A: VecLike, x: number, y: number): boolean; + // (undocumented) + equalsXY(x: number, y: number): boolean; + // (undocumented) + static From({ x, y, z }: Vec2dModel): Vec2d; + // (undocumented) + static FromAngle(r: number, length?: number): Vec2d; + // (undocumented) + static FromArray(v: number[]): Vec2d; + // (undocumented) + static Len(A: VecLike): number; + // (undocumented) + len(): number; + // (undocumented) + static Len2(A: VecLike): number; + // (undocumented) + len2(): number; + // (undocumented) + static Lrp(A: VecLike, B: VecLike, t: number): Vec2d; + // (undocumented) + lrp(B: VecLike, t: number): Vec2d; + // (undocumented) + static Max(A: VecLike, B: VecLike): Vec2d; + // (undocumented) + static Med(A: VecLike, B: VecLike): Vec2d; + // (undocumented) + static Min(A: VecLike, B: VecLike): Vec2d; + // (undocumented) + static Mul(A: VecLike, t: number): Vec2d; + // (undocumented) + mul(t: number): this; + // (undocumented) + static MulV(A: VecLike, B: VecLike): Vec2d; + // (undocumented) + mulV(V: VecLike): this; + // (undocumented) + static NearestPointOnLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp?: boolean): Vec2d; + static NearestPointOnLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): Vec2d; + // (undocumented) + static Neg(A: VecLike): Vec2d; + // (undocumented) + neg(): this; + // (undocumented) + norm(): this; + // (undocumented) + static Nudge(A: VecLike, B: VecLike, distance: number): Vec2d; + // (undocumented) + nudge(B: VecLike, distance: number): this; + // (undocumented) + static Per(A: VecLike): Vec2d; + // (undocumented) + per(): this; + static PointsBetween(A: Vec2dModel, B: Vec2dModel, steps?: number): Vec2d[]; + // (undocumented) + get pressure(): number; + // (undocumented) + static Pry(A: VecLike, B: VecLike): number; + // (undocumented) + pry(V: VecLike): number; + // (undocumented) + static Rescale(A: VecLike, n: number): Vec2d; + // (undocumented) + static Rot(A: VecLike, r?: number): Vec2d; + // (undocumented) + rot(r: number): this; + // (undocumented) + static RotWith(A: VecLike, C: VecLike, r: number): Vec2d; + // (undocumented) + rotWith(C: VecLike, r: number): this; + // (undocumented) + static ScaleWithOrigin(A: VecLike, scale: number, origin: VecLike): Vec2d; + // (undocumented) + set(x?: number, y?: number, z?: number): this; + // (undocumented) + setTo({ x, y, z }: VecLike): this; + // (undocumented) + static Slope(A: VecLike, B: VecLike): number; + // (undocumented) + slope(B: VecLike): number; + // (undocumented) + static Snap(A: VecLike, step?: number): Vec2d; + // (undocumented) + static SnapToGrid(A: VecLike, gridSize?: number): Vec2d; + // (undocumented) + snapToGrid(gridSize: number): this; + // (undocumented) + static Sub(A: VecLike, B: VecLike): Vec2d; + // (undocumented) + sub(V: VecLike): this; + // (undocumented) + static SubScalar(A: VecLike, n: number): Vec2d; + // (undocumented) + subScalar(n: number): this; + // (undocumented) + static SubXY(A: VecLike, x: number, y: number): Vec2d; + // (undocumented) + subXY(x: number, y: number): this; + // (undocumented) + static Tan(A: VecLike, B: VecLike): Vec2d; + // (undocumented) + tan(V: VecLike): Vec2d; + // (undocumented) + static ToAngle(A: VecLike): number; + // (undocumented) + toAngle(): number; + // (undocumented) + static ToArray(A: VecLike): number[]; + // (undocumented) + toArray(): number[]; + // (undocumented) + static ToFixed(A: VecLike, n?: number): Vec2d; + // (undocumented) + toFixed(): Vec2d; + // (undocumented) + static ToJson(A: VecLike): { + x: number; + y: number; + z: number | undefined; + }; + // (undocumented) + toJson(): Vec2dModel; + // (undocumented) + static ToString(A: VecLike): string; + // (undocumented) + toString(): string; + // (undocumented) + static Uni(A: VecLike): Vec2d; + // (undocumented) + uni(): Vec2d; + // (undocumented) + x: number; + // (undocumented) + y: number; + // (undocumented) + z: number; +} // @public (undocumented) -export class VideoShapeUtil extends BaseBoxShapeUtil { - // (undocumented) - canEdit: () => boolean; - // (undocumented) - component(shape: TLVideoShape): JSX.Element; - // (undocumented) - getDefaultProps(): TLVideoShape['props']; - // (undocumented) - indicator(shape: TLVideoShape): JSX.Element; - // (undocumented) - isAspectRatioLocked: () => boolean; - // (undocumented) - toSvg(shape: TLVideoShape): SVGGElement; - // (undocumented) - static type: "video"; -} +export type VecLike = Vec2d | Vec2dModel; // @public (undocumented) export class WeakMapCache { @@ -2898,8 +2452,10 @@ export { whyAmIRunning } export const ZOOMS: number[]; -export * from "@tldraw/indices"; +export * from "@tldraw/store"; export * from "@tldraw/tlschema"; +export * from "@tldraw/utils"; +export * from "@tldraw/validate"; // (No @packageDocumentation comment for this package) diff --git a/packages/editor/editor.css b/packages/editor/editor.css index 16f19dc7a..5f1a65f6c 100644 --- a/packages/editor/editor.css +++ b/packages/editor/editor.css @@ -245,14 +245,11 @@ input, pointer-events: none; } -.tlui-following { - display: block; +.tl-positioned { position: absolute; - inset: 0px; - border-width: 2px; - border-style: solid; - z-index: 9999999; - pointer-events: none; + top: 0px; + left: 0px; + transform-origin: top left; } /* ------------------- Background ------------------- */ diff --git a/packages/editor/package.json b/packages/editor/package.json index e99ff8626..509a504b7 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -45,24 +45,21 @@ "lint": "yarn run -T tsx ../../scripts/lint.ts" }, "dependencies": { - "@tldraw/indices": "workspace:*", - "@tldraw/primitives": "workspace:*", "@tldraw/state": "workspace:*", "@tldraw/store": "workspace:*", "@tldraw/tlschema": "workspace:*", "@tldraw/utils": "workspace:*", "@tldraw/validate": "workspace:*", - "@types/canvas-size": "^1.2.0", + "@types/core-js": "^2.5.5", "@use-gesture/react": "^10.2.27", - "canvas-size": "^1.2.6", "classnames": "^2.3.2", - "escape-string-regexp": "^5.0.0", + "core-js": "^3.31.1", "eventemitter3": "^4.0.7", "idb": "^7.1.1", "is-plain-object": "^5.0.0", "lodash.throttle": "^4.1.1", "lodash.uniq": "^4.5.0", - "nanoid": "4.0.2" + "nanoid": "^3.3.6" }, "peerDependencies": { "react": "^18", @@ -95,7 +92,7 @@ "^.+\\.*.css$" ], "transformIgnorePatterns": [ - "node_modules/(?!(nanoid|escape-string-regexp)/)" + "node_modules/(?!(nanoid)/)" ], "moduleNameMapper": { "^~(.*)": "/src/$1", diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 94af02e53..50fe96866 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -1,23 +1,30 @@ // Important! don't move this tlschema re-export to lib/index.ts, doing so causes esbuild to produce // incorrect output. https://github.com/evanw/esbuild/issues/1737 -// eslint-disable-next-line local/no-export-star -export * from '@tldraw/indices' export { + EMPTY_ARRAY, atom, computed, react, track, + transact, + transaction, useComputed, useQuickReactor, useReactor, useValue, whyAmIRunning, + type Atom, + type Signal, } from '@tldraw/state' -export { defineMigrations } from '@tldraw/store' +// eslint-disable-next-line local/no-export-star +export * from '@tldraw/store' // eslint-disable-next-line local/no-export-star export * from '@tldraw/tlschema' -export { getHashForString } from '@tldraw/utils' +// eslint-disable-next-line local/no-export-star +export * from '@tldraw/utils' +// eslint-disable-next-line local/no-export-star +export * from '@tldraw/validate' export { ErrorScreen, LoadingScreen, @@ -26,20 +33,16 @@ export { type TldrawEditorBaseProps, type TldrawEditorProps, } from './lib/TldrawEditor' -export { - defaultEditorAssetUrls, - setDefaultEditorAssetUrls, - type TLEditorAssetUrls, -} from './lib/assetUrls' export { Canvas } from './lib/components/Canvas' -export { DefaultErrorFallback } from './lib/components/DefaultErrorFallback' export { ErrorBoundary, OptionalErrorBoundary, type TLErrorBoundaryProps, } from './lib/components/ErrorBoundary' export { HTMLContainer, type HTMLContainerProps } from './lib/components/HTMLContainer' +export { PositionedOnCanvas } from './lib/components/PositionedOnCanvas' export { SVGContainer, type SVGContainerProps } from './lib/components/SVGContainer' +export { DefaultErrorFallback } from './lib/components/default-components/DefaultErrorFallback' export { TAB_ID, createSessionStateSnapshotSignal, @@ -60,39 +63,35 @@ export { type TLStoreOptions, } from './lib/config/createTLStore' export { createTLUser } from './lib/config/createTLUser' -export { coreShapes, defaultShapes } from './lib/config/defaultShapes' -export { defaultTools } from './lib/config/defaultTools' -export { defineShape, type TLShapeInfo } from './lib/config/defineShape' +export { coreShapes, type TLAnyShapeUtilConstructor } from './lib/config/defaultShapes' export { ANIMATION_MEDIUM_MS, ANIMATION_SHORT_MS, + CAMERA_SLIDE_FRICTION, DEFAULT_ANIMATION_OPTIONS, DOUBLE_CLICK_DURATION, DRAG_DISTANCE, GRID_INCREMENT, GRID_STEPS, - HAND_TOOL_FRICTION, HASH_PATTERN_ZOOM_NAMES, MAJOR_NUDGE_FACTOR, - MAX_ASSET_HEIGHT, - MAX_ASSET_WIDTH, MAX_PAGES, MAX_SHAPES_PER_PAGE, MAX_ZOOM, MINOR_NUDGE_FACTOR, MIN_ZOOM, MULTI_CLICK_DURATION, - REMOVE_SYMBOL, - RICH_TYPES, SVG_PADDING, ZOOMS, } from './lib/constants' export { Editor, type TLAnimationOptions, type TLEditorOptions } from './lib/editor/Editor' export { - ExternalContentManager as PlopManager, - type TLExternalContent, -} from './lib/editor/managers/ExternalContentManager' -export { ScribbleManager } from './lib/editor/managers/ScribbleManager' + SnapManager, + type GapsSnapLine, + type PointsSnapLine, + type SnapLine, + type SnapPoint, +} from './lib/editor/managers/SnapManager' export { BaseBoxShapeUtil, type TLBaseBoxShape } from './lib/editor/shapes/BaseBoxShapeUtil' export { ShapeUtil, @@ -117,38 +116,25 @@ export { type TLOnTranslateStartHandler, type TLResizeInfo, type TLResizeMode, + type TLShapeUtilCanvasSvgDef, type TLShapeUtilConstructor, type TLShapeUtilFlag, } from './lib/editor/shapes/ShapeUtil' -export { ArrowShape } from './lib/editor/shapes/arrow/ArrowShape' -export { ArrowShapeUtil } from './lib/editor/shapes/arrow/ArrowShapeUtil' -export { BookmarkShape } from './lib/editor/shapes/bookmark/BookmarkShape' -export { BookmarkShapeUtil } from './lib/editor/shapes/bookmark/BookmarkShapeUtil' -export { DrawShape } from './lib/editor/shapes/draw/DrawShape' -export { DrawShapeUtil } from './lib/editor/shapes/draw/DrawShapeUtil' -export { EmbedShape } from './lib/editor/shapes/embed/EmbedShape' -export { EmbedShapeUtil } from './lib/editor/shapes/embed/EmbedShapeUtil' -export { FrameShape } from './lib/editor/shapes/frame/FrameShape' -export { FrameShapeUtil } from './lib/editor/shapes/frame/FrameShapeUtil' -export { GeoShape } from './lib/editor/shapes/geo/GeoShape' -export { GeoShapeUtil } from './lib/editor/shapes/geo/GeoShapeUtil' -export { GroupShape } from './lib/editor/shapes/group/GroupShape' export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil' -export { HighlightShape } from './lib/editor/shapes/highlight/HighlightShape' -export { HighlightShapeUtil } from './lib/editor/shapes/highlight/HighlightShapeUtil' -export { ImageShape } from './lib/editor/shapes/image/ImageShape' -export { ImageShapeUtil } from './lib/editor/shapes/image/ImageShapeUtil' -export { LineShape } from './lib/editor/shapes/line/LineShape' -export { LineShapeUtil, getSplineForLineShape } from './lib/editor/shapes/line/LineShapeUtil' -export { NoteShape } from './lib/editor/shapes/note/NoteShape' -export { NoteShapeUtil } from './lib/editor/shapes/note/NoteShapeUtil' +export { getArrowheadPathForType } from './lib/editor/shapes/shared/arrow/arrowheads' +export { + getCurvedArrowHandlePath, + getSolidCurvedArrowPath, +} from './lib/editor/shapes/shared/arrow/curved-arrow' +export { getArrowTerminalsInArrowSpace } from './lib/editor/shapes/shared/arrow/shared' +export { + getSolidStraightArrowPath, + getStraightArrowHandlePath, +} from './lib/editor/shapes/shared/arrow/straight-arrow' export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/resizeBox' -export { TextShape } from './lib/editor/shapes/text/TextShape' -export { INDENT, TextShapeUtil } from './lib/editor/shapes/text/TextShapeUtil' -export { VideoShape } from './lib/editor/shapes/video/VideoShape' -export { VideoShapeUtil } from './lib/editor/shapes/video/VideoShapeUtil' export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool' export { StateNode, type TLStateNodeConstructor } from './lib/editor/tools/StateNode' +export { type SvgExportContext, type SvgExportDef } from './lib/editor/types/SvgExportContext' export { type TLContent } from './lib/editor/types/clipboard-types' export { type TLEventMap, type TLEventMapHandler } from './lib/editor/types/emit-types' export { @@ -184,6 +170,10 @@ export { type UiEvent, type UiEventType, } from './lib/editor/types/event-types' +export { + type TLExternalAssetContent, + type TLExternalContent, +} from './lib/editor/types/external-content' export { type TLCommand, type TLCommandHandler, @@ -192,83 +182,126 @@ export { } from './lib/editor/types/history-types' export { type RequiredKeys } from './lib/editor/types/misc-types' export { type TLResizeHandle, type TLSelectionHandle } from './lib/editor/types/selection-types' -export { normalizeWheel } from './lib/hooks/shared' export { useContainer } from './lib/hooks/useContainer' +export { getCursor } from './lib/hooks/useCursor' export { useEditor } from './lib/hooks/useEditor' export type { TLEditorComponents } from './lib/hooks/useEditorComponents' +export { useIsCropping } from './lib/hooks/useIsCropping' +export { useIsEditing } from './lib/hooks/useIsEditing' export { useLocalStore } from './lib/hooks/useLocalStore' export { usePeerIds } from './lib/hooks/usePeerIds' export { usePresence } from './lib/hooks/usePresence' +export { useSelectionEvents } from './lib/hooks/useSelectionEvents' export { useTLStore } from './lib/hooks/useTLStore' +export { useTransform } from './lib/hooks/useTransform' +export { + Box2d, + ROTATE_CORNER_TO_SELECTION_CORNER, + rotateSelectionHandle, + type RotateCorner, + type SelectionCorner, + type SelectionEdge, + type SelectionHandle, +} from './lib/primitives/Box2d' +export { Matrix2d, type Matrix2dModel } from './lib/primitives/Matrix2d' +export { Vec2d, type VecLike } from './lib/primitives/Vec2d' +export { EASINGS } from './lib/primitives/easings' +export { + intersectLineSegmentPolygon, + intersectLineSegmentPolyline, + intersectPolygonPolygon, + linesIntersect, + polygonsIntersect, +} from './lib/primitives/intersect' +export { + EPSILON, + PI, + PI2, + SIN, + TAU, + angleDelta, + approximately, + areAnglesCompatible, + average, + canonicalizeRotation, + clamp, + clampRadians, + degreesToRadians, + getArcLength, + getPointOnCircle, + getPolygonVertices, + getStarBounds, + getSweep, + isAngleBetween, + isSafeFloat, + lerpAngles, + longAngleDist, + perimeterOfEllipse, + pointInBounds, + pointInCircle, + pointInEllipse, + pointInPolygon, + pointInPolyline, + pointInRect, + pointNearToLineSegment, + pointNearToPolyline, + precise, + radiansToDegrees, + rangeIntersection, + shortAngleDist, + snapAngle, + toDomPrecision, + toFixed, + toPrecision, +} from './lib/primitives/utils' export { ReadonlySharedStyleMap, SharedStyleMap, type SharedStyle, } from './lib/utils/SharedStylesMap' export { WeakMapCache } from './lib/utils/WeakMapCache' -export { - ACCEPTED_ASSET_TYPE, - ACCEPTED_IMG_TYPE, - ACCEPTED_VID_TYPE, - containBoxSize, - dataUrlToFile, - getFileMetaData, - getImageSizeFromSrc, - getMediaAssetFromFile, - getResizedImageDataUrl, - getValidHttpURLList, - getVideoSizeFromSrc, - isImage, - isSvgText, - isValidHttpURL, -} from './lib/utils/assets' -export { - checkFlag, - fileToBase64, - getIncrementedName, - isSerializable, - isValidUrl, - snapToGrid, - uniqueId, -} from './lib/utils/data' +export { dataUrlToFile } from './lib/utils/assets' export { debugFlags, featureFlags, type DebugFlag } from './lib/utils/debug-flags' export { - getRotatedBoxShadow, loopToHtmlElement, preventDefault, releasePointerCapture, setPointerCapture, - truncateStringWithEllipsis, - usePrefersReducedMotion, + stopEventPropagation, } from './lib/utils/dom' +export { getIncrementedName } from './lib/utils/getIncrementedName' +export { getPointerInfo } from './lib/utils/getPointerInfo' +export { getSvgPathFromPoints } from './lib/utils/getSvgPathFromPoints' +export { hardResetEditor } from './lib/utils/hardResetEditor' +export { normalizeWheel } from './lib/utils/normalizeWheel' +export { png } from './lib/utils/png' +export { refreshPage } from './lib/utils/refreshPage' export { - getEmbedInfo, - getEmbedInfoUnsafely, - matchEmbedUrl, - matchUrl, - type TLEmbedResult, -} from './lib/utils/embeds' + getIndexAbove, + getIndexBelow, + getIndexBetween, + getIndices, + getIndicesAbove, + getIndicesBelow, + getIndicesBetween, + sortByIndex, +} from './lib/utils/reordering/reordering' export { - downloadDataURLAsFile, - getSvgAsDataUrl, - getSvgAsDataUrlSync, - getSvgAsImage, - getSvgAsString, - getTextBoundingBox, - type TLCopyType, - type TLExportType, -} from './lib/utils/export' -export { hardResetEditor } from './lib/utils/hard-reset' -export { isAnimated, isGIF } from './lib/utils/is-gif-animated' -export { refreshPage } from './lib/utils/refresh-page' + applyRotationToSnapshotShapes, + getRotationSnapshot, + type TLRotationSnapshot, +} from './lib/utils/rotation' export { runtime, setRuntimeOverrides } from './lib/utils/runtime' -export { - blobAsString, - correctSpacesToNbsp, - dataTransferItemAsString, - defaultEmptyAs, -} from './lib/utils/string' -export { getPointerInfo, getSvgPathFromStroke, getSvgPathFromStrokePoints } from './lib/utils/svg' export { type TLStoreWithStatus } from './lib/utils/sync/StoreWithStatus' export { hardReset } from './lib/utils/sync/hardReset' +export { uniq } from './lib/utils/uniq' +export { uniqueId } from './lib/utils/uniqueId' export { openWindow } from './lib/utils/window-open' + +/** @polyfills */ + +import 'core-js/stable/array/at' +import 'core-js/stable/array/flat' +import 'core-js/stable/array/flat-map' +import 'core-js/stable/string/at' +import 'core-js/stable/string/replace-all' diff --git a/packages/editor/src/lib/TldrawEditor.tsx b/packages/editor/src/lib/TldrawEditor.tsx index 031382a07..c92a7dcac 100644 --- a/packages/editor/src/lib/TldrawEditor.tsx +++ b/packages/editor/src/lib/TldrawEditor.tsx @@ -1,6 +1,6 @@ import { SerializedStore, Store } from '@tldraw/store' import { TLRecord, TLStore } from '@tldraw/tlschema' -import { RecursivePartial, Required, annotateError } from '@tldraw/utils' +import { Required, annotateError } from '@tldraw/utils' import React, { memo, useCallback, @@ -10,11 +10,11 @@ import React, { useSyncExternalStore, } from 'react' -import { TLEditorAssetUrls, useDefaultEditorAssetsWithOverrides } from './assetUrls' -import { DefaultErrorFallback } from './components/DefaultErrorFallback' +import { Canvas } from './components/Canvas' import { OptionalErrorBoundary } from './components/ErrorBoundary' +import { DefaultErrorFallback } from './components/default-components/DefaultErrorFallback' import { TLUser, createTLUser } from './config/createTLUser' -import { AnyTLShapeInfo } from './config/defineShape' +import { TLAnyShapeUtilConstructor } from './config/defaultShapes' import { Editor } from './editor/Editor' import { TLStateNodeConstructor } from './editor/tools/StateNode' import { ContainerProvider, useContainer } from './hooks/useContainer' @@ -29,7 +29,6 @@ import { import { useEvent } from './hooks/useEvent' import { useForceUpdate } from './hooks/useForceUpdate' import { useLocalStore } from './hooks/useLocalStore' -import { usePreloadAssets } from './hooks/usePreloadAssets' import { useSafariFocusOutFix } from './hooks/useSafariFocusOutFix' import { useZoomCss } from './hooks/useZoomCss' import { TLStoreWithStatus } from './utils/sync/StoreWithStatus' @@ -65,20 +64,15 @@ export interface TldrawEditorBaseProps { children?: any /** - * An array of shapes definitions to make available to the editor. + * An array of shape utils to use in the editor. */ - shapes?: readonly AnyTLShapeInfo[] + shapeUtils?: readonly TLAnyShapeUtilConstructor[] /** * An array of tools to add to the editor's state chart. */ tools?: readonly TLStateNodeConstructor[] - /** - * Urls for the editor to find fonts and other assets. - */ - assetUrls?: RecursivePartial - /** * Whether to automatically focus the editor when it mounts. */ @@ -93,6 +87,11 @@ export interface TldrawEditorBaseProps { * Called when the editor has mounted. */ onMount?: TLOnMountHandler + + /** + * The editor's initial state (usually the id of the first active tool). + */ + initialState?: string } /** @@ -113,7 +112,7 @@ declare global { } } -const EMPTY_SHAPES_ARRAY = [] as const +const EMPTY_SHAPE_UTILS_ARRAY = [] as const const EMPTY_TOOLS_ARRAY = [] as const /** @public */ @@ -133,7 +132,7 @@ export const TldrawEditor = memo(function TldrawEditor({ // defaults applied in @tldraw/tldraw. const withDefaults = { ...rest, - shapes: rest.shapes ?? EMPTY_SHAPES_ARRAY, + shapeUtils: rest.shapeUtils ?? EMPTY_SHAPE_UTILS_ARRAY, tools: rest.tools ?? EMPTY_TOOLS_ARRAY, } @@ -167,12 +166,12 @@ export const TldrawEditor = memo(function TldrawEditor({ }) function TldrawEditorWithOwnStore( - props: Required + props: Required ) { - const { defaultName, initialData, shapes, persistenceKey, sessionId, user } = props + const { defaultName, initialData, shapeUtils, persistenceKey, sessionId, user } = props const syncedStore = useLocalStore({ - shapes, + shapeUtils, initialData, persistenceKey, sessionId, @@ -186,7 +185,10 @@ const TldrawEditorWithLoadingStore = memo(function TldrawEditorBeforeLoading({ store, user, ...rest -}: Required) { +}: Required< + TldrawEditorProps & { store: TLStoreWithStatus; user: TLUser }, + 'shapeUtils' | 'tools' +>) { const container = useContainer() useLayoutEffect(() => { @@ -225,16 +227,16 @@ function TldrawEditorWithReadyStore({ children, store, tools, - shapes, + shapeUtils, autoFocus, user, - assetUrls, + initialState, }: Required< TldrawEditorProps & { store: TLStore user: TLUser }, - 'shapes' | 'tools' + 'shapeUtils' | 'tools' >) { const { ErrorFallback } = useEditorComponents() const container = useContainer() @@ -243,10 +245,11 @@ function TldrawEditorWithReadyStore({ useLayoutEffect(() => { const editor = new Editor({ store, - shapes, + shapeUtils, tools, getContainer: () => container, user, + initialState, }) ;(window as any).app = editor ;(window as any).editor = editor @@ -255,10 +258,10 @@ function TldrawEditorWithReadyStore({ return () => { editor.dispose() } - }, [container, shapes, tools, store, user]) + }, [container, shapeUtils, tools, store, user, initialState]) React.useLayoutEffect(() => { - if (editor && autoFocus) editor.focus() + if (editor && autoFocus) editor.isFocused = true }, [editor, autoFocus]) const onMountEvent = useEvent((editor: Editor) => { @@ -288,17 +291,6 @@ function TldrawEditorWithReadyStore({ () => editor?.crashingError ?? null ) - const assets = useDefaultEditorAssetsWithOverrides(assetUrls) - const { done: preloadingComplete, error: preloadingError } = usePreloadAssets(assets) - - if (preloadingError) { - return Could not load assets. Please refresh the page. - } - - if (!preloadingComplete) { - return Loading assets... - } - if (!editor) { return null } @@ -311,7 +303,7 @@ function TldrawEditorWithReadyStore({ // document in the event of an error to reassure them that their work is // not lost. editor.annotateError(error, { origin: 'react.tldraw', willCrashApp: true }) } @@ -334,7 +326,7 @@ function Layout({ children }: { children: any }) { useSafariFocusOutFix() useForceUpdate() - return children + return children ?? } function Crash({ crashingError }: { crashingError: unknown }): null { diff --git a/packages/editor/src/lib/components/Canvas.tsx b/packages/editor/src/lib/components/Canvas.tsx index 73401dbdd..e7b405640 100644 --- a/packages/editor/src/lib/components/Canvas.tsx +++ b/packages/editor/src/lib/components/Canvas.tsx @@ -1,4 +1,3 @@ -import { Matrix2d, toDomPrecision } from '@tldraw/primitives' import { react, track, useQuickReactor, useValue } from '@tldraw/state' import { TLHandle, TLShapeId } from '@tldraw/tlschema' import { dedupe, modulate, objectMapValues } from '@tldraw/utils' @@ -12,10 +11,10 @@ import { useFixSafariDoubleTapZoomPencilEvents } from '../hooks/useFixSafariDoub import { useGestureEvents } from '../hooks/useGestureEvents' import { useHandleEvents } from '../hooks/useHandleEvents' import { useScreenBounds } from '../hooks/useScreenBounds' +import { Matrix2d } from '../primitives/Matrix2d' +import { toDomPrecision } from '../primitives/utils' import { debugFlags } from '../utils/debug-flags' import { LiveCollaborators } from './LiveCollaborators' -import { SelectionBg } from './SelectionBg' -import { SelectionFg } from './SelectionFg' import { Shape } from './Shape' import { ShapeIndicator } from './ShapeIndicator' @@ -97,7 +96,7 @@ export const Canvas = track(function Canvas() { {SvgDefs && } - +
@@ -110,7 +109,7 @@ export const Canvas = track(function Canvas() { - +
@@ -124,12 +123,13 @@ const GridWrapper = track(function GridWrapper() { // get grid from context + const { gridSize } = editor.documentSettings const { x, y, z } = editor.camera const isGridMode = editor.isGridMode if (!(Grid && isGridMode)) return null - return + return }) const ScribbleWrapper = track(function ScribbleWrapper() { @@ -432,3 +432,15 @@ const UiLogger = track(() => {
) }) + +export function SelectionForegroundWrapper() { + const { SelectionForeground } = useEditorComponents() + if (!SelectionForeground) return null + return +} + +export function SelectionBackgroundWrapper() { + const { SelectionBackground } = useEditorComponents() + if (!SelectionBackground) return null + return +} diff --git a/packages/editor/src/lib/components/DefaultBackground.tsx b/packages/editor/src/lib/components/DefaultBackground.tsx deleted file mode 100644 index 5d0426b48..000000000 --- a/packages/editor/src/lib/components/DefaultBackground.tsx +++ /dev/null @@ -1,6 +0,0 @@ -/** @public */ -export type TLBackgroundComponent = () => JSX.Element | null - -export function DefaultBackground() { - return
-} diff --git a/packages/editor/src/lib/components/ErrorBoundary.tsx b/packages/editor/src/lib/components/ErrorBoundary.tsx index 976f0c252..94ba1851c 100644 --- a/packages/editor/src/lib/components/ErrorBoundary.tsx +++ b/packages/editor/src/lib/components/ErrorBoundary.tsx @@ -1,11 +1,11 @@ import * as React from 'react' -import { TLErrorFallbackComponent } from './DefaultErrorFallback' +import { TLErrorFallbackComponent } from './default-components/DefaultErrorFallback' /** @public */ export interface TLErrorBoundaryProps { children: React.ReactNode onError?: ((error: unknown) => void) | null - fallback: (props: { error: unknown }) => any + fallback: TLErrorFallbackComponent } type TLErrorBoundaryState = { error: Error | null } @@ -21,13 +21,13 @@ export class ErrorBoundary extends React.Component< return { error } } - state = initialState + override state = initialState - componentDidCatch(error: unknown) { + override componentDidCatch(error: unknown) { this.props.onError?.(error) } - render() { + override render() { const { error } = this.state if (error !== null) { @@ -52,7 +52,7 @@ export function OptionalErrorBoundary({ } return ( - + {children} ) diff --git a/packages/editor/src/lib/components/PositionedOnCanvas.tsx b/packages/editor/src/lib/components/PositionedOnCanvas.tsx new file mode 100644 index 000000000..2afaf5df8 --- /dev/null +++ b/packages/editor/src/lib/components/PositionedOnCanvas.tsx @@ -0,0 +1,30 @@ +import { track } from '@tldraw/state' +import classNames from 'classnames' +import { HTMLProps, useLayoutEffect, useRef } from 'react' +import { useEditor } from '../hooks/useEditor' + +/** @public */ +export const PositionedOnCanvas = track(function PositionedOnCanvas({ + x: offsetX = 0, + y: offsetY = 0, + rotation = 0, + ...rest +}: { + x?: number + y?: number + rotation?: number +} & HTMLProps) { + const editor = useEditor() + const rContainer = useRef(null) + + useLayoutEffect(() => { + const { x, y, z } = editor.camera + const elm = rContainer.current + if (!elm) return + if (x === undefined) return + + elm.style.transform = `translate(${x}px, ${y}px) scale(${z}) rotate(${rotation}rad) translate(${offsetX}px, ${offsetY}px)` + }, [editor.camera, offsetX, offsetY, rotation]) + + return
+}) diff --git a/packages/editor/src/lib/components/SelectionBg.tsx b/packages/editor/src/lib/components/SelectionBg.tsx deleted file mode 100644 index 912f27b72..000000000 --- a/packages/editor/src/lib/components/SelectionBg.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { Matrix2d, toDomPrecision } from '@tldraw/primitives' -import { track } from '@tldraw/state' -import * as React from 'react' -import { TLPointerEventInfo } from '../editor/types/event-types' -import { useEditor } from '../hooks/useEditor' -import { releasePointerCapture, setPointerCapture } from '../utils/dom' -import { getPointerInfo } from '../utils/svg' - -export const SelectionBg = track(function SelectionBg() { - const editor = useEditor() - - const events = React.useMemo(() => { - const onPointerDown = (e: React.PointerEvent) => { - if ((e as any).isKilled) return - - setPointerCapture(e.currentTarget, e) - - const info: TLPointerEventInfo = { - type: 'pointer', - target: 'selection', - name: 'pointer_down', - ...getPointerInfo(e, editor.getContainer()), - } - - editor.dispatch(info) - } - - const onPointerMove = (e: React.PointerEvent) => { - if ((e as any).isKilled) return - - const info: TLPointerEventInfo = { - type: 'pointer', - target: 'selection', - name: 'pointer_move', - ...getPointerInfo(e, editor.getContainer()), - } - - editor.dispatch(info) - } - - const onPointerUp = (e: React.PointerEvent) => { - if ((e as any).isKilled) return - - releasePointerCapture(e.currentTarget, e) - - const info: TLPointerEventInfo = { - type: 'pointer', - target: 'selection', - name: 'pointer_up', - ...getPointerInfo(e, editor.getContainer()), - } - - editor.dispatch(info) - } - - const onPointerEnter = (e: React.PointerEvent) => { - if ((e as any).isKilled) return - - const info: TLPointerEventInfo = { - type: 'pointer', - target: 'selection', - name: 'pointer_enter', - ...getPointerInfo(e, editor.getContainer()), - } - - editor.dispatch(info) - } - - const onPointerLeave = (e: React.PointerEvent) => { - if ((e as any).isKilled) return - - const info: TLPointerEventInfo = { - type: 'pointer', - target: 'selection', - name: 'pointer_leave', - ...getPointerInfo(e, editor.getContainer()), - } - - editor.dispatch(info) - } - - return { - onPointerDown, - onPointerMove, - onPointerUp, - onPointerEnter, - onPointerLeave, - } - }, [editor]) - - const { selectionBounds: bounds, selectedIds } = editor - if (!bounds) return null - - const shouldDisplay = editor.isInAny( - 'select.idle', - 'select.brushing', - 'select.scribble_brushing', - 'select.pointing_shape', - 'select.pointing_selection', - 'text.resizing' - ) - - if (selectedIds.length === 1) { - const shape = editor.getShapeById(selectedIds[0]) - if (!shape) { - return null - } - const util = editor.getShapeUtil(shape) - if (util.hideSelectionBoundsBg(shape)) { - return null - } - } - - const transform = Matrix2d.toCssString( - Matrix2d.Compose( - Matrix2d.Translate(bounds.minX, bounds.minY), - Matrix2d.Rotate(editor.selectionRotation) - ) - ) - - return ( -
- ) -}) diff --git a/packages/editor/src/lib/components/Shape.tsx b/packages/editor/src/lib/components/Shape.tsx index 8597fed5a..910749cf1 100644 --- a/packages/editor/src/lib/components/Shape.tsx +++ b/packages/editor/src/lib/components/Shape.tsx @@ -1,11 +1,11 @@ -import { Matrix2d } from '@tldraw/primitives' import { track, useQuickReactor, useStateTracking } from '@tldraw/state' import { TLShape, TLShapeId } from '@tldraw/tlschema' import * as React from 'react' -import { useEditor } from '../..' import { ShapeUtil } from '../editor/shapes/ShapeUtil' +import { useEditor } from '../hooks/useEditor' import { useEditorComponents } from '../hooks/useEditorComponents' import { useShapeEvents } from '../hooks/useShapeEvents' +import { Matrix2d } from '../primitives/Matrix2d' import { OptionalErrorBoundary } from './ErrorBoundary' /* @@ -135,7 +135,7 @@ export const Shape = track(function Shape({ {isCulled && util.canUnmount(shape) ? ( ) : ( - + )} @@ -146,7 +146,7 @@ export const Shape = track(function Shape({ const InnerShape = React.memo( function InnerShape({ shape, util }: { shape: T; util: ShapeUtil }) { - return useStateTracking('InnerShape:' + util.type, () => util.component(shape)) + return useStateTracking('InnerShape:' + shape.type, () => util.component(shape)) }, (prev, next) => prev.shape.props === next.shape.props && prev.shape.meta === next.shape.meta ) @@ -159,7 +159,7 @@ const InnerShapeBackground = React.memo( shape: T util: ShapeUtil }) { - return useStateTracking('InnerShape:' + util.type, () => util.backgroundComponent?.(shape)) + return useStateTracking('InnerShape:' + shape.type, () => util.backgroundComponent?.(shape)) }, (prev, next) => prev.shape.props === next.shape.props && prev.shape.meta === next.shape.meta ) diff --git a/packages/editor/src/lib/components/default-components/DefaultBackground.tsx b/packages/editor/src/lib/components/default-components/DefaultBackground.tsx new file mode 100644 index 000000000..4584c40a1 --- /dev/null +++ b/packages/editor/src/lib/components/default-components/DefaultBackground.tsx @@ -0,0 +1,8 @@ +import { ComponentType } from 'react' + +/** @public */ +export type TLBackgroundComponent = ComponentType | null + +export function DefaultBackground() { + return
+} diff --git a/packages/editor/src/lib/components/DefaultBrush.tsx b/packages/editor/src/lib/components/default-components/DefaultBrush.tsx similarity index 82% rename from packages/editor/src/lib/components/DefaultBrush.tsx rename to packages/editor/src/lib/components/default-components/DefaultBrush.tsx index c87843714..8e7a771cd 100644 --- a/packages/editor/src/lib/components/DefaultBrush.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultBrush.tsx @@ -1,15 +1,15 @@ -import { toDomPrecision } from '@tldraw/primitives' import { Box2dModel } from '@tldraw/tlschema' -import { useRef } from 'react' -import { useTransform } from '../hooks/useTransform' +import { ComponentType, useRef } from 'react' +import { useTransform } from '../../hooks/useTransform' +import { toDomPrecision } from '../../primitives/utils' /** @public */ -export type TLBrushComponent = (props: { +export type TLBrushComponent = ComponentType<{ brush: Box2dModel color?: string opacity?: number className?: string -}) => any | null +}> export const DefaultBrush: TLBrushComponent = ({ brush, color, opacity }) => { const rSvg = useRef(null) diff --git a/packages/editor/src/lib/components/DefaultCollaboratorHint.tsx b/packages/editor/src/lib/components/default-components/DefaultCollaboratorHint.tsx similarity index 73% rename from packages/editor/src/lib/components/DefaultCollaboratorHint.tsx rename to packages/editor/src/lib/components/default-components/DefaultCollaboratorHint.tsx index cb48c9989..0b0fd0c57 100644 --- a/packages/editor/src/lib/components/DefaultCollaboratorHint.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultCollaboratorHint.tsx @@ -1,17 +1,19 @@ -import { Box2d, clamp, Vec2d } from '@tldraw/primitives' import { Vec2dModel } from '@tldraw/tlschema' import classNames from 'classnames' -import { useRef } from 'react' -import { useTransform } from '../hooks/useTransform' +import { ComponentType, useRef } from 'react' +import { useTransform } from '../../hooks/useTransform' +import { Box2d } from '../../primitives/Box2d' +import { Vec2d } from '../../primitives/Vec2d' +import { clamp } from '../../primitives/utils' -export type TLCollaboratorHintComponent = (props: { +export type TLCollaboratorHintComponent = ComponentType<{ className?: string point: Vec2dModel viewport: Box2d zoom: number opacity?: number color: string -}) => JSX.Element | null +}> export const DefaultCollaboratorHint: TLCollaboratorHintComponent = ({ className, diff --git a/packages/editor/src/lib/components/DefaultCursor.tsx b/packages/editor/src/lib/components/default-components/DefaultCursor.tsx similarity index 86% rename from packages/editor/src/lib/components/DefaultCursor.tsx rename to packages/editor/src/lib/components/default-components/DefaultCursor.tsx index 6808394b4..7377568c8 100644 --- a/packages/editor/src/lib/components/DefaultCursor.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultCursor.tsx @@ -1,17 +1,17 @@ import { Vec2dModel } from '@tldraw/tlschema' import classNames from 'classnames' -import { memo, useRef } from 'react' -import { useTransform } from '../hooks/useTransform' +import { ComponentType, memo, useRef } from 'react' +import { useTransform } from '../../hooks/useTransform' /** @public */ -export type TLCursorComponent = (props: { +export type TLCursorComponent = ComponentType<{ className?: string point: Vec2dModel | null zoom: number color?: string name: string | null chatMessage: string -}) => any | null +}> const _Cursor: TLCursorComponent = ({ className, zoom, point, color, name, chatMessage }) => { const rCursor = useRef(null) diff --git a/packages/editor/src/lib/components/DefaultErrorFallback.tsx b/packages/editor/src/lib/components/default-components/DefaultErrorFallback.tsx similarity index 92% rename from packages/editor/src/lib/components/DefaultErrorFallback.tsx rename to packages/editor/src/lib/components/default-components/DefaultErrorFallback.tsx index 1781cad0e..2dacf92d6 100644 --- a/packages/editor/src/lib/components/DefaultErrorFallback.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultErrorFallback.tsx @@ -1,12 +1,12 @@ import { useValue } from '@tldraw/state' import classNames from 'classnames' -import { useEffect, useLayoutEffect, useRef, useState } from 'react' -import { Editor } from '../editor/Editor' -import { EditorContext } from '../hooks/useEditor' -import { hardResetEditor } from '../utils/hard-reset' -import { refreshPage } from '../utils/refresh-page' -import { Canvas } from './Canvas' -import { ErrorBoundary } from './ErrorBoundary' +import { ComponentType, useEffect, useLayoutEffect, useRef, useState } from 'react' +import { Editor } from '../../editor/Editor' +import { EditorContext } from '../../hooks/useEditor' +import { hardResetEditor } from '../../utils/hardResetEditor' +import { refreshPage } from '../../utils/refreshPage' +import { Canvas } from '../Canvas' +import { ErrorBoundary } from '../ErrorBoundary' const BASE_ERROR_URL = 'https://github.com/tldraw/tldraw/issues/new' @@ -14,7 +14,7 @@ const BASE_ERROR_URL = 'https://github.com/tldraw/tldraw/issues/new' function noop() {} /** @public */ -export type TLErrorFallbackComponent = (props: { error: unknown; editor?: Editor }) => any | null +export type TLErrorFallbackComponent = ComponentType<{ error: unknown; editor?: Editor }> /** @internal */ export const DefaultErrorFallback: TLErrorFallbackComponent = ({ error, editor }) => { diff --git a/packages/editor/src/lib/components/DefaultGrid.tsx b/packages/editor/src/lib/components/default-components/DefaultGrid.tsx similarity index 88% rename from packages/editor/src/lib/components/DefaultGrid.tsx rename to packages/editor/src/lib/components/default-components/DefaultGrid.tsx index fcc43db6e..c76d033ca 100644 --- a/packages/editor/src/lib/components/DefaultGrid.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultGrid.tsx @@ -1,13 +1,14 @@ import { modulate } from '@tldraw/utils' -import { GRID_STEPS } from '../constants' +import { ComponentType } from 'react' +import { GRID_STEPS } from '../../constants' /** @public */ -export type TLGridComponent = (props: { +export type TLGridComponent = ComponentType<{ x: number y: number z: number size: number -}) => JSX.Element | null +}> export const DefaultGrid: TLGridComponent = ({ x, y, z, size }) => { return ( diff --git a/packages/editor/src/lib/components/DefaultHandle.tsx b/packages/editor/src/lib/components/default-components/DefaultHandle.tsx similarity index 83% rename from packages/editor/src/lib/components/DefaultHandle.tsx rename to packages/editor/src/lib/components/default-components/DefaultHandle.tsx index 4aad09b32..a21cd10bf 100644 --- a/packages/editor/src/lib/components/DefaultHandle.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultHandle.tsx @@ -1,11 +1,12 @@ import { TLHandle, TLShapeId } from '@tldraw/tlschema' import classNames from 'classnames' +import { ComponentType } from 'react' -export type TLHandleComponent = (props: { +export type TLHandleComponent = ComponentType<{ shapeId: TLShapeId handle: TLHandle className?: string -}) => any | null +}> export const DefaultHandle: TLHandleComponent = ({ handle, className }) => { return ( diff --git a/packages/editor/src/lib/components/default-components/DefaultScribble.tsx b/packages/editor/src/lib/components/default-components/DefaultScribble.tsx new file mode 100644 index 000000000..617531efa --- /dev/null +++ b/packages/editor/src/lib/components/default-components/DefaultScribble.tsx @@ -0,0 +1,36 @@ +import { TLScribble } from '@tldraw/tlschema' +import classNames from 'classnames' +import { ComponentType } from 'react' +import { getSvgPathFromPoints } from '../../utils/getSvgPathFromPoints' + +/** @public */ +export type TLScribbleComponent = ComponentType<{ + scribble: TLScribble + zoom: number + color?: string + opacity?: number + className?: string +}> + +export const DefaultScribble: TLScribbleComponent = ({ + scribble, + zoom, + color, + opacity, + className, +}) => { + if (!scribble.points.length) return null + + return ( + + + + ) +} diff --git a/packages/editor/src/lib/components/default-components/DefaultSelectionBackground.tsx b/packages/editor/src/lib/components/default-components/DefaultSelectionBackground.tsx new file mode 100644 index 000000000..0857adec7 --- /dev/null +++ b/packages/editor/src/lib/components/default-components/DefaultSelectionBackground.tsx @@ -0,0 +1,141 @@ +import { track } from '@tldraw/state' +import * as React from 'react' +import { TLPointerEventInfo } from '../../editor/types/event-types' +import { useEditor } from '../../hooks/useEditor' +import { Matrix2d } from '../../primitives/Matrix2d' +import { toDomPrecision } from '../../primitives/utils' +import { releasePointerCapture, setPointerCapture } from '../../utils/dom' +import { getPointerInfo } from '../../utils/getPointerInfo' + +/** @public */ +export type TLSelectionBackgroundComponent = React.ComponentType + +export const DefaultSelectionBackground: TLSelectionBackgroundComponent = track( + function SelectionBg() { + const editor = useEditor() + + const events = React.useMemo(() => { + const onPointerDown = (e: React.PointerEvent) => { + if ((e as any).isKilled) return + + setPointerCapture(e.currentTarget, e) + + const info: TLPointerEventInfo = { + type: 'pointer', + target: 'selection', + name: 'pointer_down', + ...getPointerInfo(e, editor.getContainer()), + } + + editor.dispatch(info) + } + + const onPointerMove = (e: React.PointerEvent) => { + if ((e as any).isKilled) return + + const info: TLPointerEventInfo = { + type: 'pointer', + target: 'selection', + name: 'pointer_move', + ...getPointerInfo(e, editor.getContainer()), + } + + editor.dispatch(info) + } + + const onPointerUp = (e: React.PointerEvent) => { + if ((e as any).isKilled) return + + releasePointerCapture(e.currentTarget, e) + + const info: TLPointerEventInfo = { + type: 'pointer', + target: 'selection', + name: 'pointer_up', + ...getPointerInfo(e, editor.getContainer()), + } + + editor.dispatch(info) + } + + const onPointerEnter = (e: React.PointerEvent) => { + if ((e as any).isKilled) return + + const info: TLPointerEventInfo = { + type: 'pointer', + target: 'selection', + name: 'pointer_enter', + ...getPointerInfo(e, editor.getContainer()), + } + + editor.dispatch(info) + } + + const onPointerLeave = (e: React.PointerEvent) => { + if ((e as any).isKilled) return + + const info: TLPointerEventInfo = { + type: 'pointer', + target: 'selection', + name: 'pointer_leave', + ...getPointerInfo(e, editor.getContainer()), + } + + editor.dispatch(info) + } + + return { + onPointerDown, + onPointerMove, + onPointerUp, + onPointerEnter, + onPointerLeave, + } + }, [editor]) + + const { selectionBounds: bounds, selectedIds } = editor + if (!bounds) return null + + const shouldDisplay = editor.isInAny( + 'select.idle', + 'select.brushing', + 'select.scribble_brushing', + 'select.pointing_shape', + 'select.pointing_selection', + 'text.resizing' + ) + + if (selectedIds.length === 1) { + const shape = editor.getShapeById(selectedIds[0]) + if (!shape) { + return null + } + const util = editor.getShapeUtil(shape) + if (util.hideSelectionBoundsBg(shape)) { + return null + } + } + + const transform = Matrix2d.toCssString( + Matrix2d.Compose( + Matrix2d.Translate(bounds.minX, bounds.minY), + Matrix2d.Rotate(editor.selectionRotation) + ) + ) + + return ( +
+ ) + } +) diff --git a/packages/editor/src/lib/components/default-components/DefaultSelectionForeground.tsx b/packages/editor/src/lib/components/default-components/DefaultSelectionForeground.tsx new file mode 100644 index 000000000..760a783d5 --- /dev/null +++ b/packages/editor/src/lib/components/default-components/DefaultSelectionForeground.tsx @@ -0,0 +1,49 @@ +import { track } from '@tldraw/state' +import classNames from 'classnames' +import { ComponentType, useRef } from 'react' +import { useEditor } from '../../hooks/useEditor' +import { useTransform } from '../../hooks/useTransform' +import { toDomPrecision } from '../../primitives/utils' + +/** @public */ +export type TLSelectionForegroundComponent = ComponentType + +export const DefaultSelectionForeground: TLSelectionForegroundComponent = track(() => { + const editor = useEditor() + const rSvg = useRef(null) + + let bounds = editor.selectionBounds + + const onlyShape = editor.onlySelectedShape + + // if all shapes have an expandBy for the selection outline, we can expand by the l + const expandOutlineBy = onlyShape + ? editor.getShapeUtil(onlyShape).expandSelectionOutlinePx(onlyShape) + : 0 + + useTransform(rSvg, bounds?.x, bounds?.y, 1, editor.selectionRotation, { + x: -expandOutlineBy, + y: -expandOutlineBy, + }) + + if (!bounds) return null + + bounds = bounds.clone().expandBy(expandOutlineBy) + + const width = Math.max(1, bounds.width) + const height = Math.max(1, bounds.height) + + return ( + + + + ) +}) diff --git a/packages/editor/src/lib/components/DefaultShapeErrorFallback.tsx b/packages/editor/src/lib/components/default-components/DefaultShapeErrorFallback.tsx similarity index 64% rename from packages/editor/src/lib/components/DefaultShapeErrorFallback.tsx rename to packages/editor/src/lib/components/default-components/DefaultShapeErrorFallback.tsx index cc025efbf..0e8561033 100644 --- a/packages/editor/src/lib/components/DefaultShapeErrorFallback.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultShapeErrorFallback.tsx @@ -1,5 +1,7 @@ +import { ComponentType } from 'react' + /** @public */ -export type TLShapeErrorFallbackComponent = (props: { error: any }) => any | null +export type TLShapeErrorFallbackComponent = ComponentType<{ error: any }> /** @internal */ export const DefaultShapeErrorFallback: TLShapeErrorFallbackComponent = ({ diff --git a/packages/editor/src/lib/components/DefaultShapeIndicatorErrorFallback.tsx b/packages/editor/src/lib/components/default-components/DefaultShapeIndicatorErrorFallback.tsx similarity index 51% rename from packages/editor/src/lib/components/DefaultShapeIndicatorErrorFallback.tsx rename to packages/editor/src/lib/components/default-components/DefaultShapeIndicatorErrorFallback.tsx index 70a76d47a..f45c32b6d 100644 --- a/packages/editor/src/lib/components/DefaultShapeIndicatorErrorFallback.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultShapeIndicatorErrorFallback.tsx @@ -1,7 +1,9 @@ +import { ComponentType } from 'react' + /** @public */ -export type TLShapeIndicatorErrorFallback = (props: { error: unknown }) => any | null +export type TLShapeIndicatorErrorFallbackComponent = ComponentType<{ error: unknown }> /** @internal */ -export const DefaultShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallback = () => { +export const DefaultShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallbackComponent = () => { return } diff --git a/packages/editor/src/lib/components/DefaultSnapLine.tsx b/packages/editor/src/lib/components/default-components/DefaultSnapLine.tsx similarity index 96% rename from packages/editor/src/lib/components/DefaultSnapLine.tsx rename to packages/editor/src/lib/components/default-components/DefaultSnapLine.tsx index b7110bbc0..7479941d6 100644 --- a/packages/editor/src/lib/components/DefaultSnapLine.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultSnapLine.tsx @@ -1,11 +1,11 @@ -import { rangeIntersection } from '@tldraw/primitives' import classNames from 'classnames' import * as React from 'react' import { type GapsSnapLine, type PointsSnapLine, type SnapLine, -} from '../editor/managers/SnapManager' +} from '../../editor/managers/SnapManager' +import { rangeIntersection } from '../../primitives/utils' function PointsSnapLine({ points, zoom }: { zoom: number } & PointsSnapLine) { const l = 2.5 / zoom @@ -153,11 +153,11 @@ function GapsSnapLine({ gaps, direction, zoom }: { zoom: number } & GapsSnapLine ) } -export type TLSnapLineComponent = (props: { +export type TLSnapLineComponent = React.ComponentType<{ className?: string line: SnapLine zoom: number -}) => any +}> export const DefaultSnapLine: TLSnapLineComponent = ({ className, line, zoom }) => { return ( diff --git a/packages/editor/src/lib/components/DefaultSpinner.tsx b/packages/editor/src/lib/components/default-components/DefaultSpinner.tsx similarity index 85% rename from packages/editor/src/lib/components/DefaultSpinner.tsx rename to packages/editor/src/lib/components/default-components/DefaultSpinner.tsx index 673c09965..d5975034a 100644 --- a/packages/editor/src/lib/components/DefaultSpinner.tsx +++ b/packages/editor/src/lib/components/default-components/DefaultSpinner.tsx @@ -1,4 +1,6 @@ -export type TLSpinnerComponent = () => any | null +import { ComponentType } from 'react' + +export type TLSpinnerComponent = ComponentType export const DefaultSpinner: TLSpinnerComponent = () => { return ( diff --git a/packages/editor/src/lib/components/DefaultSvgDefs.tsx b/packages/editor/src/lib/components/default-components/DefaultSvgDefs.tsx similarity index 100% rename from packages/editor/src/lib/components/DefaultSvgDefs.tsx rename to packages/editor/src/lib/components/default-components/DefaultSvgDefs.tsx diff --git a/packages/editor/src/lib/components/shared.ts b/packages/editor/src/lib/components/shared.ts deleted file mode 100644 index 3abad0ecd..000000000 --- a/packages/editor/src/lib/components/shared.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { StrokePoint, toDomPrecision, Vec2d, VecLike } from '@tldraw/primitives' - -export function getPointerInfo(e: React.PointerEvent | PointerEvent) { - ;(e as any).isKilled = true - - return { - point: { - x: e.clientX, - y: e.clientY, - z: e.pressure, - }, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.metaKey || e.ctrlKey, - pointerId: e.pointerId, - button: e.button, - isPen: e.pointerType === 'pen', - } -} - -function precise(A: VecLike) { - return `${toDomPrecision(A.x)},${toDomPrecision(A.y)} ` -} - -function average(A: VecLike, B: VecLike) { - return `${toDomPrecision((A.x + B.x) / 2)},${toDomPrecision((A.y + B.y) / 2)} ` -} - -/** - * Turn an array of points into a path of quadradic curves. - * - * @param points - The points returned from perfect-freehand - * @param closed - Whether the stroke is closed - */ -export function getSvgPathFromStroke(points: Vec2d[], closed = true): string { - const len = points.length - - if (len < 2) { - return '' - } - - let a = points[0] - let b = points[1] - - if (len === 2) { - // If only two points, just draw a line - return `M${precise(a)}L${precise(b)}` - } - - let result = '' - - for (let i = 2, max = len - 1; i < max; i++) { - a = points[i] - b = points[i + 1] - result += average(a, b) - } - - if (closed) { - // If closed, draw a curve from the last point to the first - return `M${average(points[0], points[1])}Q${precise(points[1])}${average( - points[1], - points[2] - )}T${result}${average(points[len - 1], points[0])}${average(points[0], points[1])}Z` - } else { - // If not closed, draw a curve starting at the first point and - // ending at the midpoint of the last and second-last point, then - // complete the curve with a line segment to the last point. - return `M${precise(points[0])}Q${precise(points[1])}${average(points[1], points[2])}${ - points.length > 3 ? 'T' : '' - }${result}L${precise(points[len - 1])}` - } -} - -/** - * Turn an array of stroke points into a path of quadradic curves. - * - * @param points - The stroke points returned from perfect-freehand - * @param closed - Whether the shape is closed - */ -export function getSvgPathFromStrokePoints(points: StrokePoint[], closed = false): string { - const len = points.length - - if (len < 2) { - return '' - } - - let a = points[0].point - let b = points[1].point - - if (len === 2) { - return `M${precise(a)}L${precise(b)}` - } - - let result = '' - - for (let i = 2, max = len - 1; i < max; i++) { - a = points[i].point - b = points[i + 1].point - result += average(a, b) - } - - if (closed) { - // If closed, draw a curve from the last point to the first - return `M${average(points[0].point, points[1].point)}Q${precise(points[1].point)}${average( - points[1].point, - points[2].point - )}T${result}${average(points[len - 1].point, points[0].point)}${average( - points[0].point, - points[1].point - )}Z` - } else { - // If not closed, draw a curve starting at the first point and - // ending at the midpoint of the last and second-last point, then - // complete the curve with a line segment to the last point. - return `M${precise(points[0].point)}Q${precise(points[1].point)}${average( - points[1].point, - points[2].point - )}${points.length > 3 ? 'T' : ''}${result}L${precise(points[len - 1].point)}` - } -} diff --git a/packages/editor/src/lib/config/TLSessionStateSnapshot.ts b/packages/editor/src/lib/config/TLSessionStateSnapshot.ts index 8ae4237d9..7fc1fd002 100644 --- a/packages/editor/src/lib/config/TLSessionStateSnapshot.ts +++ b/packages/editor/src/lib/config/TLSessionStateSnapshot.ts @@ -19,7 +19,7 @@ import { } from '@tldraw/tlschema' import { objectMapFromEntries } from '@tldraw/utils' import { T } from '@tldraw/validate' -import { uniqueId } from '../utils/data' +import { uniqueId } from '../utils/uniqueId' const tabIdKey = 'TLDRAW_TAB_ID_v2' as const diff --git a/packages/editor/src/lib/config/TLUserPreferences.ts b/packages/editor/src/lib/config/TLUserPreferences.ts index d18b06fe5..7ae67d2c2 100644 --- a/packages/editor/src/lib/config/TLUserPreferences.ts +++ b/packages/editor/src/lib/config/TLUserPreferences.ts @@ -2,7 +2,7 @@ import { atom } from '@tldraw/state' import { defineMigrations, migrate } from '@tldraw/store' import { getDefaultTranslationLocale } from '@tldraw/tlschema' import { T } from '@tldraw/validate' -import { uniqueId } from '../utils/data' +import { uniqueId } from '../utils/uniqueId' const USER_DATA_KEY = 'TLDRAW_USER_DATA_v3' diff --git a/packages/editor/src/lib/config/createTLStore.ts b/packages/editor/src/lib/config/createTLStore.ts index e5589ab4d..7585e886f 100644 --- a/packages/editor/src/lib/config/createTLStore.ts +++ b/packages/editor/src/lib/config/createTLStore.ts @@ -1,13 +1,23 @@ import { HistoryEntry, SerializedStore, Store, StoreSchema } from '@tldraw/store' -import { TLRecord, TLStore, TLStoreProps, createTLSchema } from '@tldraw/tlschema' -import { checkShapesAndAddCore } from './defaultShapes' -import { AnyTLShapeInfo, TLShapeInfo } from './defineShape' +import { + SchemaShapeInfo, + TLRecord, + TLStore, + TLStoreProps, + TLUnknownShape, + createTLSchema, +} from '@tldraw/tlschema' +import { TLShapeUtilConstructor } from '../editor/shapes/ShapeUtil' +import { TLAnyShapeUtilConstructor, checkShapesAndAddCore } from './defaultShapes' /** @public */ export type TLStoreOptions = { initialData?: SerializedStore defaultName?: string -} & ({ shapes: readonly AnyTLShapeInfo[] } | { schema: StoreSchema }) +} & ( + | { shapeUtils: readonly TLAnyShapeUtilConstructor[] } + | { schema: StoreSchema } +) /** @public */ export type TLStoreEventInfo = HistoryEntry @@ -22,7 +32,7 @@ export function createTLStore({ initialData, defaultName = '', ...rest }: TLStor const schema = 'schema' in rest ? rest.schema - : createTLSchema({ shapes: shapesArrayToShapeMap(checkShapesAndAddCore(rest.shapes)) }) + : createTLSchema({ shapes: shapesArrayToShapeMap(checkShapesAndAddCore(rest.shapeUtils)) }) return new Store({ schema, initialData, @@ -32,6 +42,14 @@ export function createTLStore({ initialData, defaultName = '', ...rest }: TLStor }) } -function shapesArrayToShapeMap(shapes: TLShapeInfo[]) { - return Object.fromEntries(shapes.map((s) => [s.type, s])) +function shapesArrayToShapeMap(shapeUtils: TLShapeUtilConstructor[]) { + return Object.fromEntries( + shapeUtils.map((s): [string, SchemaShapeInfo] => [ + s.type, + { + props: s.props, + migrations: s.migrations, + }, + ]) + ) } diff --git a/packages/editor/src/lib/config/defaultShapes.ts b/packages/editor/src/lib/config/defaultShapes.ts index 64c32feab..341f77639 100644 --- a/packages/editor/src/lib/config/defaultShapes.ts +++ b/packages/editor/src/lib/config/defaultShapes.ts @@ -1,47 +1,19 @@ -import { ArrowShape } from '../editor/shapes/arrow/ArrowShape' -import { BookmarkShape } from '../editor/shapes/bookmark/BookmarkShape' -import { DrawShape } from '../editor/shapes/draw/DrawShape' -import { EmbedShape } from '../editor/shapes/embed/EmbedShape' -import { FrameShape } from '../editor/shapes/frame/FrameShape' -import { GeoShape } from '../editor/shapes/geo/GeoShape' -import { GroupShape } from '../editor/shapes/group/GroupShape' -import { HighlightShape } from '../editor/shapes/highlight/HighlightShape' -import { ImageShape } from '../editor/shapes/image/ImageShape' -import { LineShape } from '../editor/shapes/line/LineShape' -import { NoteShape } from '../editor/shapes/note/NoteShape' -import { TextShape } from '../editor/shapes/text/TextShape' -import { VideoShape } from '../editor/shapes/video/VideoShape' -import { AnyTLShapeInfo, TLShapeInfo } from './defineShape' +import { TLShapeUtilConstructor } from '../editor/shapes/ShapeUtil' +import { GroupShapeUtil } from '../editor/shapes/group/GroupShapeUtil' + +/** @public */ +export type TLAnyShapeUtilConstructor = TLShapeUtilConstructor /** @public */ export const coreShapes = [ // created by grouping interactions, probably the corest core shape that we have - GroupShape, - // created by embed menu / url drop - EmbedShape, - // created by copy and paste / url drop - BookmarkShape, - // created by copy and paste / file drop - ImageShape, - // created by copy and paste - TextShape, -] as const - -/** @public */ -export const defaultShapes = [ - DrawShape, - GeoShape, - LineShape, - NoteShape, - FrameShape, - ArrowShape, - HighlightShape, - VideoShape, + GroupShapeUtil, ] as const const coreShapeTypes = new Set(coreShapes.map((s) => s.type)) -export function checkShapesAndAddCore(customShapes: readonly TLShapeInfo[]) { - const shapes: AnyTLShapeInfo[] = [...coreShapes] + +export function checkShapesAndAddCore(customShapes: readonly TLAnyShapeUtilConstructor[]) { + const shapes = [...coreShapes] as TLAnyShapeUtilConstructor[] const addedCustomShapeTypes = new Set() for (const customShape of customShapes) { diff --git a/packages/editor/src/lib/config/defaultTools.ts b/packages/editor/src/lib/config/defaultTools.ts deleted file mode 100644 index a80af28d2..000000000 --- a/packages/editor/src/lib/config/defaultTools.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ArrowShapeTool } from '../editor/shapes/arrow/ArrowShapeTool' -import { DrawShapeTool } from '../editor/shapes/draw/DrawShapeTool' -import { FrameShapeTool } from '../editor/shapes/frame/FrameShapeTool' -import { GeoShapeTool } from '../editor/shapes/geo/GeoShapeTool' -import { HighlightShapeTool } from '../editor/shapes/highlight/HighlightShapeTool' -import { LineShapeTool } from '../editor/shapes/line/LineShapeTool' -import { NoteShapeTool } from '../editor/shapes/note/NoteShapeTool' -import { TextShapeTool } from '../editor/shapes/text/TextShapeTool' -import { EraserTool } from '../editor/tools/EraserTool/EraserTool' -import { HandTool } from '../editor/tools/HandTool/HandTool' -import { LaserTool } from '../editor/tools/LaserTool/LaserTool' -import { TLStateNodeConstructor } from '../editor/tools/StateNode' - -/** @public */ -export const coreTools = [ - // created by copy and paste - TextShapeTool, -] - -/** @public */ -export const defaultTools: TLStateNodeConstructor[] = [ - HandTool, - EraserTool, - LaserTool, - DrawShapeTool, - GeoShapeTool, - LineShapeTool, - NoteShapeTool, - FrameShapeTool, - ArrowShapeTool, - HighlightShapeTool, -] diff --git a/packages/editor/src/lib/config/defineShape.ts b/packages/editor/src/lib/config/defineShape.ts deleted file mode 100644 index 03b5604ba..000000000 --- a/packages/editor/src/lib/config/defineShape.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Migrations } from '@tldraw/store' -import { ShapeProps, TLBaseShape, TLUnknownShape } from '@tldraw/tlschema' -import { assert } from '@tldraw/utils' -import { TLShapeUtilConstructor } from '../editor/shapes/ShapeUtil' - -/** @public */ -export type TLShapeInfo = { - type: T['type'] - util: TLShapeUtilConstructor - props?: ShapeProps - migrations?: Migrations -} - -export type AnyTLShapeInfo = TLShapeInfo> - -/** @public */ -export function defineShape( - type: T['type'], - opts: Omit, 'type'> -): TLShapeInfo { - assert( - type === opts.util.type, - `Shape type "${type}" does not match util type "${opts.util.type}"` - ) - return { type, ...opts } -} diff --git a/packages/editor/src/lib/constants.ts b/packages/editor/src/lib/constants.ts index f73d6f3c0..c7f0e707a 100644 --- a/packages/editor/src/lib/constants.ts +++ b/packages/editor/src/lib/constants.ts @@ -1,21 +1,10 @@ -import { EASINGS } from '@tldraw/primitives' +import { EASINGS } from './primitives/easings' /** @internal */ export const MAX_SHAPES_PER_PAGE = 2000 /** @internal */ export const MAX_PAGES = 40 -/** @internal */ -export const REMOVE_SYMBOL = Symbol('remove') - -/** @internal */ -export const RICH_TYPES: Record = { - Date: true, - RegExp: true, - String: true, - Number: true, -} - /** @internal */ export const ANIMATION_SHORT_MS = 80 /** @internal */ @@ -44,17 +33,9 @@ export const MAJOR_NUDGE_FACTOR = 10 /** @internal */ export const MINOR_NUDGE_FACTOR = 1 -/** @internal */ -export const MAX_ASSET_WIDTH = 1000 -/** @internal */ -export const MAX_ASSET_HEIGHT = 1000 - /** @internal */ export const GRID_INCREMENT = 5 -/** @internal */ -export const MIN_CROP_SIZE = 8 - /** @internal */ export const DOUBLE_CLICK_DURATION = 450 /** @internal */ @@ -84,7 +65,7 @@ export const DEFAULT_ANIMATION_OPTIONS = { } /** @internal */ -export const HAND_TOOL_FRICTION = 0.09 +export const CAMERA_SLIDE_FRICTION = 0.09 /** @public */ export const GRID_STEPS = [ diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index 6fb93fed3..e1e8eda7e 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -1,31 +1,9 @@ -import { - getIndexAbove, - getIndexBetween, - getIndices, - getIndicesAbove, - getIndicesBetween, - sortByIndex, -} from '@tldraw/indices' -import { - Box2d, - EASINGS, - MatLike, - Matrix2d, - Matrix2dModel, - PI2, - Vec2d, - VecLike, - approximately, - areAnglesCompatible, - clamp, - intersectPolygonPolygon, - pointInPolygon, -} from '@tldraw/primitives' import { EMPTY_ARRAY, atom, computed, transact } from '@tldraw/state' import { ComputedCache, RecordType } from '@tldraw/store' import { Box2dModel, CameraRecordType, + EmbedDefinition, InstancePageStateRecordType, PageRecordType, StyleProp, @@ -48,7 +26,6 @@ import { TLPage, TLPageId, TLParentId, - TLRecord, TLScribble, TLShape, TLShapeId, @@ -73,20 +50,17 @@ import { deepCopy, getOwnProperty, hasOwnProperty, - partition, sortById, structuredClone, } from '@tldraw/utils' -import { EventEmitter } from 'eventemitter3' -import { nanoid } from 'nanoid' +import EventEmitter from 'eventemitter3' import { TLUser, createTLUser } from '../config/createTLUser' import { checkShapesAndAddCore } from '../config/defaultShapes' -import { coreTools } from '../config/defaultTools' -import { AnyTLShapeInfo } from '../config/defineShape' import { ANIMATION_MEDIUM_MS, CAMERA_MAX_RENDERING_INTERVAL, CAMERA_MOVING_TIMEOUT, + CAMERA_SLIDE_FRICTION, COARSE_DRAG_DISTANCE, COLLABORATOR_IDLE_TIMEOUT, DEFAULT_ANIMATION_OPTIONS, @@ -97,7 +71,6 @@ import { FOLLOW_CHASE_ZOOM_SNAP, FOLLOW_CHASE_ZOOM_UNSNAP, GRID_INCREMENT, - HAND_TOOL_FRICTION, INTERNAL_POINTER_IDS, MAJOR_NUDGE_FACTOR, MAX_PAGES, @@ -108,30 +81,44 @@ import { SVG_PADDING, ZOOMS, } from '../constants' +import { Box2d } from '../primitives/Box2d' +import { MatLike, Matrix2d, Matrix2dModel } from '../primitives/Matrix2d' +import { Vec2d, VecLike } from '../primitives/Vec2d' +import { EASINGS } from '../primitives/easings' +import { intersectPolygonPolygon } from '../primitives/intersect' +import { PI2, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils' import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap' import { WeakMapCache } from '../utils/WeakMapCache' import { dataUrlToFile } from '../utils/assets' -import { getIncrementedName, uniqueId } from '../utils/data' +import { getIncrementedName } from '../utils/getIncrementedName' import { getReorderingShapesChanges } from '../utils/reorderShapes' +import { + getIndexAbove, + getIndexBetween, + getIndices, + getIndicesAbove, + getIndicesBetween, + sortByIndex, +} from '../utils/reordering/reordering' import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation' +import { uniqueId } from '../utils/uniqueId' import { arrowBindingsIndex } from './derivations/arrowBindingsIndex' import { parentsToChildrenWithIndexes } from './derivations/parentsToChildrenWithIndexes' import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage' import { ClickManager } from './managers/ClickManager' -import { ExternalContentManager, TLExternalContent } from './managers/ExternalContentManager' import { HistoryManager } from './managers/HistoryManager' import { SnapManager } from './managers/SnapManager' import { TextManager } from './managers/TextManager' import { TickManager } from './managers/TickManager' import { UserPreferencesManager } from './managers/UserPreferencesManager' -import { ShapeUtil, TLResizeMode } from './shapes/ShapeUtil' -import { ArrowInfo } from './shapes/arrow/arrow/arrow-types' -import { getCurvedArrowInfo } from './shapes/arrow/arrow/curved-arrow' -import { getArrowTerminalsInArrowSpace, getIsArrowStraight } from './shapes/arrow/arrow/shared' -import { getStraightArrowInfo } from './shapes/arrow/arrow/straight-arrow' -import { SvgExportContext, SvgExportDef } from './shapes/shared/SvgExportContext' +import { ShapeUtil, TLResizeMode, TLShapeUtilConstructor } from './shapes/ShapeUtil' +import { ArrowInfo } from './shapes/shared/arrow/arrow-types' +import { getCurvedArrowInfo } from './shapes/shared/arrow/curved-arrow' +import { getArrowTerminalsInArrowSpace, getIsArrowStraight } from './shapes/shared/arrow/shared' +import { getStraightArrowInfo } from './shapes/shared/arrow/straight-arrow' import { RootState } from './tools/RootState' import { StateNode, TLStateNodeConstructor } from './tools/StateNode' +import { SvgExportContext, SvgExportDef } from './types/SvgExportContext' import { TLContent } from './types/clipboard-types' import { TLEventMap } from './types/emit-types' import { TLEventInfo, TLPinchEventInfo, TLPointerEventInfo } from './types/event-types' @@ -160,7 +147,7 @@ export interface TLEditorOptions { /** * An array of shapes to use in the editor. These will be used to create and manage shapes in the editor. */ - shapes: readonly AnyTLShapeInfo[] + shapeUtils: readonly TLShapeUtilConstructor[] /** * An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor. */ @@ -174,36 +161,49 @@ export interface TLEditorOptions { * given, the body element will be used. */ getContainer: () => HTMLElement + + initialState?: string } /** @public */ export class Editor extends EventEmitter { - constructor({ store, user, shapes, tools, getContainer }: TLEditorOptions) { + constructor({ store, user, shapeUtils, tools, getContainer, initialState }: TLEditorOptions) { super() this.store = store + this.snaps = new SnapManager(this) + this.user = new UserPreferencesManager(user ?? createTLUser()) this.getContainer = getContainer ?? (() => document.body) this.textMeasure = new TextManager(this) - this.root = new RootState(this) + class NewRoot extends RootState { + static override initial = initialState ?? '' + } - const allTools = [...coreTools, ...tools] - const allShapes = checkShapesAndAddCore(shapes) + this.root = new NewRoot(this) + this.root.children = {} + + const allShapeUtils = checkShapesAndAddCore(shapeUtils) + + // console.log( + // 'after checking shapes', + // [...allShapeUtils].map((u) => u.type) + // ) const shapeTypesInSchema = new Set( Object.keys(store.schema.types.shape.migrations.subTypeMigrations!) ) - for (const shape of allShapes) { - if (!shapeTypesInSchema.has(shape.type)) { + for (const shapeUtil of allShapeUtils) { + if (!shapeTypesInSchema.has(shapeUtil.type)) { throw Error( - `Editor and store have different shapes: "${shape.type}" was passed into the editor but not the schema` + `Editor and store have different shapes: "${shapeUtil.type}" was passed into the editor but not the schema` ) } - shapeTypesInSchema.delete(shape.type) + shapeTypesInSchema.delete(shapeUtil.type) } if (shapeTypesInSchema.size > 0) { throw Error( @@ -212,12 +212,16 @@ export class Editor extends EventEmitter { }" is present in the store schema but not provided to the editor` ) } - const shapeUtils = {} as Record + const _shapeUtils = {} as Record> + const _styleProps = {} as Record, string>> const allStylesById = new Map>() - for (const { util: Util, props } of allShapes) { - const propKeysByStyle = getShapePropKeysByStyle(props ?? {}) - shapeUtils[Util.type] = new Util(this, Util.type, propKeysByStyle) + for (const Util of allShapeUtils) { + const util = new Util(this) + _shapeUtils[Util.type] = util + + const propKeysByStyle = getShapePropKeysByStyle(Util.props ?? {}) + _styleProps[Util.type] = propKeysByStyle for (const style of propKeysByStyle.keys()) { if (!allStylesById.has(style.id)) { @@ -230,16 +234,17 @@ export class Editor extends EventEmitter { } } - this.shapeUtils = shapeUtils + this.shapeUtils = _shapeUtils + this.styleProps = _styleProps // Tools. // Accept tools from constructor parameters which may not conflict with the root note's default or // "baked in" tools, select and zoom. - for (const Tool of allTools) { + for (const Tool of [...tools]) { if (hasOwnProperty(this.root.children!, Tool.id)) { throw Error(`Can't override tool with id "${Tool.id}"`) } - this.root.children![Tool.id] = new Tool(this) + this.root.children![Tool.id] = new Tool(this, this.root) } if (typeof window !== 'undefined' && 'navigator' in window) { @@ -339,6 +344,10 @@ export class Editor extends EventEmitter { true ) + if (initialState && this.root.children[initialState] === undefined) { + throw Error(`No state found for initialState "${initialState}".`) + } + this.root.enter(undefined, 'initial') if (this.instanceState.followingUserId) { @@ -384,7 +393,7 @@ export class Editor extends EventEmitter { * * @public */ - readonly snaps = new SnapManager(this) + readonly snaps: SnapManager /** * A manager for the user and their preferences. @@ -530,14 +539,14 @@ export class Editor extends EventEmitter { * editor.mark('flip shapes') * ``` * - * @param reason - The reason for the mark. + * @param markId - The mark's id, usually the reason for adding the mark. * @param onUndo - Whether to stop at the mark when undoing. * @param onRedo - Whether to stop at the mark when redoing. * * @public */ - mark(reason?: string, onUndo?: boolean, onRedo?: boolean) { - return this.history.mark(reason, onUndo, onRedo) + mark(markId?: string, onUndo?: boolean, onRedo?: boolean) { + return this.history.mark(markId, onUndo, onRedo) } /** @@ -601,6 +610,8 @@ export class Editor extends EventEmitter { */ shapeUtils: { readonly [K in string]?: ShapeUtil } + styleProps: { [key: string]: Map, string> } + /** * Get a shape util from a shape itself. * @@ -1055,21 +1066,19 @@ export class Editor extends EventEmitter { * * @public */ - get currentToolId(): string { - const activeTool = this.root.current.value - let activeToolId = activeTool?.id + @computed get currentToolId(): string { + const { currentTool } = this + if (!currentTool) return '' + return currentTool.currentToolIdMask ?? currentTool.id + } - // Often a tool will transition into one of the following select states after the initial pointerdown: 'translating', 'resizing', 'dragging_handle' - // It should then supply the tool id to the `onInteractionEnd` property to tell us which tool initially triggered the interaction. - // If tool lock mode is on then tldraw will switch to the given tool id. - // If tool lock mode is off then tldraw will switch back to the select tool when the interaction ends. - - if (activeToolId === 'select' || activeToolId === 'zoom') { - const currentChildState = activeTool?.current.value as any - activeToolId = currentChildState?.info?.onInteractionEnd ?? 'select' - } - - return activeToolId ?? 'select' + /** + * The current selected tool. + * + * @public + */ + @computed get currentTool(): StateNode | undefined { + return this.root.current.value } /** @@ -1077,8 +1086,8 @@ export class Editor extends EventEmitter { * * @example * ```ts - * editor.setSelectedTool('hand') - * editor.setSelectedTool('hand', { date: Date.now() }) + * editor.setCurrentTool('hand') + * editor.setCurrentTool('hand', { date: Date.now() }) * ``` * * @param id - The id of the tool to select. @@ -1086,7 +1095,7 @@ export class Editor extends EventEmitter { * * @public */ - setSelectedTool(id: string, info = {}) { + setCurrentTool(id: string, info = {}): this { this.root.transition(id, info) return this } @@ -1119,41 +1128,6 @@ export class Editor extends EventEmitter { /* ----------------- Internal State ----------------- */ - /** - * Blur the app, cancelling any interaction state. - * - * @example - * ```ts - * editor.blur() - * ``` - * - * @public - */ - blur() { - this.complete() - this.getContainer().blur() - this._isFocused.set(false) - return this - } - - /** - * Focus the editor. - * - * @example - * ```ts - * editor.focus() - * ``` - * - * @public - */ - focus() { - this.getContainer().focus() - this._isFocused.set(true) - return this - } - - private _canMoveCamera = atom('can move camera', true) - /** * Whether the editor's camera can move. * @@ -1166,76 +1140,69 @@ export class Editor extends EventEmitter { * * @public */ - get canMoveCamera() { + get canMoveCamera(): boolean { return this._canMoveCamera.value } - set canMoveCamera(canMove: boolean) { this._canMoveCamera.set(canMove) } - - private _isFocused = atom('_isFocused', false) + private _canMoveCamera = atom('can move camera', true) /** * Whether or not the editor is focused. * * @public */ - get isFocused() { + @computed get isFocused(): boolean { return this._isFocused.value } + set isFocused(isFocused) { + if (isFocused) { + this.getContainer().focus() + this._isFocused.set(true) + } else { + this.complete() + this.getContainer().blur() + } + this._isFocused.set(isFocused) + } + private _isFocused = atom('_isFocused', false) - /** @internal */ - private _dpr = atom( - 'devicePixelRatio', - typeof window === 'undefined' ? 1 : window.devicePixelRatio - ) + focus = () => (this.isFocused = true) + blur = () => (this.isFocused = false) /** * The window's device pixel ratio. * * @public */ - @computed get devicePixelRatio() { + @computed get devicePixelRatio(): number { return this._dpr.value } - - /** - * Set the window's device pixel ratio. This should usually only be set by the Canvas component. - * - * ```ts - * editor.setDevicePixelRatio(2) - * ``` - * - * @public - */ - setDevicePixelRatio(dpr: number) { + set devicePixelRatio(dpr: number) { this._dpr.set(dpr) - return this } + /** @internal */ + private _dpr = atom('dpr', typeof window === 'undefined' ? 1 : window.devicePixelRatio) // Coarse Pointer - /** @internal */ - private _isCoarsePointer = atom('isCoarsePointer', false as any) - /** * Whether the user is using a "coarse" pointer, such as on a touch screen. This is automatically set by the canvas. * * @public **/ - get isCoarsePointer() { + get isCoarsePointer(): boolean { return this._isCoarsePointer.value } - set isCoarsePointer(v) { this._isCoarsePointer.set(v) } + /** @internal */ + private _isCoarsePointer = atom('isCoarsePointer', false as any) // Menus - private _openMenus = atom('open-menus', [] as string[]) - /** * A set of strings representing any open menus. When menus are open, * certain interactions will behave differently; for example, when a @@ -1250,6 +1217,7 @@ export class Editor extends EventEmitter { @computed get openMenus(): string[] { return this._openMenus.value } + private _openMenus = atom('open-menus', [] as string[]) /** * Add an open menu. @@ -1261,7 +1229,7 @@ export class Editor extends EventEmitter { * * @public */ - addOpenMenu(id: string) { + addOpenMenu(id: string): this { const menus = new Set(this.openMenus) if (!menus.has(id)) { menus.add(id) @@ -1280,7 +1248,7 @@ export class Editor extends EventEmitter { * * @public */ - deleteOpenMenu(id: string) { + deleteOpenMenu(id: string): this { const menus = new Set(this.openMenus) if (menus.has(id)) { menus.delete(id) @@ -1292,20 +1260,19 @@ export class Editor extends EventEmitter { /** * Get whether any menus are open. * + * @example + * ```ts + * editor.isMenuOpen() + * ``` + * * @public */ - @computed get isMenuOpen() { + @computed get isMenuOpen(): boolean { return this.openMenus.length > 0 } // Changing style - /** @internal */ - private _isChangingStyle = atom('isChangingStyle', false as any) - - /** @internal */ - private _isChangingStyleTimeout = -1 as any - /** * Whether the user is currently changing the style of a shape. This may cause the UI to change. * @@ -1316,10 +1283,9 @@ export class Editor extends EventEmitter { * * @public */ - get isChangingStyle() { + get isChangingStyle(): boolean { return this._isChangingStyle.value } - set isChangingStyle(v) { this._isChangingStyle.set(v) // Clear any reset timeout @@ -1329,49 +1295,28 @@ export class Editor extends EventEmitter { this._isChangingStyleTimeout = setTimeout(() => (this.isChangingStyle = false), 2000) } } + /** @internal */ + private _isChangingStyle = atom('isChangingStyle', false as any) + /** @internal */ + private _isChangingStyleTimeout = -1 as any // Pen Mode - /** @internal */ - private _isPenMode = atom('isPenMode', false as any) - /** * Whether the editor is in pen mode or not. * * @public **/ - get isPenMode() { + get isPenMode(): boolean { return this._isPenMode.value } - - /** - * Set whether the editor is in pen mode or not. - * - * @public - **/ - setPenMode(isPenMode: boolean): this { + set isPenMode(isPenMode: boolean) { if (isPenMode !== this.isPenMode) { this._isPenMode.set(isPenMode) } - return this - } - - // Read only - - private _isReadOnly = atom('isReadOnly', false as any) - - /** - * Set whether the editor is in read-only mode or not. - * - * @public - **/ - setReadOnly(isReadOnly: boolean): this { - this._isReadOnly.set(isReadOnly) - if (isReadOnly) { - this.setSelectedTool('hand') - } - return this } + /** @internal */ + private _isPenMode = atom('isPenMode', false as any) /** * Whether the editor is in read-only mode or not. @@ -1381,6 +1326,13 @@ export class Editor extends EventEmitter { get isReadOnly() { return this._isReadOnly.value } + set isReadOnly(isReadOnly: boolean) { + this._isReadOnly.set(isReadOnly) + if (isReadOnly) { + this.setCurrentTool('hand') + } + } + private _isReadOnly = atom('isReadOnly', false as any) /* ---------------- Document Settings --------------- */ @@ -1398,51 +1350,30 @@ export class Editor extends EventEmitter { * * @public **/ - updateDocumentSettings(settings: Partial) { + updateDocumentSettings(settings: Partial): this { this.store.put([{ ...this.documentSettings, ...settings }]) - } - - /** - * The document's grid size. - * - * @public - **/ - get gridSize() { - return this.documentSettings.gridSize + return this } /** @internal */ - get projectName() { + @computed get projectName() { return this.documentSettings.name } - - /** @internal */ - setProjectName(name: string) { + set projectName(name: string) { this.updateDocumentSettings({ name }) } /* ---------------------- User ---------------------- */ /** - * Get the user's locale. + * The user's locale. * * @public */ - get locale() { + @computed get locale() { return this.user.locale } - - /** - * Update the user's locale. This affects which translations are used when rendering UI elements. - * - * @example - * ```ts - * editor.setLocale('fr') - * ``` - * - * @public - */ - setLocale(locale: string) { + set locale(locale: string) { this.user.updateUserPreferences({ locale }) } @@ -1451,20 +1382,13 @@ export class Editor extends EventEmitter { * * @public **/ - get isSnapMode() { + @computed get isSnapMode() { return this.user.isSnapMode } - - /** - * Set whether the user has "always snap" mode enabled. - * - * @public - **/ - setSnapMode(isSnapMode: boolean) { + set isSnapMode(isSnapMode: boolean) { if (isSnapMode !== this.isSnapMode) { this.user.updateUserPreferences({ isSnapMode }) } - return this } /** @@ -1472,43 +1396,27 @@ export class Editor extends EventEmitter { * * @public **/ - get isDarkMode() { + @computed get isDarkMode() { return this.user.isDarkMode } - - /** - * Set whether the user has dark mode enabled. - * - * @public - **/ - setDarkMode(isDarkMode: boolean) { + set isDarkMode(isDarkMode: boolean) { if (isDarkMode !== this.isDarkMode) { this.user.updateUserPreferences({ isDarkMode }) } - return this } /** - * The user's chosen animation speed. + * The user's chosen animation speed. 0 is disabled, 1 is full speed. * * @public */ - get animationSpeed() { + @computed get animationSpeed() { return this.user.animationSpeed } - - /** - * Set the user's chosen animation speed. - * Set to 0.0 to disable animations. - * Set to 1.0 for full speed. - * - * @public - */ - setAnimationSpeed(animationSpeed: number): this { + set animationSpeed(animationSpeed: number) { if (animationSpeed !== this.animationSpeed) { this.user.updateUserPreferences({ animationSpeed }) } - return this } /* ----------------- Instance State ----------------- */ @@ -1518,171 +1426,10 @@ export class Editor extends EventEmitter { * * @public */ - get instanceState(): TLInstance { + @computed get instanceState(): TLInstance { return this.store.get(TLINSTANCE_ID)! } - /** - * The instance's cursor state. - * - * @public - **/ - get cursor() { - return this.instanceState.cursor - } - - /** - * The instance's brush state. - * - * @public - **/ - get brush() { - return this.instanceState.brush - } - - /** - * Set the current brush. - * - * @example - * ```ts - * editor.setBrush({ x: 0, y: 0, w: 100, h: 100 }) - * editor.setBrush() // Clears the brush - * ``` - * - * @param brush - The brush box model to set, or null for no brush model. - * - * @public - */ - setBrush(brush: Box2dModel | null = null): this { - if (!brush && !this.brush) return this - this.updateInstanceState({ brush }, true) - return this - } - - /** - * The instance's zoom brush state. - * - * @public - **/ - get zoomBrush() { - return this.instanceState.zoomBrush - } - - /** - * Set the current zoom brush. - * - * @example - * ```ts - * editor.setZoomBrush({ x: 0, y: 0, w: 100, h: 100 }) - * editor.setZoomBrush() // Clears the zoom - * ``` - * - * @param zoomBrush - The zoom box model to set, or null for no zoom model. - * - * @public - */ - setZoomBrush(zoomBrush: Box2dModel | null = null): this { - if (!zoomBrush && !this.zoomBrush) return this - this.updateInstanceState({ zoomBrush }, true) - return this - } - - /** - * The instance's scribble state. - * - * @public - **/ - get scribble() { - return this.instanceState.scribble - } - - /** - * Set the current scribble. - * - * @example - * ```ts - * editor.setScribble(nextScribble) - * editor.setScribble() // clears the scribble - * ``` - * - * @param scribble - The new scribble object. - * - * @public - */ - setScribble(scribble: TLScribble | null = null): this { - this.updateInstanceState({ scribble }, true) - return this - } - - // Focus Mode - - /** - * Whether the instance is in focus mode or not. - * - * @public - **/ - get isFocusMode() { - return this.instanceState.isFocusMode - } - - /** - * Set whether the instance is in focus mode or not. - * - * @public - **/ - setFocusMode(isFocusMode: boolean): this { - if (isFocusMode !== this.isFocusMode) { - this.updateInstanceState({ isFocusMode }, true) - } - return this - } - - // Tool Locked - - /** - * Whether the instance has "tool lock" mode enabled. - * - * @public - **/ - get isToolLocked() { - return this.instanceState.isToolLocked - } - - /** - * Set whether the instance has "tool lock" mode enabled. - * - * @public - **/ - setToolLocked(isToolLocked: boolean): this { - if (isToolLocked !== this.isToolLocked) { - this.updateInstanceState({ isToolLocked }, true) - } - return this - } - - // Grid Mode - - /** - * Whether the instance's grid is enabled. - * - * @public - **/ - get isGridMode() { - return this.instanceState.isGridMode - } - - /** - * Set whether the instance's grid is enabled. - * - * @public - **/ - setGridMode(isGridMode: boolean): this { - if (isGridMode !== this.isGridMode) { - this.updateInstanceState({ isGridMode }, true) - } - return this - } - /** * Update the instance's state. * @@ -1728,37 +1475,98 @@ export class Editor extends EventEmitter { ) /** - * Set the current cursor. - * - * @example - * ```ts - * editor.setCursor({ type: 'default' }) - * editor.setCursor({ type: 'default', rotation: Math.PI / 2, color: 'red' }) - * ``` - * - * @param cursor - A partial of the cursor object. + * The instance's cursor state. * * @public - */ - setCursor(cursor: Partial): this { + **/ + @computed get cursor(): TLCursor { + return this.instanceState.cursor + } + set cursor(cursor) { const current = this.cursor - const next = { - ...current, - rotation: 0, - ...cursor, + if (!(current.type === cursor.type && current.rotation === cursor.rotation)) { + this.updateInstanceState({ cursor }, true) } + } - if ( - !( - current.type === next.type && - current.rotation === next.rotation && - current.color === next.color - ) - ) { - this.updateInstanceState({ cursor: next }, true) + /** + * The instance's brush state. + * + * @public + **/ + @computed get brush() { + return this.instanceState.brush + } + set brush(brush: Box2dModel | null) { + if (!brush && !this.brush) return + this.updateInstanceState({ brush }, true) + } + + /** + * The instance's zoom brush state. + * + * @public + **/ + @computed get zoomBrush() { + return this.instanceState.zoomBrush + } + set zoomBrush(zoomBrush: Box2dModel | null) { + if (!zoomBrush && !this.zoomBrush) return + this.updateInstanceState({ zoomBrush }, true) + } + + /** + * The instance's scribble state. + * + * @public + **/ + @computed get scribble() { + return this.instanceState.scribble + } + set scribble(scribble: TLScribble | null) { + this.updateInstanceState({ scribble }, true) + } + + /** + * Whether the instance is in focus mode or not. + * + * @public + **/ + @computed get isFocusMode() { + return this.instanceState.isFocusMode + } + set isFocusMode(isFocusMode: boolean) { + if (isFocusMode !== this.isFocusMode) { + this.updateInstanceState({ isFocusMode }, true) } + } - return this + /** + * Whether the instance has "tool lock" mode enabled. + * + * @public + **/ + @computed get isToolLocked() { + return this.instanceState.isToolLocked + } + set isToolLocked(isToolLocked: boolean) { + if (isToolLocked !== this.isToolLocked) { + this.updateInstanceState({ isToolLocked }, true) + } + } + + /** + * Whether the instance's grid is enabled. + * + * @public + **/ + @computed get isGridMode() { + return this.instanceState.isGridMode + } + set isGridMode(isGridMode: boolean) { + if (isGridMode !== this.isGridMode) { + this.updateInstanceState({ isGridMode }, true) + } } /* ------------------- Page State ------------------- */ @@ -1831,8 +1639,6 @@ export class Editor extends EventEmitter { } ) - // Selected Ids - /** * The current selected ids. * @@ -1916,19 +1722,15 @@ export class Editor extends EventEmitter { } /** - * Determine whether a not a shape is within the current selection. A shape is within the - * selection if it or any of its parents is selected. + * Determine whether or not any of a shape's ancestors are selected. * * @param id - The id of the shape to check. * * @public */ - isWithinSelection(id: TLShapeId) { + isAncestorSelected(id: TLShapeId) { const shape = this.getShapeById(id) if (!shape) return false - - if (this.isSelected(id)) return true - return !!this.findAncestor(shape, (parent) => this.isSelected(parent.id)) } @@ -2134,26 +1936,47 @@ export class Editor extends EventEmitter { // Focus Layer Id /** - * The shape id of the current focus layer. + * The shape id of the current focus layer. Null when the focus layer id is the current page. * * @public */ - get focusLayerId() { + get focusLayerId(): TLShapeId | TLPageId { return this.pageState.focusLayerId ?? this.currentPageId } - - /** - * The shape of the current focus layer. - * - * @public - */ - get focusLayerShape(): TLShape | undefined { - const id = this.pageState.focusLayerId - if (!id) { - return - } - return this.getShapeById(id) + set focusLayerId(next) { + this._setFocusLayerId(next) } + /** @internal */ + private _setFocusLayerId = this.history.createCommand( + 'setFocusLayerId', + (next: undefined | TLShapeId | TLPageId) => { + next = isPageId(next as string) ? undefined : (next as TLShapeId | undefined) + // When we first click an empty canvas we don't want this to show up in the undo stack + if (!next && !this.canUndo) { + return + } + const prev = this.pageState.focusLayerId + return { + data: { + prev, + next, + }, + preservesRedoStack: true, + squashing: true, + } + }, + { + do: ({ next }) => { + this.store.update(this.pageState.id, (s) => ({ ...s, focusLayerId: next ?? null })) + }, + undo: ({ prev }) => { + this.store.update(this.pageState.id, (s) => ({ ...s, focusLayerId: prev })) + }, + squash({ prev }, { next }) { + return { prev, next } + }, + } + ) /** * Exit the current focus layer, moving up to the next group if there is one. @@ -2170,55 +1993,17 @@ export class Editor extends EventEmitter { this.isShapeOfType(shape, 'group') ) // If we have an ancestor that can become a focused layer, set it as the focused layer - this.setFocusLayer(match?.id ?? null) + this.focusLayerId = match?.id ?? this.currentPageId this.select(focusedShape.id) } else { // If there's no focused shape, then clear the focus layer and clear selection - this.setFocusLayer(null) + this.focusLayerId = this.currentPageId this.selectNone() } return this } - /** - * Set the focus layer to the given shape id. - * - * @param next - The next focus layer id or null to reset the focus layer to the page - * - * @public - */ - setFocusLayer(next: null | TLShapeId) { - this._setFocusLayer(next) - return this - } - - /** @internal */ - private _setFocusLayer = this.history.createCommand( - 'setFocusLayer', - (next: null | TLShapeId) => { - // When we first click an empty canvas we don't want this to show up in the undo stack - if (next === null && !this.canUndo) { - return - } - const prev = this.pageState.focusLayerId - return { data: { prev, next }, preservesRedoStack: true, squashing: true } - }, - { - do: ({ next }) => { - this.store.update(this.pageState.id, (s) => ({ ...s, focusLayerId: next })) - }, - undo: ({ prev }) => { - this.store.update(this.pageState.id, (s) => ({ ...s, focusLayerId: prev })) - }, - squash({ prev }, { next }) { - return { prev, next } - }, - } - ) - - // Editing Id - /** * The current editing shape's id. * @@ -2227,15 +2012,7 @@ export class Editor extends EventEmitter { get editingId() { return this.pageState.editingId } - - /** - * Set the current editing id. - * - * @param id - The id of the shape to edit or null to clear the editing id. - * - * @public - */ - setEditingId(id: TLShapeId | null): this { + set editingId(id) { if (!id) { this.setPageState({ editingId: null }) } else { @@ -2247,13 +2024,6 @@ export class Editor extends EventEmitter { } } } - - return this - } - - @computed get editingShape() { - if (!this.editingId) return null - return this.getShapeById(this.editingId) ?? null } // Hovered Id @@ -2267,36 +2037,9 @@ export class Editor extends EventEmitter { @computed get hoveredId() { return this.pageState.hoveredId } - - /** - * Set the current hovered shape. - * - * @example - * ```ts - * editor.setHoveredId('box1') - * editor.setHoveredId() // Clears the hovered shape. - * ``` - * - * @param id - The id of the page to set as the current page - * - * @public - */ - setHoveredId(id: TLShapeId | null = null): this { - if (id === this.pageState.hoveredId) return this - + set hoveredId(id: TLShapeId | null) { + if (id === this.pageState.hoveredId) return this.setPageState({ hoveredId: id }, true) - return this - } - - /** - * The current hovered shape. - * - * @readonly - * @public - */ - @computed get hoveredShape() { - if (!this.hoveredId) return null - return this.getShapeById(this.hoveredId) ?? null } // Hinting ids @@ -2309,22 +2052,11 @@ export class Editor extends EventEmitter { @computed get hintingIds() { return this.pageState.hintingIds } - - /** - * Set the hinted shape ids. - * - * @param ids - The ids to set as hinted. - * - * @public - */ - setHintingIds(ids: TLShapeId[]): this { + set hintingIds(ids: TLShapeId[]) { // always ephemeral this.store.update(this.pageState.id, (s) => ({ ...s, hintingIds: dedupe(ids) })) - return this } - // Erasing Ids - /** * The editor's current erasing ids. * @@ -2333,6 +2065,11 @@ export class Editor extends EventEmitter { @computed get erasingIds() { return this.pageState.erasingIds } + set erasingIds(ids: TLShapeId[]) { + const erasingIds = this.erasingIdsSet + if (ids.length === erasingIds.size && ids.every((id) => erasingIds.has(id))) return + this.setPageState({ erasingIds: ids }, true) + } /** * A derived set containing the current erasing ids. @@ -2344,29 +2081,6 @@ export class Editor extends EventEmitter { return new Set(this.erasingIds) } - /** - * Set the current erasing shapes. - * - * @example - * ```ts - * editor.setErasingIds(['box1', 'box2']) - * editor.setErasingIds() // Clears the erasing set - * ``` - * - * @param ids - The ids of shapes to set as erasing. - * - * @public - */ - setErasingIds(ids: TLShapeId[] = []): this { - const erasingIds = this.erasingIdsSet - if (ids.length === erasingIds.size && ids.every((id) => erasingIds.has(id))) return this - - this.setPageState({ erasingIds: ids }, true) - return this - } - - // Cropping Id - /** * The current cropping shape's id. * @@ -2375,20 +2089,12 @@ export class Editor extends EventEmitter { get croppingId() { return this.pageState.croppingId } - - /** - * Set the current cropping shape's id. - * - * @param id - The id of the shape to crop or null to clear the cropping id. - * - * @public - */ - setCroppingId(id: TLShapeId | null): this { + set croppingId(id: TLShapeId | null) { if (id !== this.croppingId) { if (!id) { this.setPageState({ croppingId: null }) if (this.isInAny('select.crop', 'select.pointing_crop_handle', 'select.cropping')) { - this.setSelectedTool('select.idle') + this.setCurrentTool('select.idle') } } else { const shape = this.getShapeById(id)! @@ -2398,7 +2104,6 @@ export class Editor extends EventEmitter { } } } - return this } /** @internal */ @@ -3676,7 +3381,7 @@ export class Editor extends EventEmitter { } /** @internal */ - readonly _renderingBounds = atom('rendering viewport', new Box2d()) + private readonly _renderingBounds = atom('rendering viewport', new Box2d()) /** * The current rendering bounds in page space, expanded slightly. Used for determining which shapes @@ -3689,7 +3394,7 @@ export class Editor extends EventEmitter { } /** @internal */ - readonly _renderingBoundsExpanded = atom('rendering viewport expanded', new Box2d()) + private readonly _renderingBoundsExpanded = atom('rendering viewport expanded', new Box2d()) /** * Update the rendering bounds. This should be called when the viewport has stopped changing, such @@ -5576,11 +5281,12 @@ export class Editor extends EventEmitter { getOutermostSelectableShape(shape: TLShape, filter?: (shape: TLShape) => boolean): TLShape { let match = shape let node = shape as TLShape | undefined + const focusLayerShape = this.focusLayerId ? this.getShapeById(this.focusLayerId) : undefined while (node) { if ( this.isShapeOfType(node, 'group') && this.focusLayerId !== node.id && - !this.hasAncestor(this.focusLayerShape, node.id) && + !this.hasAncestor(focusLayerShape, node.id) && (filter?.(node) ?? true) ) { match = node @@ -5633,11 +5339,12 @@ export class Editor extends EventEmitter { */ nudgeShapes(ids: TLShapeId[], direction: Vec2dModel, major = false, ephemeral = false): this { if (ids.length <= 0) return this + const { gridSize } = this.documentSettings const step = this.isGridMode ? major - ? this.gridSize * GRID_INCREMENT - : this.gridSize + ? gridSize * GRID_INCREMENT + : gridSize : major ? MAJOR_NUDGE_FACTOR : MINOR_NUDGE_FACTOR @@ -5912,7 +5619,7 @@ export class Editor extends EventEmitter { // Put the shape content onto the new page; parents and indices will // be taken care of by the putContent method; make sure to pop any focus // layers so that the content will be put onto the page. - this.setFocusLayer(null) + this.focusLayerId = this.currentPageId this.selectNone() this.putContent(content, { select: true, preserveIds: true, preservePosition: true }) @@ -7045,7 +6752,7 @@ export class Editor extends EventEmitter { // We then look up each key in the tab state's styles; and if it's there, // we use the value from the tab state's styles instead of the default. - for (const [style, propKey] of util.styleProps) { + for (const [style, propKey] of this.styleProps[partial.type]) { ;(initialProps as any)[propKey] = this.getStyleForNextShape(style) } @@ -7079,7 +6786,10 @@ export class Editor extends EventEmitter { // Add meta properties, if any, to the shapes shapeRecordsToCreate.forEach((shape) => { - shape.meta = this.getInitialMetaForShape(shape) + shape.meta = { + ...this.getInitialMetaForShape(shape), + ...shape.meta, + } }) this.store.put(shapeRecordsToCreate) @@ -7562,8 +7272,7 @@ export class Editor extends EventEmitter { this._extractSharedStyles(this.getShapeById(childIds[i][0])!, sharedStyleMap) } } else { - const util = this.getShapeUtil(shape) - for (const [style, propKey] of util.styleProps) { + for (const [style, propKey] of this.styleProps[shape.type]) { sharedStyleMap.applyValue(style, getOwnProperty(shape.props, propKey)) } } @@ -7599,8 +7308,7 @@ export class Editor extends EventEmitter { } getShapeStyleIfExists(shape: TLShape, style: StyleProp): T | undefined { - const util = this.getShapeUtil(shape) - const styleKey = util.styleProps.get(style) + const styleKey = this.styleProps[shape.type].get(style) if (styleKey === undefined) return undefined return getOwnProperty(shape.props, styleKey) as T | undefined } @@ -7632,7 +7340,7 @@ export class Editor extends EventEmitter { const currentTool = this.root.current.value! const styles = new SharedStyleMap() if (currentTool.shapeType) { - for (const style of this.getShapeUtil(currentTool.shapeType).styleProps.keys()) { + for (const style of this.styleProps[currentTool.shapeType].keys()) { styles.applyValue(style, this.getStyleForNextShape(style)) } } @@ -7789,7 +7497,7 @@ export class Editor extends EventEmitter { } } else { const util = this.getShapeUtil(shape) - const stylePropKey = util.styleProps.get(style) + const stylePropKey = this.styleProps[shape.type].get(style) if (stylePropKey) { const shapePartial: TLShapePartial = { id: shape.id, @@ -7830,8 +7538,107 @@ export class Editor extends EventEmitter { /* --------------------- Content -------------------- */ - /** @public */ - externalContentManager = new ExternalContentManager(this) + /** @internal */ + externalAssetContentHandlers: { + [K in TLExternalAssetContent['type']]: { + [Key in K]: + | null + | ((info: TLExternalAssetContent & { type: Key }) => Promise) + }[K] + } = { + file: null, + url: null, + } + + /** + * Register an external content handler. This handler will be called when the editor receives + * external content of the provided type. For example, the 'image' type handler will be called + * when a user drops an image onto the canvas. + * + * @example + * ```ts + * editor.registerExternalAssetHandler('text', myHandler) + * ``` + * + * @param type - The type of external content. + * @param handler - The handler to use for this content type. + * + * @public + */ + registerExternalAssetHandler( + type: T, + handler: null | ((info: TLExternalAssetContent & { type: T }) => Promise) + ): this { + this.externalAssetContentHandlers[type] = handler as any + return this + } + + /** + * Get an asset for an external asset content type. + * + * @example + * ```ts + * const asset = await editor.getAssetForExternalContent({ type: 'file', file: myFile }) + * const asset = await editor.getAssetForExternalContent({ type: 'url', url: myUrl }) + * ``` + * + * @param info - Info about the external content. + * @returns The asset. + */ + async getAssetForExternalContent(info: TLExternalAssetContent): Promise { + return await this.externalAssetContentHandlers[info.type]?.(info as any) + } + + /** @internal */ + externalContentHandlers: { + [K in TLExternalContent['type']]: { + [Key in K]: null | ((info: TLExternalContent & { type: Key }) => void) + }[K] + } = { + text: null, + files: null, + embed: null, + 'svg-text': null, + url: null, + } + + /** + * Register an external content handler. This handler will be called when the editor receives + * external content of the provided type. For example, the 'image' type handler will be called + * when a user drops an image onto the canvas. + * + * @example + * ```ts + * editor.registerExternalContentHandler('text', myHandler) + * ``` + * + * @param type - The type of external content. + * @param handler - The handler to use for this content type. + * + * @public + */ + registerExternalContentHandler( + type: T, + handler: + | null + | (( + info: T extends TLExternalContent['type'] + ? TLExternalContent & { type: T } + : TLExternalContent + ) => void) + ): this { + this.externalContentHandlers[type] = handler as any + return this + } + + /** + * Handle external content, such as files, urls, embeds, or plain text which has been put into the app, for example by pasting external text or dropping external images onto canvas. + * + * @param info - Info about the external content. + */ + async putExternalContent(info: TLExternalContent): Promise { + return this.externalContentHandlers[info.type]?.(info as any) + } /** * Get content that can be exported for the given shape ids. @@ -7995,7 +7802,7 @@ export class Editor extends EventEmitter { if (this.isReadOnly) return this if (!content.schema) { - throw Error('Could not put content: content is missing a schema.') + throw Error('Could not put content:\ncontent is missing a schema.') } const { select = false, preserveIds = false, preservePosition = false } = options @@ -8155,11 +7962,7 @@ export class Editor extends EventEmitter { assets[i] = result.value as TLAsset } else { throw Error( - `Could not put content: could not migrate content for asset:\n${JSON.stringify( - asset, - null, - 2 - )}` + `Could not put content:\ncould not migrate content for asset:\n${asset.id}\n${asset.type}\nreason:${result.reason}` ) } } @@ -8189,7 +7992,11 @@ export class Editor extends EventEmitter { asset.props.mimeType ?? 'image/png' ) - const newAsset = await this.externalContentManager.createAssetFromFile(this, file) + const newAsset = await this.getAssetForExternalContent({ type: 'file', file }) + + if (!newAsset) { + return null + } return [asset, newAsset] as const }) @@ -8197,7 +8004,7 @@ export class Editor extends EventEmitter { this.updateAssets( compact( assets.map((result) => - result.status === 'fulfilled' + result.status === 'fulfilled' && result.value ? { ...result.value[1], id: result.value[0].id } : undefined ) @@ -8213,11 +8020,7 @@ export class Editor extends EventEmitter { newShapes[i] = result.value as TLShape } else { throw Error( - `Could not put content: could not migrate content for shape:\n${JSON.stringify( - shape, - null, - 2 - )}` + `Could not put content:\ncould not migrate content for shape:\n${shape.id}, ${shape.type}\nreason:${result.reason}` ) } } @@ -8293,38 +8096,6 @@ export class Editor extends EventEmitter { return this } - /** - * Replace the store's contents with the given records. - * - * @param records - The records to replace the store's contents with. - */ - replaceStoreContentsWithRecordsForOtherDocument(records: TLRecord[]) { - transact(() => { - this.store.clear() - const [shapes, nonShapes] = partition(records, (record) => record.typeName === 'shape') - this.store.put(nonShapes, 'initialize') - this.store.ensureStoreIsUsable() - this.store.put(shapes, 'initialize') - this.history.clear() - this.updateViewportScreenBounds() - this.updateRenderingBounds() - - const bounds = this.allShapesCommonBounds - if (bounds) { - this.zoomToBounds(bounds.minX, bounds.minY, bounds.width, bounds.height, 1) - } - }) - } - - /** - * Handle external content, such as files, urls, embeds, or plain text which has been put into the app, for example by pasting external text or dropping external images onto canvas. - * - * @param info - Info about the external content. - */ - async putExternalContent(info: TLExternalContent): Promise { - this.externalContentManager.handleContent(info) - } - /** * Get an exported SVG of the given shapes. * @@ -8506,7 +8277,7 @@ export class Editor extends EventEmitter { // Create a clip path and add it to defs const clipPathEl = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath') defs.appendChild(clipPathEl) - const id = nanoid() + const id = uniqueId() clipPathEl.id = id // Create a polyline mask that does the clipping @@ -8603,7 +8374,7 @@ export class Editor extends EventEmitter { * * @param info - The event info. */ - private _updateInputsFromEvent(info: TLPointerEventInfo | TLPinchEventInfo) { + private _updateInputsFromEvent(info: TLPointerEventInfo | TLPinchEventInfo): void { const { previousScreenPoint, previousPagePoint, currentScreenPoint, currentPagePoint } = this.inputs @@ -8656,7 +8427,7 @@ export class Editor extends EventEmitter { * * @public */ - cancel() { + cancel(): this { this.dispatch({ type: 'misc', name: 'cancel' }) return this } @@ -8671,7 +8442,7 @@ export class Editor extends EventEmitter { * * @public */ - interrupt() { + interrupt(): this { this.dispatch({ type: 'misc', name: 'interrupt' }) return this } @@ -8686,7 +8457,7 @@ export class Editor extends EventEmitter { * * @public */ - complete() { + complete(): this { this.dispatch({ type: 'misc', name: 'complete' }) return this } @@ -8808,9 +8579,11 @@ export class Editor extends EventEmitter { if (this.inputs.isPanning) { this.inputs.isPanning = false - this.setCursor({ + this.cursor = { + ...this.cursor, type: this._prevCursor, - }) + rotation: 0, + } } } @@ -9013,7 +8786,7 @@ export class Editor extends EventEmitter { } } else { if (isPen) { - this.setPenMode(true) + this.isPenMode = true } } @@ -9021,7 +8794,7 @@ export class Editor extends EventEmitter { // Eraser button activates eraser this._restoreToolId = this.currentToolId this.complete() - this.setSelectedTool('eraser') + this.setCurrentTool('eraser') } else if (info.button === 1) { // Middle mouse pan activates panning if (!this.inputs.isPanning) { @@ -9033,9 +8806,11 @@ export class Editor extends EventEmitter { if (this.inputs.isPanning) { this.stopCameraAnimation() - this.setCursor({ + this.cursor = { + ...this.cursor, type: 'grabbing', - }) + rotation: 0, + } return this } @@ -9099,36 +8874,42 @@ export class Editor extends EventEmitter { this.slideCamera({ speed: Math.min(2, this.inputs.pointerVelocity.len()), direction: this.inputs.pointerVelocity, - friction: HAND_TOOL_FRICTION, + friction: CAMERA_SLIDE_FRICTION, }) - this.setCursor({ + this.cursor = { + ...this.cursor, type: this._prevCursor, - }) + rotation: 0, + } } else { this.slideCamera({ speed: Math.min(2, this.inputs.pointerVelocity.len()), direction: this.inputs.pointerVelocity, - friction: HAND_TOOL_FRICTION, + friction: CAMERA_SLIDE_FRICTION, }) - this.setCursor({ + this.cursor = { + ...this.cursor, type: 'grab', - }) + rotation: 0, + } } } else if (info.button === 0) { this.slideCamera({ speed: Math.min(2, this.inputs.pointerVelocity.len()), direction: this.inputs.pointerVelocity, - friction: HAND_TOOL_FRICTION, + friction: CAMERA_SLIDE_FRICTION, }) - this.setCursor({ + this.cursor = { + ...this.cursor, type: 'grab', - }) + rotation: 0, + } } } else { if (info.button === 5) { // Eraser button activates eraser this.complete() - this.setSelectedTool(this._restoreToolId) + this.setCurrentTool(this._restoreToolId) } } @@ -9156,9 +8937,11 @@ export class Editor extends EventEmitter { } this.inputs.isPanning = true - this.setCursor({ + this.cursor = { + ...this.cursor, type: this.inputs.isPointing ? 'grabbing' : 'grab', - }) + rotation: 0, + } } break @@ -9169,9 +8952,11 @@ export class Editor extends EventEmitter { if (info.code === 'Space' && !this.inputs.buttons.has(1)) { this.inputs.isPanning = false - this.setCursor({ + this.cursor = { + ...this.cursor, type: this._prevCursor, - }) + rotation: 0, + } } break @@ -9242,3 +9027,36 @@ function alertMaxShapes(editor: Editor, pageId = editor.currentPageId) { const name = editor.getPageById(pageId)!.name editor.emit('max-shapes', { name, pageId, count: MAX_SHAPES_PER_PAGE }) } + +/** @public */ +export type TLExternalContent = + | { + type: 'text' + point?: VecLike + text: string + } + | { + type: 'files' + files: File[] + point?: VecLike + ignoreParent: boolean + } + | { + type: 'url' + url: string + point?: VecLike + } + | { + type: 'svg-text' + text: string + point?: VecLike + } + | { + type: 'embed' + url: string + point?: VecLike + embed: EmbedDefinition + } + +/** @public */ +export type TLExternalAssetContent = { type: 'file'; file: File } | { type: 'url'; url: string } diff --git a/packages/editor/src/lib/editor/managers/ClickManager.ts b/packages/editor/src/lib/editor/managers/ClickManager.ts index 59705a633..d19384766 100644 --- a/packages/editor/src/lib/editor/managers/ClickManager.ts +++ b/packages/editor/src/lib/editor/managers/ClickManager.ts @@ -1,11 +1,11 @@ -import { Vec2d } from '@tldraw/primitives' import { COARSE_DRAG_DISTANCE, DOUBLE_CLICK_DURATION, DRAG_DISTANCE, MULTI_CLICK_DURATION, } from '../../constants' -import { uniqueId } from '../../utils/data' +import { Vec2d } from '../../primitives/Vec2d' +import { uniqueId } from '../../utils/uniqueId' import type { Editor } from '../Editor' import { TLClickEventInfo, TLPointerEventInfo } from '../types/event-types' diff --git a/packages/editor/src/lib/editor/managers/ExternalContentManager.ts b/packages/editor/src/lib/editor/managers/ExternalContentManager.ts deleted file mode 100644 index 85baea4c3..000000000 --- a/packages/editor/src/lib/editor/managers/ExternalContentManager.ts +++ /dev/null @@ -1,599 +0,0 @@ -import { Vec2d, VecLike } from '@tldraw/primitives' -import { - AssetRecordType, - EmbedDefinition, - TLAsset, - TLAssetId, - TLEmbedShape, - TLShapePartial, - TLTextShape, - TLTextShapeProps, - createShapeId, -} from '@tldraw/tlschema' -import { compact, getHashForString } from '@tldraw/utils' -import { MAX_ASSET_HEIGHT, MAX_ASSET_WIDTH } from '../../constants' -import { - ACCEPTED_IMG_TYPE, - ACCEPTED_VID_TYPE, - containBoxSize, - getFileMetaData, - getImageSizeFromSrc, - getResizedImageDataUrl, - getVideoSizeFromSrc, - isImage, -} from '../../utils/assets' -import { truncateStringWithEllipsis } from '../../utils/dom' -import { getEmbedInfo } from '../../utils/embeds' -import { Editor } from '../Editor' -import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../shapes/shared/default-shape-constants' -import { INDENT } from '../shapes/text/TextHelpers' - -/** @public */ -export type TLExternalContent = - | { - type: 'text' - point?: VecLike - text: string - } - | { - type: 'files' - files: File[] - point?: VecLike - ignoreParent: boolean - } - | { - type: 'url' - url: string - point?: VecLike - } - | { - type: 'svg-text' - text: string - point?: VecLike - } - | { - type: 'embed' - url: string - point?: VecLike - embed: EmbedDefinition - } - -/** @public */ -export class ExternalContentManager { - constructor(public editor: Editor) {} - - handleContent = async (info: TLExternalContent) => { - switch (info.type) { - case 'text': { - return await this.handleText(this.editor, info) - } - case 'files': { - return await this.handleFiles(this.editor, info) - } - case 'embed': { - return await this.handleEmbed(this.editor, info) - } - case 'svg-text': { - return await this.handleSvgText(this.editor, info) - } - case 'url': { - return await this.handleUrl(this.editor, info) - } - } - } - - /** - * Handle svg text from an external source. Feeling lucky? Overwrite this at runtime to change the way this type of external content is handled. - * - * @example - * ```ts - * editor.this.handleSvgText = myCustomMethod - * ``` - * - * @param editor - The editor instance. - * @param info - The info object describing the external content. - * - * @public - */ - async handleSvgText( - editor: Editor, - { point, text }: Extract - ) { - const position = - point ?? (editor.inputs.shiftKey ? editor.inputs.currentPagePoint : editor.viewportPageCenter) - - const svg = new DOMParser().parseFromString(text, 'image/svg+xml').querySelector('svg') - if (!svg) { - throw new Error('No element present') - } - - let width = parseFloat(svg.getAttribute('width') || '0') - let height = parseFloat(svg.getAttribute('height') || '0') - - if (!(width && height)) { - document.body.appendChild(svg) - const box = svg.getBoundingClientRect() - document.body.removeChild(svg) - - width = box.width - height = box.height - } - - const asset = await this.createAssetFromFile( - editor, - new File([text], 'asset.svg', { type: 'image/svg+xml' }) - ) - - this.createShapesForAssets(editor, [asset], position) - } - - /** - * Handle embed info from an external source. Feeling lucky? Overwrite this at runtime to change the way this type of external content is handled. - * - * @example - * ```ts - * editor.this.handleEmbed = myCustomMethod - * ``` - * - * @param editor - The editor instance - * @param info - The info object describing the external content. - * - * @public - */ - async handleEmbed( - editor: Editor, - { point, url, embed }: Extract - ) { - const position = - point ?? (editor.inputs.shiftKey ? editor.inputs.currentPagePoint : editor.viewportPageCenter) - - const { width, height } = embed - - const shapePartial: TLShapePartial = { - id: createShapeId(), - type: 'embed', - x: position.x - (width || 450) / 2, - y: position.y - (height || 450) / 2, - props: { - w: width, - h: height, - url, - }, - } - - editor.createShapes([shapePartial], true) - } - - /** - * Handle files from an external source. Feeling lucky? Overwrite this at runtime to change the way this type of external content is handled. - * - * @example - * ```ts - * editor.this.handleFiles = myCustomMethod - * ``` - * - * @param editor - The editor instance - * @param info - The info object describing the external content. - * - * @public - */ - async handleFiles( - editor: Editor, - { point, files }: Extract - ) { - const position = - point ?? (editor.inputs.shiftKey ? editor.inputs.currentPagePoint : editor.viewportPageCenter) - - const pagePoint = new Vec2d(position.x, position.y) - - const assets: TLAsset[] = [] - - await Promise.all( - files.map(async (file, i) => { - // Use mime type instead of file ext, this is because - // window.navigator.clipboard does not preserve file names - // of copied files. - if (!file.type) throw new Error('No mime type') - - // We can only accept certain extensions (either images or a videos) - if (!ACCEPTED_IMG_TYPE.concat(ACCEPTED_VID_TYPE).includes(file.type)) { - console.warn(`${file.name} not loaded - Extension not allowed.`) - return null - } - - try { - const asset = await this.createAssetFromFile(editor, file) - - if (!asset) throw Error('Could not create an asset') - - assets[i] = asset - } catch (error) { - console.error(error) - return null - } - }) - ) - - this.createShapesForAssets(editor, compact(assets), pagePoint) - } - - /** - * Handle plain text from an external source. Feeling lucky? Overwrite this at runtime to change the way this type of external content is handled. - * - * @example - * ```ts - * editor.this.handleText = myCustomMethod - * ``` - * - * @param editor - The editor instance - * @param info - The info object describing the external content. - * - * @public - */ - async handleText(editor: Editor, { point, text }: Extract) { - const p = - point ?? (editor.inputs.shiftKey ? editor.inputs.currentPagePoint : editor.viewportPageCenter) - - const defaultProps = editor.getShapeUtil('text').getDefaultProps() - - const textToPaste = stripTrailingWhitespace( - stripCommonMinimumIndentation(replaceTabsWithSpaces(text)) - ) - - // Measure the text with default values - let w: number - let h: number - let autoSize: boolean - let align = 'middle' as TLTextShapeProps['align'] - - const isMultiLine = textToPaste.split('\n').length > 1 - - // check whether the text contains the most common characters in RTL languages - const isRtl = rtlRegex.test(textToPaste) - - if (isMultiLine) { - align = isMultiLine ? (isRtl ? 'end' : 'start') : 'middle' - } - - const rawSize = editor.textMeasure.measureText(textToPaste, { - ...TEXT_PROPS, - fontFamily: FONT_FAMILIES[defaultProps.font], - fontSize: FONT_SIZES[defaultProps.size], - width: 'fit-content', - }) - - const minWidth = Math.min( - isMultiLine ? editor.viewportPageBounds.width * 0.9 : 920, - Math.max(200, editor.viewportPageBounds.width * 0.9) - ) - - if (rawSize.w > minWidth) { - const shrunkSize = editor.textMeasure.measureText(textToPaste, { - ...TEXT_PROPS, - fontFamily: FONT_FAMILIES[defaultProps.font], - fontSize: FONT_SIZES[defaultProps.size], - width: minWidth + 'px', - }) - w = shrunkSize.w - h = shrunkSize.h - autoSize = false - align = isRtl ? 'end' : 'start' - } else { - // autosize is fine - w = rawSize.w - h = rawSize.h - autoSize = true - } - - if (p.y - h / 2 < editor.viewportPageBounds.minY + 40) { - p.y = editor.viewportPageBounds.minY + 40 + h / 2 - } - - editor.createShapes([ - { - id: createShapeId(), - type: 'text', - x: p.x - w / 2, - y: p.y - h / 2, - props: { - text: textToPaste, - // if the text has more than one line, align it to the left - align, - autoSize, - w, - }, - }, - ]) - } - - /** - * Handle urls from an external source. Feeling lucky? Overwrite this at runtime to change the way this type of external content is handled. - * - * @example - * ```ts - * editor.this.handleUrl = myCustomMethod - * ``` - * - * @param editor - The editor instance - * @param info - The info object describing the external content. - * - * @public - */ - handleUrl = async ( - editor: Editor, - { point, url }: Extract - ) => { - // try to paste as an embed first - const embedInfo = getEmbedInfo(url) - - if (embedInfo) { - return this.handleEmbed(editor, { - type: 'embed', - url: embedInfo.url, - point, - embed: embedInfo.definition, - }) - } - - const position = - point ?? (editor.inputs.shiftKey ? editor.inputs.currentPagePoint : editor.viewportPageCenter) - - const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url)) - - // Use an existing asset if we have one, or else else create a new one - let asset = editor.getAssetById(assetId) as TLAsset - let shouldAlsoCreateAsset = false - if (!asset) { - shouldAlsoCreateAsset = true - asset = await this.createAssetFromUrl(editor, url) - } - - editor.batch(() => { - if (shouldAlsoCreateAsset) { - editor.createAssets([asset]) - } - - this.createShapesForAssets(editor, [asset], position) - }) - } - - async createShapesForAssets(editor: Editor, assets: TLAsset[], position: VecLike) { - if (!assets.length) return - - const currentPoint = Vec2d.From(position) - const paritals: TLShapePartial[] = [] - - for (const asset of assets) { - switch (asset.type) { - case 'bookmark': { - paritals.push({ - id: createShapeId(), - type: 'bookmark', - x: currentPoint.x - 150, - y: currentPoint.y - 160, - opacity: 1, - props: { - assetId: asset.id, - url: asset.props.src, - }, - }) - - currentPoint.x += 300 - break - } - case 'image': { - paritals.push({ - id: createShapeId(), - type: 'image', - x: currentPoint.x - asset.props.w / 2, - y: currentPoint.y - asset.props.h / 2, - opacity: 1, - props: { - assetId: asset.id, - w: asset.props.w, - h: asset.props.h, - }, - }) - - currentPoint.x += asset.props.w - break - } - case 'video': { - paritals.push({ - id: createShapeId(), - type: 'video', - x: currentPoint.x - asset.props.w / 2, - y: currentPoint.y - asset.props.h / 2, - opacity: 1, - props: { - assetId: asset.id, - w: asset.props.w, - h: asset.props.h, - }, - }) - - currentPoint.x += asset.props.w - } - } - } - - editor.batch(() => { - // Create any assets - const assetsToCreate = assets.filter((asset) => !editor.getAssetById(asset.id)) - if (assetsToCreate.length) { - editor.createAssets(assetsToCreate) - } - - // Create the shapes - editor.createShapes(paritals, true) - - // Re-position shapes so that the center of the group is at the provided point - const { viewportPageBounds } = editor - let { selectedPageBounds } = editor - - if (selectedPageBounds) { - const offset = selectedPageBounds!.center.sub(position) - - editor.updateShapes( - paritals.map((partial) => { - return { - id: partial.id, - type: partial.type, - x: partial.x! - offset.x, - y: partial.y! - offset.y, - } - }) - ) - } - - // Zoom out to fit the shapes, if necessary - selectedPageBounds = editor.selectedPageBounds - if (selectedPageBounds && !viewportPageBounds.contains(selectedPageBounds)) { - editor.zoomToSelection() - } - }) - } - - /** - * Override this method to change how assets are created from files. - * - * @param editor - The editor instance - * @param file - The file to create the asset from. - */ - async createAssetFromFile(_editor: Editor, file: File): Promise { - return await new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onerror = () => reject(reader.error) - reader.onload = async () => { - let dataUrl = reader.result as string - - const isImageType = isImage(file.type) - const sizeFn = isImageType ? getImageSizeFromSrc : getVideoSizeFromSrc - - // Hack to make .mov videos work via dataURL. - if (file.type === 'video/quicktime' && dataUrl.includes('video/quicktime')) { - dataUrl = dataUrl.replace('video/quicktime', 'video/mp4') - } - - const originalSize = await sizeFn(dataUrl) - const size = containBoxSize(originalSize, { w: MAX_ASSET_WIDTH, h: MAX_ASSET_HEIGHT }) - - if (size !== originalSize && (file.type === 'image/jpeg' || file.type === 'image/png')) { - // If we created a new size and the type is an image, rescale the image - dataUrl = await getResizedImageDataUrl(dataUrl, size.w, size.h) - } - - const assetId: TLAssetId = AssetRecordType.createId(getHashForString(dataUrl)) - - const metadata = await getFileMetaData(file) - - const asset: Extract = { - id: assetId, - type: isImageType ? 'image' : 'video', - typeName: 'asset', - props: { - name: file.name, - src: dataUrl, - w: size.w, - h: size.h, - mimeType: file.type, - isAnimated: metadata.isAnimated, - }, - meta: {}, - } - - resolve(asset) - } - - reader.readAsDataURL(file) - }) - } - - /** - * Override me to change the way assets are created from urls. - * - * @param editor - The editor instance - * @param url - The url to create the asset from - */ - async createAssetFromUrl(_editor: Editor, url: string): Promise { - let meta: { image: string; title: string; description: string } - - try { - const resp = await fetch(url, { method: 'GET', mode: 'no-cors' }) - const html = await resp.text() - const doc = new DOMParser().parseFromString(html, 'text/html') - meta = { - image: doc.head.querySelector('meta[property="og:image"]')?.getAttribute('content') ?? '', - title: - doc.head.querySelector('meta[property="og:title"]')?.getAttribute('content') ?? - truncateStringWithEllipsis(url, 32), - description: - doc.head.querySelector('meta[property="og:description"]')?.getAttribute('content') ?? '', - } - } catch (error) { - console.error(error) - meta = { image: '', title: truncateStringWithEllipsis(url, 32), description: '' } - } - - // Create the bookmark asset from the meta - return { - id: AssetRecordType.createId(getHashForString(url)), - typeName: 'asset', - type: 'bookmark', - props: { - src: url, - description: meta.description, - image: meta.image, - title: meta.title, - }, - meta: {}, - } - } -} - -/* --------------------- Helpers -------------------- */ - -const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/ - -/** - * Replace any tabs with double spaces. - * @param text - The text to replace tabs in. - * @internal - */ -function replaceTabsWithSpaces(text: string) { - return text.replace(/\t/g, INDENT) -} - -/** - * Strip common minimum indentation from each line. - * @param text - The text to strip. - * @internal - */ -function stripCommonMinimumIndentation(text: string): string { - // Split the text into individual lines - const lines = text.split('\n') - - // remove any leading lines that are only whitespace or newlines - while (lines[0].trim().length === 0) { - lines.shift() - } - - let minIndentation = Infinity - for (const line of lines) { - if (line.trim().length > 0) { - const indentation = line.length - line.trimStart().length - minIndentation = Math.min(minIndentation, indentation) - } - } - - return lines.map((line) => line.slice(minIndentation)).join('\n') -} - -/** - * Strip trailing whitespace from each line and remove any trailing newlines. - * @param text - The text to strip. - * @internal - */ -function stripTrailingWhitespace(text: string): string { - return text.replace(/[ \t]+$/gm, '').replace(/\n+$/, '') -} diff --git a/packages/editor/src/lib/editor/managers/HistoryManager.ts b/packages/editor/src/lib/editor/managers/HistoryManager.ts index 6e970e160..45d906c63 100644 --- a/packages/editor/src/lib/editor/managers/HistoryManager.ts +++ b/packages/editor/src/lib/editor/managers/HistoryManager.ts @@ -1,6 +1,6 @@ import { atom, transact } from '@tldraw/state' import { devFreeze } from '@tldraw/store' -import { uniqueId } from '../../utils/data' +import { uniqueId } from '../../utils/uniqueId' import { TLCommandHandler, TLHistoryEntry } from '../types/history-types' import { Stack, stack } from './Stack' diff --git a/packages/editor/src/lib/editor/managers/SnapManager.ts b/packages/editor/src/lib/editor/managers/SnapManager.ts index 8ad7890d4..8ffe03fdd 100644 --- a/packages/editor/src/lib/editor/managers/SnapManager.ts +++ b/packages/editor/src/lib/editor/managers/SnapManager.ts @@ -1,27 +1,28 @@ +import { atom, computed, EMPTY_ARRAY } from '@tldraw/state' +import { TLGroupShape, TLParentId, TLShape, TLShapeId, Vec2dModel } from '@tldraw/tlschema' +import { dedupe, deepCopy } from '@tldraw/utils' import { Box2d, flipSelectionHandleX, flipSelectionHandleY, isSelectionCorner, - Matrix2d, - rangeIntersection, - rangesOverlap, SelectionCorner, SelectionEdge, - Vec2d, - VecLike, -} from '@tldraw/primitives' -import { atom, computed, EMPTY_ARRAY } from '@tldraw/state' -import { TLGroupShape, TLParentId, TLShape, TLShapeId, Vec2dModel } from '@tldraw/tlschema' -import { dedupe, deepCopy } from '@tldraw/utils' -import { uniqueId } from '../../utils/data' +} from '../../primitives/Box2d' +import { Matrix2d } from '../../primitives/Matrix2d' +import { rangeIntersection, rangesOverlap } from '../../primitives/utils' +import { Vec2d, VecLike } from '../../primitives/Vec2d' +import { uniqueId } from '../../utils/uniqueId' import type { Editor } from '../Editor' +/** @public */ export type PointsSnapLine = { id: string type: 'points' points: VecLike[] } + +/** @public */ export type GapsSnapLine = { id: string type: 'gaps' @@ -31,6 +32,8 @@ export type GapsSnapLine = { endEdge: [VecLike, VecLike] }> } + +/** @public */ export type SnapLine = PointsSnapLine | GapsSnapLine export type SnapInteractionType = @@ -43,6 +46,7 @@ export type SnapInteractionType = type: 'resize' } +/** @public */ export interface SnapPoint { id: string x: number @@ -208,6 +212,7 @@ function dedupeGapSnaps(snaps: Array>) { } } +/** @public */ export class SnapManager { private _snapLines = atom('snapLines', undefined) diff --git a/packages/editor/src/lib/editor/managers/TextManager.ts b/packages/editor/src/lib/editor/managers/TextManager.ts index 7998e1a39..baae2209a 100644 --- a/packages/editor/src/lib/editor/managers/TextManager.ts +++ b/packages/editor/src/lib/editor/managers/TextManager.ts @@ -1,7 +1,16 @@ import { Box2dModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema' -import { uniqueId } from '../../utils/data' +import { uniqueId } from '../../utils/uniqueId' import { Editor } from '../Editor' -import { TextHelpers } from '../shapes/text/TextHelpers' + +const fixNewLines = /\r?\n|\r/g + +function normalizeTextForDom(text: string) { + return text + .replace(fixNewLines, '\n') + .split('\n') + .map((x) => x || ' ') + .join('\n') +} const textAlignmentsForLtr = { start: 'left', @@ -73,7 +82,7 @@ export class TextManager { elm.style.setProperty('max-width', opts.maxWidth) elm.style.setProperty('padding', opts.padding) - elm.textContent = TextHelpers.normalizeTextForDom(textToMeasure) + elm.textContent = normalizeTextForDom(textToMeasure) const rect = elm.getBoundingClientRect() diff --git a/packages/editor/src/lib/editor/managers/TickManager.ts b/packages/editor/src/lib/editor/managers/TickManager.ts index e755339d1..4943eaf92 100644 --- a/packages/editor/src/lib/editor/managers/TickManager.ts +++ b/packages/editor/src/lib/editor/managers/TickManager.ts @@ -1,4 +1,4 @@ -import { Vec2d } from '@tldraw/primitives' +import { Vec2d } from '../../primitives/Vec2d' import { Editor } from '../Editor' export class TickManager { diff --git a/packages/editor/src/lib/editor/shapes/BaseBoxShapeUtil.tsx b/packages/editor/src/lib/editor/shapes/BaseBoxShapeUtil.tsx index d1ab29062..6daadc4d3 100644 --- a/packages/editor/src/lib/editor/shapes/BaseBoxShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapes/BaseBoxShapeUtil.tsx @@ -1,5 +1,8 @@ -import { Box2d, linesIntersect, pointInPolygon, Vec2d, VecLike } from '@tldraw/primitives' import { TLBaseShape } from '@tldraw/tlschema' +import { Box2d } from '../../primitives/Box2d' +import { Vec2d, VecLike } from '../../primitives/Vec2d' +import { linesIntersect } from '../../primitives/intersect' +import { pointInPolygon } from '../../primitives/utils' import { ShapeUtil, TLOnResizeHandler } from './ShapeUtil' import { resizeBox } from './shared/resizeBox' diff --git a/packages/editor/src/lib/editor/shapes/ShapeUtil.ts b/packages/editor/src/lib/editor/shapes/ShapeUtil.ts index 14c142876..36170a988 100644 --- a/packages/editor/src/lib/editor/shapes/ShapeUtil.ts +++ b/packages/editor/src/lib/editor/shapes/ShapeUtil.ts @@ -1,17 +1,22 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { Box2d, linesIntersect, Vec2d, VecLike } from '@tldraw/primitives' -import { StyleProp, TLHandle, TLShape, TLShapePartial, TLUnknownShape } from '@tldraw/tlschema' +import { Migrations } from '@tldraw/store' +import { ShapeProps, TLHandle, TLShape, TLShapePartial, TLUnknownShape } from '@tldraw/tlschema' +import { Box2d } from '../../primitives/Box2d' +import { Vec2d, VecLike } from '../../primitives/Vec2d' +import { linesIntersect } from '../../primitives/intersect' import type { Editor } from '../Editor' +import { SvgExportContext } from '../types/SvgExportContext' import { TLResizeHandle } from '../types/selection-types' -import { SvgExportContext } from './shared/SvgExportContext' /** @public */ export interface TLShapeUtilConstructor< T extends TLUnknownShape, U extends ShapeUtil = ShapeUtil > { - new (editor: Editor, type: T['type'], styleProps: ReadonlyMap, string>): U + new (editor: Editor): U type: T['type'] + props?: ShapeProps + migrations?: Migrations } /** @public */ @@ -25,27 +30,9 @@ export interface TLShapeUtilCanvasSvgDef { /** @public */ export abstract class ShapeUtil { - constructor( - public editor: Editor, - public readonly type: Shape['type'], - public readonly styleProps: ReadonlyMap, string> - ) {} - - setStyleInPartial( - style: StyleProp, - shape: TLShapePartial, - value: T - ): TLShapePartial { - const styleKey = this.styleProps.get(style) - if (!styleKey) return shape - return { - ...shape, - props: { - ...shape.props, - [styleKey]: value, - }, - } - } + constructor(public editor: Editor) {} + static props?: ShapeProps + static migrations?: Migrations /** * The type of the shape util, which should match the shape's type. diff --git a/packages/editor/src/lib/editor/shapes/arrow/ArrowShape.ts b/packages/editor/src/lib/editor/shapes/arrow/ArrowShape.ts deleted file mode 100644 index 82c349a98..000000000 --- a/packages/editor/src/lib/editor/shapes/arrow/ArrowShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { arrowShapeMigrations, arrowShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { ArrowShapeUtil } from './ArrowShapeUtil' - -/** @public */ -export const ArrowShape = defineShape('arrow', { - util: ArrowShapeUtil, - props: arrowShapeProps, - migrations: arrowShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/arrow/toolStates/Idle.ts b/packages/editor/src/lib/editor/shapes/arrow/toolStates/Idle.ts deleted file mode 100644 index efe5c4a68..000000000 --- a/packages/editor/src/lib/editor/shapes/arrow/toolStates/Idle.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' - -export class Idle extends StateNode { - static override id = 'idle' - - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { - this.parent.transition('pointing', info) - } - - onEnter = () => { - this.editor.setCursor({ type: 'cross' }) - } - - onCancel = () => { - this.editor.setSelectedTool('select') - } -} diff --git a/packages/editor/src/lib/editor/shapes/bookmark/BookmarkShape.ts b/packages/editor/src/lib/editor/shapes/bookmark/BookmarkShape.ts deleted file mode 100644 index 1bceb3156..000000000 --- a/packages/editor/src/lib/editor/shapes/bookmark/BookmarkShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { bookmarkShapeMigrations, bookmarkShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { BookmarkShapeUtil } from './BookmarkShapeUtil' - -/** @public */ -export const BookmarkShape = defineShape('bookmark', { - util: BookmarkShapeUtil, - props: bookmarkShapeProps, - migrations: bookmarkShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/draw/DrawShape.ts b/packages/editor/src/lib/editor/shapes/draw/DrawShape.ts deleted file mode 100644 index 939c3c0c0..000000000 --- a/packages/editor/src/lib/editor/shapes/draw/DrawShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { drawShapeMigrations, drawShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { DrawShapeUtil } from './DrawShapeUtil' - -/** @public */ -export const DrawShape = defineShape('draw', { - util: DrawShapeUtil, - props: drawShapeProps, - migrations: drawShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/draw/toolStates/Idle.ts b/packages/editor/src/lib/editor/shapes/draw/toolStates/Idle.ts deleted file mode 100644 index 84d1000a9..000000000 --- a/packages/editor/src/lib/editor/shapes/draw/toolStates/Idle.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' - -export class Idle extends StateNode { - static override id = 'idle' - - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { - this.parent.transition('drawing', info) - } - - onEnter = () => { - this.editor.setCursor({ type: 'cross' }) - } - - onCancel = () => { - this.editor.setSelectedTool('select') - } -} diff --git a/packages/editor/src/lib/editor/shapes/embed/EmbedShape.ts b/packages/editor/src/lib/editor/shapes/embed/EmbedShape.ts deleted file mode 100644 index e422b05a6..000000000 --- a/packages/editor/src/lib/editor/shapes/embed/EmbedShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { embedShapeMigrations, embedShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { EmbedShapeUtil } from './EmbedShapeUtil' - -/** @public */ -export const EmbedShape = defineShape('embed', { - util: EmbedShapeUtil, - props: embedShapeProps, - migrations: embedShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/frame/FrameShape.ts b/packages/editor/src/lib/editor/shapes/frame/FrameShape.ts deleted file mode 100644 index 83444cd46..000000000 --- a/packages/editor/src/lib/editor/shapes/frame/FrameShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { frameShapeMigrations, frameShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { FrameShapeUtil } from './FrameShapeUtil' - -/** @public */ -export const FrameShape = defineShape('frame', { - util: FrameShapeUtil, - props: frameShapeProps, - migrations: frameShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/frame/FrameShapeTool.ts b/packages/editor/src/lib/editor/shapes/frame/FrameShapeTool.ts deleted file mode 100644 index e6c130338..000000000 --- a/packages/editor/src/lib/editor/shapes/frame/FrameShapeTool.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { BaseBoxShapeTool } from '../../tools/BaseBoxShapeTool/BaseBoxShapeTool' - -export class FrameShapeTool extends BaseBoxShapeTool { - static override id = 'frame' - static initial = 'idle' - - shapeType = 'frame' -} diff --git a/packages/editor/src/lib/editor/shapes/geo/GeoShape.ts b/packages/editor/src/lib/editor/shapes/geo/GeoShape.ts deleted file mode 100644 index 727212c62..000000000 --- a/packages/editor/src/lib/editor/shapes/geo/GeoShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { geoShapeMigrations, geoShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { GeoShapeUtil } from './GeoShapeUtil' - -/** @public */ -export const GeoShape = defineShape('geo', { - util: GeoShapeUtil, - props: geoShapeProps, - migrations: geoShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/shared/DashedOutlineBox.tsx b/packages/editor/src/lib/editor/shapes/group/DashedOutlineBox.tsx similarity index 85% rename from packages/editor/src/lib/editor/shapes/shared/DashedOutlineBox.tsx rename to packages/editor/src/lib/editor/shapes/group/DashedOutlineBox.tsx index 3943c248a..31689d4f8 100644 --- a/packages/editor/src/lib/editor/shapes/shared/DashedOutlineBox.tsx +++ b/packages/editor/src/lib/editor/shapes/group/DashedOutlineBox.tsx @@ -1,5 +1,5 @@ -import { Box2d } from '@tldraw/primitives' -import { getPerfectDashProps } from './getPerfectDashProps' +import { Box2d } from '../../../primitives/Box2d' +import { getPerfectDashProps } from '../shared/getPerfectDashProps' export function DashedOutlineBox({ bounds, diff --git a/packages/editor/src/lib/editor/shapes/group/GroupShape.tsx b/packages/editor/src/lib/editor/shapes/group/GroupShape.tsx deleted file mode 100644 index b797189bf..000000000 --- a/packages/editor/src/lib/editor/shapes/group/GroupShape.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { groupShapeMigrations, groupShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { GroupShapeUtil } from './GroupShapeUtil' - -/** @public */ -export const GroupShape = defineShape('group', { - util: GroupShapeUtil, - props: groupShapeProps, - migrations: groupShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/group/GroupShapeUtil.tsx b/packages/editor/src/lib/editor/shapes/group/GroupShapeUtil.tsx index 65367a3a8..c6ec11229 100644 --- a/packages/editor/src/lib/editor/shapes/group/GroupShapeUtil.tsx +++ b/packages/editor/src/lib/editor/shapes/group/GroupShapeUtil.tsx @@ -1,19 +1,20 @@ -import { Box2d, Matrix2d, Vec2d } from '@tldraw/primitives' -import { TLGroupShape } from '@tldraw/tlschema' +import { TLGroupShape, groupShapeMigrations, groupShapeProps } from '@tldraw/tlschema' import { SVGContainer } from '../../../components/SVGContainer' +import { Box2d } from '../../../primitives/Box2d' +import { Matrix2d } from '../../../primitives/Matrix2d' import { ShapeUtil, TLOnChildrenChangeHandler } from '../ShapeUtil' -import { DashedOutlineBox } from '../shared/DashedOutlineBox' +import { DashedOutlineBox } from './DashedOutlineBox' /** @public */ export class GroupShapeUtil extends ShapeUtil { static override type = 'group' as const + static override props = groupShapeProps + static override migrations = groupShapeMigrations - type = 'group' as const + override hideSelectionBoundsBg = () => false + override hideSelectionBoundsFg = () => true - hideSelectionBoundsBg = () => false - hideSelectionBoundsFg = () => true - - canBind = () => false + override canBind = () => false getDefaultProps(): TLGroupShape['props'] { return {} @@ -35,14 +36,6 @@ export class GroupShapeUtil extends ShapeUtil { return Box2d.FromPoints(allChildPoints) } - getCenter(shape: TLGroupShape): Vec2d { - return this.editor.getBounds(shape).center - } - - getOutline(shape: TLGroupShape): Vec2d[] { - return this.editor.getBounds(shape).corners - } - component(shape: TLGroupShape) { // Not a class component, but eslint can't tell that :( const { @@ -91,7 +84,7 @@ export class GroupShapeUtil extends ShapeUtil { return } - onChildrenChange: TLOnChildrenChangeHandler = (group) => { + override onChildrenChange: TLOnChildrenChangeHandler = (group) => { const children = this.editor.getSortedChildIds(group.id) if (children.length === 0) { if (this.editor.pageState.focusLayerId === group.id) { diff --git a/packages/editor/src/lib/editor/shapes/highlight/HighlightShape.ts b/packages/editor/src/lib/editor/shapes/highlight/HighlightShape.ts deleted file mode 100644 index 6d74df2db..000000000 --- a/packages/editor/src/lib/editor/shapes/highlight/HighlightShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { highlightShapeMigrations, highlightShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { HighlightShapeUtil } from './HighlightShapeUtil' - -/** @public */ -export const HighlightShape = defineShape('highlight', { - util: HighlightShapeUtil, - props: highlightShapeProps, - migrations: highlightShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/image/ImageShape.ts b/packages/editor/src/lib/editor/shapes/image/ImageShape.ts deleted file mode 100644 index 0c83d6a20..000000000 --- a/packages/editor/src/lib/editor/shapes/image/ImageShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { imageShapeMigrations, imageShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { ImageShapeUtil } from './ImageShapeUtil' - -/** @public */ -export const ImageShape = defineShape('image', { - util: ImageShapeUtil, - props: imageShapeProps, - migrations: imageShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/line/LineShape.ts b/packages/editor/src/lib/editor/shapes/line/LineShape.ts deleted file mode 100644 index 59314991e..000000000 --- a/packages/editor/src/lib/editor/shapes/line/LineShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { lineShapeMigrations, lineShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { LineShapeUtil } from './LineShapeUtil' - -/** @public */ -export const LineShape = defineShape('line', { - util: LineShapeUtil, - props: lineShapeProps, - migrations: lineShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/line/toolStates/Idle.ts b/packages/editor/src/lib/editor/shapes/line/toolStates/Idle.ts deleted file mode 100644 index 293549be3..000000000 --- a/packages/editor/src/lib/editor/shapes/line/toolStates/Idle.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { TLShapeId } from '@tldraw/tlschema' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' - -export class Idle extends StateNode { - static override id = 'idle' - - shapeId = '' as TLShapeId - - onEnter = (info: { shapeId: TLShapeId }) => { - this.shapeId = info.shapeId - this.editor.setCursor({ type: 'cross' }) - } - - onPointerDown: TLEventHandlers['onPointerDown'] = () => { - this.parent.transition('pointing', { shapeId: this.shapeId }) - } - - onCancel = () => { - this.editor.setSelectedTool('select') - } -} diff --git a/packages/editor/src/lib/editor/shapes/note/NoteShape.ts b/packages/editor/src/lib/editor/shapes/note/NoteShape.ts deleted file mode 100644 index cb6af23df..000000000 --- a/packages/editor/src/lib/editor/shapes/note/NoteShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { noteShapeMigrations, noteShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { NoteShapeUtil } from './NoteShapeUtil' - -/** @public */ -export const NoteShape = defineShape('note', { - util: NoteShapeUtil, - props: noteShapeProps, - migrations: noteShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/note/toolStates/Idle.ts b/packages/editor/src/lib/editor/shapes/note/toolStates/Idle.ts deleted file mode 100644 index efe5c4a68..000000000 --- a/packages/editor/src/lib/editor/shapes/note/toolStates/Idle.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' - -export class Idle extends StateNode { - static override id = 'idle' - - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { - this.parent.transition('pointing', info) - } - - onEnter = () => { - this.editor.setCursor({ type: 'cross' }) - } - - onCancel = () => { - this.editor.setSelectedTool('select') - } -} diff --git a/packages/editor/src/lib/editor/shapes/arrow/arrow/arrow-types.ts b/packages/editor/src/lib/editor/shapes/shared/arrow/arrow-types.ts similarity index 91% rename from packages/editor/src/lib/editor/shapes/arrow/arrow/arrow-types.ts rename to packages/editor/src/lib/editor/shapes/shared/arrow/arrow-types.ts index 35122f722..864a0f371 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/arrow/arrow-types.ts +++ b/packages/editor/src/lib/editor/shapes/shared/arrow/arrow-types.ts @@ -1,5 +1,5 @@ -import { VecLike } from '@tldraw/primitives' import { TLArrowShapeArrowheadStyle } from '@tldraw/tlschema' +import { VecLike } from '../../../../primitives/Vec2d' export type ArrowPoint = { handle: VecLike diff --git a/packages/editor/src/lib/editor/shapes/arrow/arrow/arrowheads.ts b/packages/editor/src/lib/editor/shapes/shared/arrow/arrowheads.ts similarity index 94% rename from packages/editor/src/lib/editor/shapes/arrow/arrow/arrowheads.ts rename to packages/editor/src/lib/editor/shapes/shared/arrow/arrowheads.ts index 93977f482..e3664bb45 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/arrow/arrowheads.ts +++ b/packages/editor/src/lib/editor/shapes/shared/arrow/arrowheads.ts @@ -1,4 +1,6 @@ -import { intersectCircleCircle, PI, TAU, Vec2d, VecLike } from '@tldraw/primitives' +import { Vec2d, VecLike } from '../../../../primitives/Vec2d' +import { intersectCircleCircle } from '../../../../primitives/intersect' +import { PI, TAU } from '../../../../primitives/utils' import { ArrowInfo } from './arrow-types' type TLArrowPointsInfo = { @@ -106,6 +108,7 @@ export function getPipeHead() { return '' } +/** @public */ export function getArrowheadPathForType( info: ArrowInfo, side: 'start' | 'end', diff --git a/packages/editor/src/lib/editor/shapes/arrow/arrow/curved-arrow.ts b/packages/editor/src/lib/editor/shapes/shared/arrow/curved-arrow.ts similarity index 97% rename from packages/editor/src/lib/editor/shapes/arrow/arrow/curved-arrow.ts rename to packages/editor/src/lib/editor/shapes/shared/arrow/curved-arrow.ts index 4b1433445..3c217b577 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/arrow/curved-arrow.ts +++ b/packages/editor/src/lib/editor/shapes/shared/arrow/curved-arrow.ts @@ -1,28 +1,26 @@ +import { TLArrowShape } from '@tldraw/tlschema' +import { Box2d } from '../../../../primitives/Box2d' +import { Matrix2d } from '../../../../primitives/Matrix2d' +import { Vec2d, VecLike } from '../../../../primitives/Vec2d' +import { intersectCirclePolygon, intersectCirclePolyline } from '../../../../primitives/intersect' import { - Box2d, - getArcLength, - getPointOnCircle, - intersectCirclePolygon, - intersectCirclePolyline, - isSafeFloat, - lerpAngles, - Matrix2d, PI, PI2, + getArcLength, + getPointOnCircle, + isSafeFloat, + lerpAngles, shortAngleDist, - Vec2d, - VecLike, -} from '@tldraw/primitives' -import { TLArrowShape } from '@tldraw/tlschema' +} from '../../../../primitives/utils' import type { Editor } from '../../../Editor' -import { STROKE_SIZES } from '../../shared/default-shape-constants' import { ArcInfo, ArrowInfo } from './arrow-types' import { BOUND_ARROW_OFFSET, + MIN_ARROW_LENGTH, + STROKE_SIZES, + WAY_TOO_BIG_ARROW_BEND_FACTOR, getArrowTerminalsInArrowSpace, getBoundShapeInfoForTerminal, - MIN_ARROW_LENGTH, - WAY_TOO_BIG_ARROW_BEND_FACTOR, } from './shared' import { getStraightArrowInfo } from './straight-arrow' @@ -291,6 +289,7 @@ export function getCurvedArrowInfo(editor: Editor, shape: TLArrowShape, extraBen * Get a solid path for a curved arrow's handles. * * @param info - The arrow info. + * @public */ export function getCurvedArrowHandlePath(info: ArrowInfo & { isStraight: false }) { const { @@ -305,6 +304,7 @@ export function getCurvedArrowHandlePath(info: ArrowInfo & { isStraight: false } * Get a solid path for a curved arrow's body. * * @param info - The arrow info. + * @public */ export function getSolidCurvedArrowPath(info: ArrowInfo & { isStraight: false }) { const { diff --git a/packages/editor/src/lib/editor/shapes/arrow/arrow/shared.ts b/packages/editor/src/lib/editor/shapes/shared/arrow/shared.ts similarity index 90% rename from packages/editor/src/lib/editor/shapes/arrow/arrow/shared.ts rename to packages/editor/src/lib/editor/shapes/shared/arrow/shared.ts index 298fc1f51..27dc1820d 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/arrow/shared.ts +++ b/packages/editor/src/lib/editor/shapes/shared/arrow/shared.ts @@ -1,5 +1,6 @@ -import { Matrix2d, Vec2d } from '@tldraw/primitives' import { TLArrowShape, TLArrowShapeTerminal, TLShape } from '@tldraw/tlschema' +import { Matrix2d } from '../../../../primitives/Matrix2d' +import { Vec2d } from '../../../../primitives/Vec2d' import { Editor } from '../../../Editor' export function getIsArrowStraight(shape: TLArrowShape) { @@ -63,6 +64,7 @@ export function getArrowTerminalInArrowSpace( } } +/** @public */ export function getArrowTerminalsInArrowSpace(editor: Editor, shape: TLArrowShape) { const arrowPageTransform = editor.getPageTransform(shape)! @@ -78,3 +80,11 @@ export const MIN_ARROW_LENGTH = 48 export const BOUND_ARROW_OFFSET = 10 /** @internal */ export const WAY_TOO_BIG_ARROW_BEND_FACTOR = 10 + +/** @public */ +export const STROKE_SIZES: Record = { + s: 2, + m: 3.5, + l: 5, + xl: 10, +} diff --git a/packages/editor/src/lib/editor/shapes/arrow/arrow/straight-arrow.ts b/packages/editor/src/lib/editor/shapes/shared/arrow/straight-arrow.ts similarity index 95% rename from packages/editor/src/lib/editor/shapes/arrow/arrow/straight-arrow.ts rename to packages/editor/src/lib/editor/shapes/shared/arrow/straight-arrow.ts index bcd2b9f7b..9b2a5f621 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/arrow/straight-arrow.ts +++ b/packages/editor/src/lib/editor/shapes/shared/arrow/straight-arrow.ts @@ -1,22 +1,20 @@ +import { TLArrowShape } from '@tldraw/tlschema' +import { Box2d } from '../../../../primitives/Box2d' +import { Matrix2d, Matrix2dModel } from '../../../../primitives/Matrix2d' +import { Vec2d, VecLike } from '../../../../primitives/Vec2d' import { - Box2d, intersectLineSegmentPolygon, intersectLineSegmentPolyline, - Matrix2d, - Matrix2dModel, - Vec2d, - VecLike, -} from '@tldraw/primitives' -import { TLArrowShape } from '@tldraw/tlschema' +} from '../../../../primitives/intersect' import { Editor } from '../../../Editor' -import { STROKE_SIZES } from '../../shared/default-shape-constants' import { ArrowInfo } from './arrow-types' import { BOUND_ARROW_OFFSET, BoundShapeInfo, + MIN_ARROW_LENGTH, + STROKE_SIZES, getArrowTerminalsInArrowSpace, getBoundShapeInfoForTerminal, - MIN_ARROW_LENGTH, } from './shared' export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): ArrowInfo { @@ -205,10 +203,12 @@ function updateArrowheadPointWithBoundShape( targetShapeInfo.didIntersect = true } +/** @public */ export function getStraightArrowHandlePath(info: ArrowInfo & { isStraight: true }) { return getArrowPath(info.start.handle, info.end.handle) } +/** @public */ export function getSolidStraightArrowPath(info: ArrowInfo & { isStraight: true }) { return getArrowPath(info.start.point, info.end.point) } @@ -217,6 +217,7 @@ function getArrowPath(start: VecLike, end: VecLike) { return `M${start.x},${start.y}L${end.x},${end.y}` } +/** @public */ export function getStraightArrowBoundingBox(start: VecLike, end: VecLike) { return new Box2d( Math.min(start.x, end.x), diff --git a/packages/editor/src/lib/editor/shapes/shared/resizeBox.ts b/packages/editor/src/lib/editor/shapes/shared/resizeBox.ts index 0b792265d..2c03cd4e3 100644 --- a/packages/editor/src/lib/editor/shapes/shared/resizeBox.ts +++ b/packages/editor/src/lib/editor/shapes/shared/resizeBox.ts @@ -1,5 +1,6 @@ -import { Box2d, Vec2d } from '@tldraw/primitives' import { Vec2dModel } from '@tldraw/tlschema' +import { Box2d } from '../../../primitives/Box2d' +import { Vec2d } from '../../../primitives/Vec2d' import { TLResizeHandle } from '../../types/selection-types' import { TLBaseBoxShape } from '../BaseBoxShapeUtil' import { TLResizeMode } from '../ShapeUtil' diff --git a/packages/editor/src/lib/editor/shapes/shared/resizeScaled.ts b/packages/editor/src/lib/editor/shapes/shared/resizeScaled.ts index fa580816b..48ac4e561 100644 --- a/packages/editor/src/lib/editor/shapes/shared/resizeScaled.ts +++ b/packages/editor/src/lib/editor/shapes/shared/resizeScaled.ts @@ -1,5 +1,6 @@ -import { Box2d, Vec2d } from '@tldraw/primitives' import { TLShape, Vec2dModel } from '@tldraw/tlschema' +import { Box2d } from '../../../primitives/Box2d' +import { Vec2d } from '../../../primitives/Vec2d' export function resizeScaled( shape: Extract, diff --git a/packages/editor/src/lib/editor/shapes/text/TextShape.ts b/packages/editor/src/lib/editor/shapes/text/TextShape.ts deleted file mode 100644 index f35aaf1ac..000000000 --- a/packages/editor/src/lib/editor/shapes/text/TextShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { textShapeMigrations, textShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { TextShapeUtil } from './TextShapeUtil' - -/** @public */ -export const TextShape = defineShape('text', { - util: TextShapeUtil, - props: textShapeProps, - migrations: textShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/shapes/video/VideoShape.ts b/packages/editor/src/lib/editor/shapes/video/VideoShape.ts deleted file mode 100644 index 32943d3ad..000000000 --- a/packages/editor/src/lib/editor/shapes/video/VideoShape.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { videoShapeMigrations, videoShapeProps } from '@tldraw/tlschema' -import { defineShape } from '../../../config/defineShape' -import { VideoShapeUtil } from './VideoShapeUtil' - -/** @public */ -export const VideoShape = defineShape('video', { - util: VideoShapeUtil, - props: videoShapeProps, - migrations: videoShapeMigrations, -}) diff --git a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts index 09e69f38f..4742dced0 100644 --- a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts +++ b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts @@ -5,8 +5,8 @@ import { Pointing } from './children/Pointing' /** @public */ export abstract class BaseBoxShapeTool extends StateNode { static override id = 'box' - static initial = 'idle' - static children = () => [Idle, Pointing] + static override initial = 'idle' + static override children = () => [Idle, Pointing] - abstract shapeType: string + abstract override shapeType: string } diff --git a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Idle.ts b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Idle.ts index b52db1a56..873bf3d7e 100644 --- a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Idle.ts +++ b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Idle.ts @@ -4,15 +4,15 @@ import { StateNode } from '../../StateNode' export class Idle extends StateNode { static override id = 'idle' - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { this.parent.transition('pointing', info) } - onEnter = () => { - this.editor.setCursor({ type: 'cross' }) + override onEnter = () => { + this.editor.cursor = { type: 'cross', rotation: 0 } } - onCancel = () => { - this.editor.setSelectedTool('select') + override onCancel = () => { + this.editor.setCurrentTool('select') } } diff --git a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts index 458f8b567..4b14514db 100644 --- a/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +++ b/packages/editor/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts @@ -1,5 +1,5 @@ -import { Vec2d } from '@tldraw/primitives' import { createShapeId } from '@tldraw/tlschema' +import { Vec2d } from '../../../../primitives/Vec2d' import { TLBaseBoxShape } from '../../../shapes/BaseBoxShapeUtil' import { TLEventHandlers } from '../../../types/event-types' import { StateNode } from '../../StateNode' @@ -12,12 +12,12 @@ export class Pointing extends StateNode { wasFocusedOnEnter = false - onEnter = () => { + override onEnter = () => { const { isMenuOpen } = this.editor this.wasFocusedOnEnter = !isMenuOpen } - onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { + override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { const { originPagePoint } = this.editor.inputs @@ -42,7 +42,7 @@ export class Pointing extends StateNode { ], true ) - this.editor.setSelectedTool('select.resizing', { + this.editor.setCurrentTool('select.resizing', { ...info, target: 'selection', handle: 'bottom_right', @@ -111,7 +111,7 @@ export class Pointing extends StateNode { if (this.editor.instanceState.isToolLocked) { this.parent.transition('idle', {}) } else { - this.editor.setSelectedTool('select.idle') + this.editor.setCurrentTool('select.idle') } } diff --git a/packages/editor/src/lib/editor/tools/EraserTool/EraserTool.ts b/packages/editor/src/lib/editor/tools/EraserTool/EraserTool.ts deleted file mode 100644 index 9df50d542..000000000 --- a/packages/editor/src/lib/editor/tools/EraserTool/EraserTool.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StateNode } from '../StateNode' - -import { Erasing } from './children/Erasing' -import { Idle } from './children/Idle' -import { Pointing } from './children/Pointing' - -export class EraserTool extends StateNode { - static override id = 'eraser' - static initial = 'idle' - static children = () => [Idle, Pointing, Erasing] - - onEnter = () => { - this.editor.setCursor({ type: 'cross' }) - } -} diff --git a/packages/editor/src/lib/editor/tools/EraserTool/children/Idle.ts b/packages/editor/src/lib/editor/tools/EraserTool/children/Idle.ts deleted file mode 100644 index 95574aad2..000000000 --- a/packages/editor/src/lib/editor/tools/EraserTool/children/Idle.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TLEventHandlers } from '../../../types/event-types' -import { StateNode } from '../../StateNode' - -export class Idle extends StateNode { - static override id = 'idle' - - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { - this.parent.transition('pointing', info) - } -} diff --git a/packages/editor/src/lib/editor/tools/HandTool/children/Idle.ts b/packages/editor/src/lib/editor/tools/HandTool/children/Idle.ts deleted file mode 100644 index 7d5c9e0a4..000000000 --- a/packages/editor/src/lib/editor/tools/HandTool/children/Idle.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TLEventHandlers } from '../../../types/event-types' -import { StateNode } from '../../StateNode' - -export class Idle extends StateNode { - static override id = 'idle' - - onEnter = () => { - this.editor.setCursor({ type: 'grab' }) - } - - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { - this.parent.transition('pointing', info) - } - - onCancel = () => { - this.editor.setSelectedTool('select') - } -} diff --git a/packages/editor/src/lib/editor/tools/HandTool/children/Pointing.ts b/packages/editor/src/lib/editor/tools/HandTool/children/Pointing.ts deleted file mode 100644 index 3725d5858..000000000 --- a/packages/editor/src/lib/editor/tools/HandTool/children/Pointing.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { TLEventHandlers } from '../../../types/event-types' -import { StateNode } from '../../StateNode' - -export class Pointing extends StateNode { - static override id = 'pointing' - - onEnter = () => { - this.editor.stopCameraAnimation() - this.editor.setCursor({ type: 'grabbing' }) - } - - onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { - if (this.editor.inputs.isDragging) { - this.parent.transition('dragging', info) - } - } - - onPointerUp: TLEventHandlers['onPointerUp'] = () => { - this.complete() - } - - onCancel: TLEventHandlers['onCancel'] = () => { - this.complete() - } - - onComplete: TLEventHandlers['onComplete'] = () => { - this.complete() - } - - onInterrupt: TLEventHandlers['onInterrupt'] = () => { - this.complete() - } - - complete() { - this.parent.transition('idle', {}) - } -} diff --git a/packages/editor/src/lib/editor/tools/LaserTool/LaserTool.ts b/packages/editor/src/lib/editor/tools/LaserTool/LaserTool.ts deleted file mode 100644 index e3e062d8b..000000000 --- a/packages/editor/src/lib/editor/tools/LaserTool/LaserTool.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StateNode } from '../StateNode' - -import { Idle } from './children/Idle' -import { Lasering } from './children/Lasering' - -export class LaserTool extends StateNode { - static override id = 'laser' - - static initial = 'idle' - static children = () => [Idle, Lasering] - - onEnter = () => { - this.editor.setCursor({ type: 'cross' }) - } -} diff --git a/packages/editor/src/lib/editor/tools/LaserTool/children/Idle.ts b/packages/editor/src/lib/editor/tools/LaserTool/children/Idle.ts deleted file mode 100644 index db0b3d786..000000000 --- a/packages/editor/src/lib/editor/tools/LaserTool/children/Idle.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TLEventHandlers } from '../../../types/event-types' -import { StateNode } from '../../StateNode' - -export class Idle extends StateNode { - static override id = 'idle' - - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { - this.parent.transition('lasering', info) - } -} diff --git a/packages/editor/src/lib/editor/tools/RootState.ts b/packages/editor/src/lib/editor/tools/RootState.ts index 7b4e2d5e0..9f584725b 100644 --- a/packages/editor/src/lib/editor/tools/RootState.ts +++ b/packages/editor/src/lib/editor/tools/RootState.ts @@ -1,20 +1,20 @@ import { TLEventHandlers } from '../types/event-types' -import { SelectTool } from './SelectTool/SelectTool' import { StateNode } from './StateNode' -import { ZoomTool } from './ZoomTool/ZoomTool' export class RootState extends StateNode { static override id = 'root' - static initial = 'select' - static children = () => [SelectTool, ZoomTool] + static override initial = '' + static override children = () => [] - onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { + override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { switch (info.code) { case 'KeyZ': { if (!(info.shiftKey || info.ctrlKey)) { const currentTool = this.current.value if (currentTool && currentTool.current.value?.id === 'idle') { - this.editor.setSelectedTool('zoom', { ...info, onInteractionEnd: currentTool.id }) + if (this.children!['zoom']) { + this.editor.setCurrentTool('zoom', { ...info, onInteractionEnd: currentTool.id }) + } } } break diff --git a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/PointingCrop.ts b/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/PointingCrop.ts deleted file mode 100644 index 2a94bcd0b..000000000 --- a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/PointingCrop.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TLEventHandlers, TLPointerEvent } from '../../../../../types/event-types' -import { StateNode } from '../../../../StateNode' - -export class PointingCrop extends StateNode { - static override id = 'pointing_crop' - - onCancel: TLEventHandlers['onCancel'] = () => { - this.editor.setSelectedTool('select.crop.idle', {}) - } - - onPointerMove: TLPointerEvent = (info) => { - if (this.editor.inputs.isDragging) { - this.editor.setSelectedTool('select.crop.translating_crop', info) - } - } - - onPointerUp: TLPointerEvent = (info) => { - this.editor.setSelectedTool('select.crop.idle', info) - } -} diff --git a/packages/editor/src/lib/editor/tools/StateNode.ts b/packages/editor/src/lib/editor/tools/StateNode.ts index 66c5e459d..867f07bc6 100644 --- a/packages/editor/src/lib/editor/tools/StateNode.ts +++ b/packages/editor/src/lib/editor/tools/StateNode.ts @@ -128,6 +128,26 @@ export abstract class StateNode implements Partial { } } + /** + * This is a hack / escape hatch that will tell the editor to + * report a different state as active (in `currentToolId`) when + * this state is active. This is usually used when a tool transitions + * to a child of a different state for a certain interaction and then + * returns to the original tool when that interaction completes; and + * where we would want to show the original tool as active in the UI. + * + * @public + */ + _currentToolIdMask = atom('curent tool id mask', undefined as string | undefined) + + @computed get currentToolIdMask() { + return this._currentToolIdMask.value + } + + set currentToolIdMask(id: string | undefined) { + this._currentToolIdMask.set(id) + } + onWheel?: TLEventHandlers['onWheel'] onPointerEnter?: TLEventHandlers['onPointerEnter'] onPointerLeave?: TLEventHandlers['onPointerLeave'] diff --git a/packages/editor/src/lib/editor/tools/ZoomTool/ZoomTool.ts b/packages/editor/src/lib/editor/tools/ZoomTool/ZoomTool.ts deleted file mode 100644 index 5e0e6c4f7..000000000 --- a/packages/editor/src/lib/editor/tools/ZoomTool/ZoomTool.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { TLInterruptEvent, TLKeyboardEvent, TLPointerEventInfo } from '../../types/event-types' -import { StateNode } from '../StateNode' -import { Idle } from './children/Idle' -import { Pointing } from './children/Pointing' -import { ZoomBrushing } from './children/ZoomBrushing' - -export class ZoomTool extends StateNode { - static override id = 'zoom' - static initial = 'idle' - static children = () => [Idle, ZoomBrushing, Pointing] - - info = {} as TLPointerEventInfo & { onInteractionEnd?: string } - - onEnter = (info: TLPointerEventInfo & { onInteractionEnd: string }) => { - this.info = info - this.updateCursor() - } - - updateCursor() { - if (this.editor.inputs.altKey) { - this.editor.setCursor({ type: 'zoom-out' }) - } else { - this.editor.setCursor({ type: 'zoom-in' }) - } - } - - onExit = () => { - this.editor.setZoomBrush(null) - this.editor.setCursor({ type: 'default' }) - } - - onKeyDown: TLKeyboardEvent | undefined = () => { - this.updateCursor() - } - - onKeyUp: TLKeyboardEvent = (info) => { - this.updateCursor() - - if (info.code === 'KeyZ') { - this.complete() - } - } - - onInterrupt: TLInterruptEvent = () => { - this.complete() - } - - private complete() { - // Go back to the previous tool. If we are already in select we want to transition to idle - if (this.info.onInteractionEnd && this.info.onInteractionEnd !== 'select') { - this.editor.setSelectedTool(this.info.onInteractionEnd, this.info) - } else { - this.parent.transition('select', {}) - } - } -} diff --git a/packages/editor/src/lib/editor/tools/ZoomTool/children/Idle.ts b/packages/editor/src/lib/editor/tools/ZoomTool/children/Idle.ts deleted file mode 100644 index 8d119510a..000000000 --- a/packages/editor/src/lib/editor/tools/ZoomTool/children/Idle.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types' -import { StateNode } from '../../StateNode' - -export class Idle extends StateNode { - static override id = 'idle' - - info = {} as TLPointerEventInfo & { onInteractionEnd?: string } - - onEnter = (info: TLPointerEventInfo & { onInteractionEnd: string }) => { - this.info = info - } - - onPointerDown: TLEventHandlers['onPointerUp'] = () => { - this.parent.transition('pointing', this.info) - } -} diff --git a/packages/editor/src/lib/editor/shapes/shared/SvgExportContext.tsx b/packages/editor/src/lib/editor/types/SvgExportContext.tsx similarity index 93% rename from packages/editor/src/lib/editor/shapes/shared/SvgExportContext.tsx rename to packages/editor/src/lib/editor/types/SvgExportContext.tsx index 4143901a3..36b2297c2 100644 --- a/packages/editor/src/lib/editor/shapes/shared/SvgExportContext.tsx +++ b/packages/editor/src/lib/editor/types/SvgExportContext.tsx @@ -1,8 +1,10 @@ +/** @public */ export interface SvgExportDef { key: string getElement: () => Promise | SVGElement | SVGElement[] | null } +/** @public */ export interface SvgExportContext { /** * Add contents to the section of the export SVG. Each export def should have a unique diff --git a/packages/editor/src/lib/editor/types/event-types.ts b/packages/editor/src/lib/editor/types/event-types.ts index d0afa24b8..7b6501c75 100644 --- a/packages/editor/src/lib/editor/types/event-types.ts +++ b/packages/editor/src/lib/editor/types/event-types.ts @@ -1,5 +1,5 @@ -import { VecLike } from '@tldraw/primitives' import { TLHandle, TLShape, Vec2dModel } from '@tldraw/tlschema' +import { VecLike } from '../../primitives/Vec2d' import { TLSelectionHandle } from './selection-types' /** @public */ diff --git a/packages/editor/src/lib/editor/types/external-content.ts b/packages/editor/src/lib/editor/types/external-content.ts new file mode 100644 index 000000000..00bef5698 --- /dev/null +++ b/packages/editor/src/lib/editor/types/external-content.ts @@ -0,0 +1,35 @@ +import { EmbedDefinition } from '@tldraw/tlschema' +import { VecLike } from '../../primitives/Vec2d' + +/** @public */ +export type TLExternalContent = + | { + type: 'text' + point?: VecLike + text: string + } + | { + type: 'files' + files: File[] + point?: VecLike + ignoreParent: boolean + } + | { + type: 'url' + url: string + point?: VecLike + } + | { + type: 'svg-text' + text: string + point?: VecLike + } + | { + type: 'embed' + url: string + point?: VecLike + embed: EmbedDefinition + } + +/** @public */ +export type TLExternalAssetContent = { type: 'file'; file: File } | { type: 'url'; url: string } diff --git a/packages/editor/src/lib/editor/types/selection-types.ts b/packages/editor/src/lib/editor/types/selection-types.ts index 65b3e5e76..49c2aceb0 100644 --- a/packages/editor/src/lib/editor/types/selection-types.ts +++ b/packages/editor/src/lib/editor/types/selection-types.ts @@ -1,4 +1,4 @@ -import { RotateCorner, SelectionCorner, SelectionEdge } from '@tldraw/primitives' +import { RotateCorner, SelectionCorner, SelectionEdge } from '../../primitives/Box2d' /** @public */ export type TLSelectionHandle = SelectionCorner | SelectionEdge | RotateCorner diff --git a/packages/editor/src/lib/hooks/useCanvasEvents.ts b/packages/editor/src/lib/hooks/useCanvasEvents.ts index 1a5a5edb3..f860fec5d 100644 --- a/packages/editor/src/lib/hooks/useCanvasEvents.ts +++ b/packages/editor/src/lib/hooks/useCanvasEvents.ts @@ -1,6 +1,6 @@ import React, { useMemo } from 'react' import { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom' -import { getPointerInfo } from '../utils/svg' +import { getPointerInfo } from '../utils/getPointerInfo' import { useEditor } from './useEditor' export function useCanvasEvents() { diff --git a/packages/editor/src/lib/hooks/useCoarsePointer.ts b/packages/editor/src/lib/hooks/useCoarsePointer.ts index 73cbb17ff..8487be4e3 100644 --- a/packages/editor/src/lib/hooks/useCoarsePointer.ts +++ b/packages/editor/src/lib/hooks/useCoarsePointer.ts @@ -17,8 +17,10 @@ export function useCoarsePointer() { editor.isCoarsePointer = mql.matches } handler() - mql.addEventListener('change', handler) - return () => mql.removeEventListener('change', handler) + if (mql) { + mql.addEventListener('change', handler) + return () => mql.removeEventListener('change', handler) + } } }, [editor]) } diff --git a/packages/editor/src/lib/hooks/useCursor.ts b/packages/editor/src/lib/hooks/useCursor.ts index b4db96e29..d94107cbd 100644 --- a/packages/editor/src/lib/hooks/useCursor.ts +++ b/packages/editor/src/lib/hooks/useCursor.ts @@ -1,6 +1,6 @@ -import { PI, radiansToDegrees } from '@tldraw/primitives' -import { useQuickReactor } from '@tldraw/state' +import { useQuickReactor, useValue } from '@tldraw/state' import { TLCursorType } from '@tldraw/tlschema' +import { PI, radiansToDegrees } from '../primitives/utils' import { useContainer } from './useContainer' import { useEditor } from './useEditor' @@ -62,6 +62,7 @@ const CURSORS: Record st 'zoom-out': (r, f, c) => getCursorCss(ZOOM_OUT_SVG, r, 0, f, c), } +/** @public */ export function getCursor(cursor: TLCursorType, rotation = 0, color = 'black') { return CURSORS[cursor](radiansToDegrees(rotation), false, color) } @@ -71,19 +72,23 @@ const STATIC_CURSORS = ['default', 'pointer', 'cross', 'move', 'grab', 'grabbing export function useCursor() { const editor = useEditor() const container = useContainer() + const isDarkMode = useValue('dark mode', () => editor.user.isDarkMode, [editor]) useQuickReactor( 'useCursor', () => { - const { type, rotation, color } = editor.cursor + const { type, rotation } = editor.cursor if (STATIC_CURSORS.includes(type)) { container.style.setProperty('--tl-cursor', `var(--tl-cursor-${type})`) return } - container.style.setProperty('--tl-cursor', getCursor(type, rotation, color)) + container.style.setProperty( + '--tl-cursor', + getCursor(type, rotation, isDarkMode ? 'white' : 'black') + ) }, - [editor, container] + [editor, container, isDarkMode] ) } diff --git a/packages/editor/src/lib/hooks/useDarkMode.ts b/packages/editor/src/lib/hooks/useDarkMode.ts index 8da32982f..d2b8305bb 100644 --- a/packages/editor/src/lib/hooks/useDarkMode.ts +++ b/packages/editor/src/lib/hooks/useDarkMode.ts @@ -15,16 +15,10 @@ export function useDarkMode() { container.setAttribute('data-color-mode', 'dark') container.classList.remove('tl-theme__light') container.classList.add('tl-theme__dark') - editor.setCursor({ - color: 'white', - }) } else { container.setAttribute('data-color-mode', 'light') container.classList.remove('tl-theme__dark') container.classList.add('tl-theme__light') - editor.setCursor({ - color: 'black', - }) } if (forceSrgb) { container.classList.add('tl-theme__force-sRGB') diff --git a/packages/editor/src/lib/hooks/useDocumentEvents.ts b/packages/editor/src/lib/hooks/useDocumentEvents.ts index 276cb763e..47c6bfa2f 100644 --- a/packages/editor/src/lib/hooks/useDocumentEvents.ts +++ b/packages/editor/src/lib/hooks/useDocumentEvents.ts @@ -15,7 +15,7 @@ export function useDocumentEvents() { if (typeof matchMedia !== undefined) return function updateDevicePixelRatio() { - editor.setDevicePixelRatio(window.devicePixelRatio) + editor.devicePixelRatio = window.devicePixelRatio } const MM = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`) diff --git a/packages/editor/src/lib/hooks/useEditorComponents.tsx b/packages/editor/src/lib/hooks/useEditorComponents.tsx index eeac2d01a..0fd5218c5 100644 --- a/packages/editor/src/lib/hooks/useEditorComponents.tsx +++ b/packages/editor/src/lib/hooks/useEditorComponents.tsx @@ -1,53 +1,80 @@ -import { createContext, useContext } from 'react' -import { DefaultBackground, TLBackgroundComponent } from '../components/DefaultBackground' -import { DefaultBrush, TLBrushComponent } from '../components/DefaultBrush' +import { createContext, useContext, useMemo } from 'react' +import { ShapeIndicator, TLShapeIndicatorComponent } from '../components/ShapeIndicator' +import { + DefaultBackground, + TLBackgroundComponent, +} from '../components/default-components/DefaultBackground' +import { DefaultBrush, TLBrushComponent } from '../components/default-components/DefaultBrush' import { DefaultCollaboratorHint, TLCollaboratorHintComponent, -} from '../components/DefaultCollaboratorHint' -import { DefaultCursor, TLCursorComponent } from '../components/DefaultCursor' -import { DefaultErrorFallback, TLErrorFallbackComponent } from '../components/DefaultErrorFallback' -import { DefaultGrid, TLGridComponent } from '../components/DefaultGrid' -import { DefaultHandle, TLHandleComponent } from '../components/DefaultHandle' -import { DefaultScribble, TLScribbleComponent } from '../components/DefaultScribble' +} from '../components/default-components/DefaultCollaboratorHint' +import { DefaultCursor, TLCursorComponent } from '../components/default-components/DefaultCursor' +import { + DefaultErrorFallback, + TLErrorFallbackComponent, +} from '../components/default-components/DefaultErrorFallback' +import { DefaultGrid, TLGridComponent } from '../components/default-components/DefaultGrid' +import { DefaultHandle, TLHandleComponent } from '../components/default-components/DefaultHandle' +import { + DefaultScribble, + TLScribbleComponent, +} from '../components/default-components/DefaultScribble' +import { + DefaultSelectionBackground, + TLSelectionBackgroundComponent, +} from '../components/default-components/DefaultSelectionBackground' +import { + DefaultSelectionForeground, + TLSelectionForegroundComponent, +} from '../components/default-components/DefaultSelectionForeground' import { DefaultShapeErrorFallback, TLShapeErrorFallbackComponent, -} from '../components/DefaultShapeErrorFallback' +} from '../components/default-components/DefaultShapeErrorFallback' import { DefaultShapeIndicatorErrorFallback, - TLShapeIndicatorErrorFallback as TLShapeIndicatorErrorFallbackComponent, -} from '../components/DefaultShapeIndicatorErrorFallback' -import { DefaultSnapLine, TLSnapLineComponent } from '../components/DefaultSnapLine' -import { DefaultSpinner, TLSpinnerComponent } from '../components/DefaultSpinner' -import { DefaultSvgDefs, TLSvgDefsComponent } from '../components/DefaultSvgDefs' -import { ShapeIndicator, TLShapeIndicatorComponent } from '../components/ShapeIndicator' + TLShapeIndicatorErrorFallbackComponent, +} from '../components/default-components/DefaultShapeIndicatorErrorFallback' +import { + DefaultSnapLine, + TLSnapLineComponent, +} from '../components/default-components/DefaultSnapLine' +import { DefaultSpinner, TLSpinnerComponent } from '../components/default-components/DefaultSpinner' +import { DefaultSvgDefs, TLSvgDefsComponent } from '../components/default-components/DefaultSvgDefs' + +interface BaseEditorComponents { + Background: TLBackgroundComponent + SvgDefs: TLSvgDefsComponent + Brush: TLBrushComponent + ZoomBrush: TLBrushComponent + Cursor: TLCursorComponent + CollaboratorBrush: TLBrushComponent + CollaboratorCursor: TLCursorComponent + CollaboratorHint: TLCollaboratorHintComponent + CollaboratorShapeIndicator: TLShapeIndicatorComponent + Grid: TLGridComponent + Scribble: TLScribbleComponent + CollaboratorScribble: TLScribbleComponent + SnapLine: TLSnapLineComponent + Handle: TLHandleComponent + Spinner: TLSpinnerComponent + SelectionForeground: TLSelectionForegroundComponent + SelectionBackground: TLSelectionBackgroundComponent +} /** @public */ -export interface TLEditorComponents { - Background: TLBackgroundComponent | null - SvgDefs: TLSvgDefsComponent | null - Brush: TLBrushComponent | null - ZoomBrush: TLBrushComponent | null - Cursor: TLCursorComponent | null - CollaboratorBrush: TLBrushComponent | null - CollaboratorCursor: TLCursorComponent | null - CollaboratorHint: TLCollaboratorHintComponent | null - CollaboratorShapeIndicator: TLShapeIndicatorComponent | null - Grid: TLGridComponent | null - Scribble: TLScribbleComponent | null - CollaboratorScribble: TLScribbleComponent | null - SnapLine: TLSnapLineComponent | null - Handle: TLHandleComponent | null +export type TLEditorComponents = { + [K in keyof BaseEditorComponents]: BaseEditorComponents[K] | null +} & { ErrorFallback: TLErrorFallbackComponent ShapeErrorFallback: TLShapeErrorFallbackComponent ShapeIndicatorErrorFallback: TLShapeIndicatorErrorFallbackComponent - Spinner: TLSpinnerComponent | null } -export const EditorComponentsContext = createContext({} as TLEditorComponents) +const EditorComponentsContext = createContext({} as TLEditorComponents) -export type ComponentsContextProviderProps = { +type ComponentsContextProviderProps = { overrides?: Partial children: any } @@ -55,27 +82,32 @@ export type ComponentsContextProviderProps = { export function EditorComponentsProvider({ overrides, children }: ComponentsContextProviderProps) { return ( ({ + Background: DefaultBackground, + SvgDefs: DefaultSvgDefs, + Brush: DefaultBrush, + ZoomBrush: DefaultBrush, + CollaboratorBrush: DefaultBrush, + Cursor: DefaultCursor, + CollaboratorCursor: DefaultCursor, + CollaboratorHint: DefaultCollaboratorHint, + CollaboratorShapeIndicator: ShapeIndicator, + Grid: DefaultGrid, + Scribble: DefaultScribble, + SnapLine: DefaultSnapLine, + Handle: DefaultHandle, + CollaboratorScribble: DefaultScribble, + ErrorFallback: DefaultErrorFallback, + ShapeErrorFallback: DefaultShapeErrorFallback, + ShapeIndicatorErrorFallback: DefaultShapeIndicatorErrorFallback, + Spinner: DefaultSpinner, + SelectionBackground: DefaultSelectionBackground, + SelectionForeground: DefaultSelectionForeground, + ...overrides, + }), + [overrides] + )} > {children} diff --git a/packages/editor/src/lib/hooks/useGestureEvents.ts b/packages/editor/src/lib/hooks/useGestureEvents.ts index b8715cc2f..f7595a16f 100644 --- a/packages/editor/src/lib/hooks/useGestureEvents.ts +++ b/packages/editor/src/lib/hooks/useGestureEvents.ts @@ -1,11 +1,11 @@ -import { Vec2d } from '@tldraw/primitives' import type { AnyHandlerEventTypes, EventTypes, GestureKey, Handler } from '@use-gesture/core/types' import { createUseGesture, pinchAction, wheelAction } from '@use-gesture/react' import throttle from 'lodash.throttle' import * as React from 'react' import { TLWheelEventInfo } from '../editor/types/event-types' +import { Vec2d } from '../primitives/Vec2d' import { preventDefault } from '../utils/dom' -import { normalizeWheel } from './shared' +import { normalizeWheel } from '../utils/normalizeWheel' import { useEditor } from './useEditor' type check = undefined extends T[Key] diff --git a/packages/editor/src/lib/hooks/useHandleEvents.ts b/packages/editor/src/lib/hooks/useHandleEvents.ts index 614c4732d..d261172ca 100644 --- a/packages/editor/src/lib/hooks/useHandleEvents.ts +++ b/packages/editor/src/lib/hooks/useHandleEvents.ts @@ -2,7 +2,7 @@ import { TLArrowShape, TLLineShape, TLShapeId } from '@tldraw/tlschema' import * as React from 'react' import { Editor } from '../editor/Editor' import { loopToHtmlElement, releasePointerCapture, setPointerCapture } from '../utils/dom' -import { getPointerInfo } from '../utils/svg' +import { getPointerInfo } from '../utils/getPointerInfo' import { useEditor } from './useEditor' function getHandle(editor: Editor, id: TLShapeId, handleId: string) { diff --git a/packages/editor/src/lib/hooks/useIsCropping.ts b/packages/editor/src/lib/hooks/useIsCropping.ts index 5202df30c..c3351c8d5 100644 --- a/packages/editor/src/lib/hooks/useIsCropping.ts +++ b/packages/editor/src/lib/hooks/useIsCropping.ts @@ -2,6 +2,7 @@ import { useValue } from '@tldraw/state' import { TLShapeId } from '@tldraw/tlschema' import { useEditor } from './useEditor' +/** @public */ export function useIsCropping(shapeId: TLShapeId) { const editor = useEditor() return useValue('isCropping', () => editor.croppingId === shapeId, [editor, shapeId]) diff --git a/packages/editor/src/lib/hooks/useIsEditing.ts b/packages/editor/src/lib/hooks/useIsEditing.ts index 9178f1557..72bba078e 100644 --- a/packages/editor/src/lib/hooks/useIsEditing.ts +++ b/packages/editor/src/lib/hooks/useIsEditing.ts @@ -2,6 +2,7 @@ import { useValue } from '@tldraw/state' import { TLShapeId } from '@tldraw/tlschema' import { useEditor } from './useEditor' +/** @public */ export function useIsEditing(shapeId: TLShapeId) { const editor = useEditor() return useValue('isEditing', () => editor.editingId === shapeId, [editor, shapeId]) diff --git a/packages/editor/src/lib/hooks/useLocalStore.ts b/packages/editor/src/lib/hooks/useLocalStore.ts index 46c837cf2..8c9389993 100644 --- a/packages/editor/src/lib/hooks/useLocalStore.ts +++ b/packages/editor/src/lib/hooks/useLocalStore.ts @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react' import { TLStoreOptions } from '../config/createTLStore' -import { uniqueId } from '../utils/data' import { TLStoreWithStatus } from '../utils/sync/StoreWithStatus' import { TLLocalSyncClient } from '../utils/sync/TLLocalSyncClient' +import { uniqueId } from '../utils/uniqueId' import { useTLStore } from './useTLStore' /** @internal */ diff --git a/packages/editor/src/lib/hooks/usePeerIds.ts b/packages/editor/src/lib/hooks/usePeerIds.ts index 57032fa3c..69a0291e1 100644 --- a/packages/editor/src/lib/hooks/usePeerIds.ts +++ b/packages/editor/src/lib/hooks/usePeerIds.ts @@ -1,6 +1,6 @@ import { useComputed, useValue } from '@tldraw/state' -import uniq from 'lodash.uniq' import { useMemo } from 'react' +import { uniq } from '../utils/uniq' import { useEditor } from './useEditor' // TODO: maybe move this to a computed property on the App class? diff --git a/packages/editor/src/lib/hooks/usePrevious.ts b/packages/editor/src/lib/hooks/usePrevious.ts deleted file mode 100644 index 3f4f68947..000000000 --- a/packages/editor/src/lib/hooks/usePrevious.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useEffect, useRef } from 'react' - -/** @internal */ -export function usePrevious(value: T) { - const ref = useRef(value) - useEffect(() => { - ref.current = value - }) - return ref.current -} diff --git a/packages/editor/src/lib/hooks/useSelectionEvents.ts b/packages/editor/src/lib/hooks/useSelectionEvents.ts index fd92fee9e..9cdf9637b 100644 --- a/packages/editor/src/lib/hooks/useSelectionEvents.ts +++ b/packages/editor/src/lib/hooks/useSelectionEvents.ts @@ -1,9 +1,10 @@ import { useMemo } from 'react' import { TLSelectionHandle } from '../editor/types/selection-types' import { loopToHtmlElement, releasePointerCapture, setPointerCapture } from '../utils/dom' -import { getPointerInfo } from '../utils/svg' +import { getPointerInfo } from '../utils/getPointerInfo' import { useEditor } from './useEditor' +/** @public */ export function useSelectionEvents(handle: TLSelectionHandle) { const editor = useEditor() diff --git a/packages/editor/src/lib/hooks/useShapeEvents.ts b/packages/editor/src/lib/hooks/useShapeEvents.ts index eaae66a06..89e7b9ece 100644 --- a/packages/editor/src/lib/hooks/useShapeEvents.ts +++ b/packages/editor/src/lib/hooks/useShapeEvents.ts @@ -3,7 +3,7 @@ import React from 'react' import { Editor } from '../editor/Editor' import { TLPointerEventName } from '../editor/types/event-types' import { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom' -import { getPointerInfo } from '../utils/svg' +import { getPointerInfo } from '../utils/getPointerInfo' import { useEditor } from './useEditor' const pointerEventHandler = (editor: Editor, shapeId: TLShapeId, name: TLPointerEventName) => { diff --git a/packages/editor/src/lib/hooks/useTLStore.ts b/packages/editor/src/lib/hooks/useTLStore.ts index 390ee4cfa..f3b8593b7 100644 --- a/packages/editor/src/lib/hooks/useTLStore.ts +++ b/packages/editor/src/lib/hooks/useTLStore.ts @@ -1,14 +1,18 @@ -import { useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { TLStoreOptions, createTLStore } from '../config/createTLStore' -import { usePrevious } from './usePrevious' /** @public */ export function useTLStore(opts: TLStoreOptions) { const [store, setStore] = useState(() => createTLStore(opts)) - const prev = usePrevious(opts) + // prev + const ref = useRef(opts) + useEffect(() => void (ref.current = opts)) + if ( // shallow equality check - (Object.keys(prev) as (keyof TLStoreOptions)[]).some((key) => prev[key] !== opts[key]) + (Object.keys(ref.current) as (keyof TLStoreOptions)[]).some( + (key) => ref.current[key] !== opts[key] + ) ) { const newStore = createTLStore(opts) setStore(newStore) diff --git a/packages/editor/src/lib/hooks/useTransform.ts b/packages/editor/src/lib/hooks/useTransform.ts index fc21c34ba..a82e261c2 100644 --- a/packages/editor/src/lib/hooks/useTransform.ts +++ b/packages/editor/src/lib/hooks/useTransform.ts @@ -1,6 +1,7 @@ -import { VecLike } from '@tldraw/primitives' import { useLayoutEffect } from 'react' +import { VecLike } from '../primitives/Vec2d' +/** @public */ export function useTransform( ref: React.RefObject, x?: number, diff --git a/packages/primitives/src/lib/Box2d.test.ts b/packages/editor/src/lib/primitives/Box2d.test.ts similarity index 100% rename from packages/primitives/src/lib/Box2d.test.ts rename to packages/editor/src/lib/primitives/Box2d.test.ts diff --git a/packages/primitives/src/lib/Box2d.ts b/packages/editor/src/lib/primitives/Box2d.ts similarity index 100% rename from packages/primitives/src/lib/Box2d.ts rename to packages/editor/src/lib/primitives/Box2d.ts diff --git a/packages/primitives/src/lib/Matrix2d.test.ts b/packages/editor/src/lib/primitives/Matrix2d.test.ts similarity index 100% rename from packages/primitives/src/lib/Matrix2d.test.ts rename to packages/editor/src/lib/primitives/Matrix2d.test.ts diff --git a/packages/primitives/src/lib/Matrix2d.ts b/packages/editor/src/lib/primitives/Matrix2d.ts similarity index 100% rename from packages/primitives/src/lib/Matrix2d.ts rename to packages/editor/src/lib/primitives/Matrix2d.ts diff --git a/packages/primitives/src/lib/Vec2d.test.ts b/packages/editor/src/lib/primitives/Vec2d.test.ts similarity index 100% rename from packages/primitives/src/lib/Vec2d.test.ts rename to packages/editor/src/lib/primitives/Vec2d.test.ts diff --git a/packages/primitives/src/lib/Vec2d.ts b/packages/editor/src/lib/primitives/Vec2d.ts similarity index 100% rename from packages/primitives/src/lib/Vec2d.ts rename to packages/editor/src/lib/primitives/Vec2d.ts diff --git a/packages/primitives/src/lib/easings.ts b/packages/editor/src/lib/primitives/easings.ts similarity index 100% rename from packages/primitives/src/lib/easings.ts rename to packages/editor/src/lib/primitives/easings.ts diff --git a/packages/primitives/src/lib/intersect.ts b/packages/editor/src/lib/primitives/intersect.ts similarity index 100% rename from packages/primitives/src/lib/intersect.ts rename to packages/editor/src/lib/primitives/intersect.ts diff --git a/packages/primitives/src/lib/utils.ts b/packages/editor/src/lib/primitives/utils.ts similarity index 98% rename from packages/primitives/src/lib/utils.ts rename to packages/editor/src/lib/primitives/utils.ts index a789724f8..0f7e6f2cc 100644 --- a/packages/primitives/src/lib/utils.ts +++ b/packages/editor/src/lib/primitives/utils.ts @@ -1,6 +1,16 @@ import { Box2d } from './Box2d' import { Vec2d, VecLike } from './Vec2d' +/** @public */ +export function precise(A: VecLike) { + return `${toDomPrecision(A.x)},${toDomPrecision(A.y)} ` +} + +/** @public */ +export function average(A: VecLike, B: VecLike) { + return `${toDomPrecision((A.x + B.x) / 2)},${toDomPrecision((A.y + B.y) / 2)} ` +} + /** @public */ export const PI = Math.PI /** @public */ diff --git a/packages/editor/src/lib/test/currentToolIdMask.test.ts b/packages/editor/src/lib/test/currentToolIdMask.test.ts new file mode 100644 index 000000000..d6e17d6e4 --- /dev/null +++ b/packages/editor/src/lib/test/currentToolIdMask.test.ts @@ -0,0 +1,47 @@ +import { createTLStore } from '../config/createTLStore' +import { Editor } from '../editor/Editor' +import { StateNode } from '../editor/tools/StateNode' + +let editor: Editor + +class A extends StateNode { + static override id = 'A' +} + +class B extends StateNode { + static override id = 'B' +} + +class C extends StateNode { + static override id = 'C' + + override onEnter = () => { + this.currentToolIdMask = 'A' + } +} + +beforeEach(() => { + editor = new Editor({ + initialState: 'A', + shapeUtils: [], + tools: [A, B, C], + store: createTLStore({ shapeUtils: [] }), + getContainer: () => document.body, + }) +}) + +describe('current tool id mask', () => { + it('starts with the correct tool id', () => { + expect(editor.currentToolId).toBe('A') + }) + + it('updates the current tool id', () => { + editor.setCurrentTool('B') + expect(editor.currentToolId).toBe('B') + }) + + it('masks the current tool id', () => { + editor.setCurrentTool('C') + expect(editor.currentToolId).toBe('A') + }) +}) diff --git a/packages/editor/src/lib/utils/assets.ts b/packages/editor/src/lib/utils/assets.ts index 4425ab3b5..ee9030115 100644 --- a/packages/editor/src/lib/utils/assets.ts +++ b/packages/editor/src/lib/utils/assets.ts @@ -1,276 +1,3 @@ -import { AssetRecordType, TLAsset, TLAssetId } from '@tldraw/tlschema' -import { getHashForString } from '@tldraw/utils' -import uniq from 'lodash.uniq' -import { MAX_ASSET_HEIGHT, MAX_ASSET_WIDTH } from '../constants' -import { isAnimated } from './is-gif-animated' -import { findChunk, isPng, parsePhys } from './png' - -/** @public */ -export const ACCEPTED_IMG_TYPE = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'] -/** @public */ -export const ACCEPTED_VID_TYPE = ['video/mp4', 'video/quicktime'] -/** @public */ -export const ACCEPTED_ASSET_TYPE = ACCEPTED_IMG_TYPE.concat(ACCEPTED_VID_TYPE).join(', ') - -/** @public */ -export const isImage = (ext: string) => ACCEPTED_IMG_TYPE.includes(ext) - -/** - * Get the size of a video from its source. - * - * @param src - The source of the video. - * @public - */ -export async function getVideoSizeFromSrc(src: string): Promise<{ w: number; h: number }> { - return await new Promise((resolve, reject) => { - const video = document.createElement('video') - video.onloadeddata = () => resolve({ w: video.videoWidth, h: video.videoHeight }) - video.onerror = (e) => { - console.error(e) - reject(new Error('Could not get video size')) - } - video.crossOrigin = 'anonymous' - video.src = src - }) -} - -/** - * @param dataURL - The file as a string. - * @internal - * - * from https://stackoverflow.com/a/53817185 - */ -export async function base64ToFile(dataURL: string) { - return fetch(dataURL).then(function (result) { - return result.arrayBuffer() - }) -} - -/** - * Get the size of an image from its source. - * - * @param dataURL - The file as a string. - * @public - */ -export async function getImageSizeFromSrc(dataURL: string): Promise<{ w: number; h: number }> { - return await new Promise((resolve, reject) => { - const img = new Image() - img.onload = async () => { - try { - const blob = await base64ToFile(dataURL) - const view = new DataView(blob) - if (isPng(view, 0)) { - const physChunk = findChunk(view, 'pHYs') - if (physChunk) { - const physData = parsePhys(view, physChunk.dataOffset) - if (physData.unit === 0 && physData.ppux === physData.ppuy) { - const pixelRatio = Math.round(physData.ppux / 2834.5) - resolve({ w: img.width / pixelRatio, h: img.height / pixelRatio }) - return - } - } - } - - resolve({ w: img.width, h: img.height }) - } catch (err) { - console.error(err) - resolve({ w: img.width, h: img.height }) - } - } - img.onerror = (err) => { - console.error(err) - reject(new Error('Could not get image size')) - } - img.crossOrigin = 'anonymous' - img.src = dataURL - }) -} - -/** - * Get the size of an image from its source. - * - * @param dataURLForImage - The image file as a string. - * @param width - The desired width. - * @param height - The desired height. - * @public - */ -export async function getResizedImageDataUrl( - dataURLForImage: string, - width: number, - height: number -): Promise { - return await new Promise((resolve) => { - const img = new Image() - img.onload = () => { - // Initialize the canvas and it's size - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') - - if (!ctx) return - - // Set width and height - canvas.width = width * 2 - canvas.height = height * 2 - - // Draw image and export to a data-uri - ctx.drawImage(img, 0, 0, canvas.width, canvas.height) - const newDataURL = canvas.toDataURL() - - // Do something with the result, like overwrite original - resolve(newDataURL) - } - img.crossOrigin = 'anonymous' - img.src = dataURLForImage - }) -} - -/** - * Get an asset from a file. - * - * @param file - The file. - * @returns An image or video asset partial. - * @public - */ -export async function getMediaAssetFromFile(file: File): Promise { - return await new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onerror = () => reject(reader.error) - reader.onload = async () => { - let dataUrl = reader.result as string - - const isImageType = isImage(file.type) - const sizeFn = isImageType ? getImageSizeFromSrc : getVideoSizeFromSrc - - // Hack to make .mov videos work via dataURL. - if (file.type === 'video/quicktime' && dataUrl.includes('video/quicktime')) { - dataUrl = dataUrl.replace('video/quicktime', 'video/mp4') - } - - const originalSize = await sizeFn(dataUrl) - const size = containBoxSize(originalSize, { w: MAX_ASSET_WIDTH, h: MAX_ASSET_HEIGHT }) - - if (size !== originalSize && (file.type === 'image/jpeg' || file.type === 'image/png')) { - // If we created a new size and the type is an image, rescale the image - dataUrl = await getResizedImageDataUrl(dataUrl, size.w, size.h) - } - - const assetId: TLAssetId = AssetRecordType.createId(getHashForString(dataUrl)) - - const metadata = await getFileMetaData(file) - - const asset: TLAsset = { - id: assetId, - type: isImageType ? 'image' : 'video', - typeName: 'asset', - props: { - name: file.name, - src: dataUrl, - w: size.w, - h: size.h, - mimeType: file.type, - isAnimated: metadata.isAnimated, - }, - meta: {}, - } - - resolve(asset) - } - - reader.readAsDataURL(file) - }) -} - -/** - * Get some metadata about the file - * - * @param file - The file. - * @public - */ -export async function getFileMetaData(file: File): Promise<{ isAnimated: boolean }> { - if (file.type === 'image/gif') { - return await new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onerror = () => reject(reader.error) - reader.onload = () => { - resolve({ - isAnimated: reader.result ? isAnimated(reader.result as ArrayBuffer) : false, - }) - } - reader.readAsArrayBuffer(file) - }) - } - - return { - isAnimated: isImage(file.type) ? false : true, - } -} - -type BoxWidthHeight = { - w: number - h: number -} - -/** - * Contains the size within the given box size - * - * @param originalSize - The size of the asset - * @param containBoxSize - The container size - * @returns Adjusted size - * @public - */ -export function containBoxSize( - originalSize: BoxWidthHeight, - containBoxSize: BoxWidthHeight -): BoxWidthHeight { - const overByXScale = originalSize.w / containBoxSize.w - const overByYScale = originalSize.h / containBoxSize.h - - if (overByXScale <= 1 && overByYScale <= 1) { - return originalSize - } else if (overByXScale > overByYScale) { - return { - w: originalSize.w / overByXScale, - h: originalSize.h / overByXScale, - } - } else { - return { - w: originalSize.w / overByYScale, - h: originalSize.h / overByYScale, - } - } -} - -/** @public */ -export const isValidHttpURL = (url: string) => { - try { - const u = new URL(url) - return u.protocol === 'http:' || u.protocol === 'https:' - } catch (e) { - return false - } -} - -/** @public */ -export const getValidHttpURLList = (url: string) => { - const urls = url.split(/[\n\s]/) - for (const url of urls) { - try { - const u = new URL(url) - if (!(u.protocol === 'http:' || u.protocol === 'https:')) { - return - } - } catch (e) { - return - } - } - return uniq(urls) -} - -/** @public */ -export const isSvgText = (text: string) => { - return /^ { - (value: T, previous?: number): number -} - -let TABLE: Array | Int32Array = [ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, -] - -if (typeof Int32Array !== 'undefined') { - TABLE = new Int32Array(TABLE) -} - -// crc32, https://github.com/alexgorbatchev/crc/blob/master/src/calculators/crc32.ts -export const crc: CRCCalculator = (current, previous) => { - let crc = previous === 0 ? 0 : ~~previous! ^ -1 - - for (let index = 0; index < current.length; index++) { - crc = TABLE[(crc ^ current[index]) & 0xff] ^ (crc >>> 8) - } - - return crc ^ -1 -} diff --git a/packages/editor/src/lib/utils/data.ts b/packages/editor/src/lib/utils/data.ts deleted file mode 100644 index c66e509e6..000000000 --- a/packages/editor/src/lib/utils/data.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { isPlainObject } from 'is-plain-object' -import { nanoid } from 'nanoid' - -/** - * Generate a unique id. - * - * @example - * - * ```ts - * const id = uniqueId() - * ``` - * - * @public - */ -export function uniqueId() { - return nanoid() -} - -const serializableTypes = new Set(['string', 'number', 'boolean', 'undefined']) - -/** - * Get whether a value is serializable. - * - * @example - * - * ```ts - * const A = isSerializable(1) // true - * const B = isSerializable('a') // true - * const C = isSerializable(true) // true - * const D = isSerializable(undefined) // false - * ``` - * - * @param value - The value to check. - * @public - */ -export function isSerializable(value: any): boolean { - if (serializableTypes.has(typeof value) || value === null) return true - if (Array.isArray(value)) return value.every(isSerializable) - if (isPlainObject(value)) return Object.values(value).every(isSerializable) - return false -} - -/** - * Convert a file to base64. - * - * @example - * - * ```ts - * const A = fileToBase64('./test.png') - * ``` - * - * @param value - The file as a blob. - * @public - */ -export function fileToBase64(file: Blob): Promise { - return new Promise((resolve, reject) => { - if (file) { - const reader = new FileReader() - reader.readAsDataURL(file) - reader.onload = () => resolve(reader.result as string) - reader.onerror = (error) => reject(error) - reader.onabort = (error) => reject(error) - } - }) -} - -/** - * Get an incremented name (e.g. New page (2)) from a name (e.g. New page), based on an array of - * existing names. - * - * @param name - The name to increment. - * @param others - The array of existing names. - * @public - */ -export function getIncrementedName(name: string, others: string[]) { - let result = name - const set = new Set(others) - - while (set.has(result)) { - result = /^.*(\d+)$/.exec(result)?.[1] - ? result.replace(/(\d+)(?=\D?)$/, (m) => { - return (+m + 1).toString() - }) - : `${result} 1` - } - - return result -} - -/** @public */ -export const checkFlag = (flag: boolean | (() => boolean) | undefined) => - typeof flag === 'boolean' ? flag : flag?.() - -/** @public */ -export function snapToGrid(n: number, gridSize: number) { - return Math.round(n / gridSize) * gridSize -} - -const VALID_URL_REGEX = new RegExp( - /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i -) - -/** @public */ -export function isValidUrl(url: string) { - return VALID_URL_REGEX.test(url) -} diff --git a/packages/editor/src/lib/utils/dom.ts b/packages/editor/src/lib/utils/dom.ts index fb90a1294..f1656b617 100644 --- a/packages/editor/src/lib/utils/dom.ts +++ b/packages/editor/src/lib/utils/dom.ts @@ -13,8 +13,7 @@ if the user clicks on a handle but the pointerup does not fire for whatever reason. */ -import { Vec2d } from '@tldraw/primitives' -import React, { useEffect, useState } from 'react' +import React from 'react' import { debugFlags } from './debug-flags' /** @public */ @@ -83,54 +82,5 @@ export function releasePointerCapture( } } -export const ROTATING_BOX_SHADOWS = [ - { - offsetX: 0, - offsetY: 2, - blur: 4, - spread: 0, - color: '#00000029', - }, - { - offsetX: 0, - offsetY: 3, - blur: 6, - spread: 0, - color: '#0000001f', - }, -] - /** @public */ -export function getRotatedBoxShadow(rotation: number) { - const cssStrings = ROTATING_BOX_SHADOWS.map((shadow) => { - const { offsetX, offsetY, blur, spread, color } = shadow - const vec = new Vec2d(offsetX, offsetY) - const { x, y } = vec.rot(-rotation) - return `${x}px ${y}px ${blur}px ${spread}px ${color}` - }) - return cssStrings.join(', ') -} - -/** @public */ -export function usePrefersReducedMotion() { - const [prefersReducedMotion, setPrefersReducedMotion] = useState(false) - - useEffect(() => { - const mql = window.matchMedia('(prefers-reduced-motion: reduce)') - const handler = () => { - setPrefersReducedMotion(mql.matches) - } - handler() - mql.addEventListener('change', handler) - return () => mql.removeEventListener('change', handler) - }, []) - - return prefersReducedMotion -} - -/** @public */ -export const truncateStringWithEllipsis = (str: string, maxLength: number) => { - return str.length <= maxLength ? str : str.substring(0, maxLength - 3) + '...' -} - export const stopEventPropagation = (e: any) => e.stopPropagation() diff --git a/packages/editor/src/lib/utils/getIncrementedName.ts b/packages/editor/src/lib/utils/getIncrementedName.ts new file mode 100644 index 000000000..d2a0a3787 --- /dev/null +++ b/packages/editor/src/lib/utils/getIncrementedName.ts @@ -0,0 +1,22 @@ +/** + * Get an incremented name (e.g. New page (2)) from a name (e.g. New page), based on an array of + * existing names. + * + * @param name - The name to increment. + * @param others - The array of existing names. + * @public + */ +export function getIncrementedName(name: string, others: string[]) { + let result = name + const set = new Set(others) + + while (set.has(result)) { + result = /^.*(\d+)$/.exec(result)?.[1] + ? result.replace(/(\d+)(?=\D?)$/, (m) => { + return (+m + 1).toString() + }) + : `${result} 1` + } + + return result +} diff --git a/packages/editor/src/lib/utils/getPointerInfo.ts b/packages/editor/src/lib/utils/getPointerInfo.ts new file mode 100644 index 000000000..e2657f06a --- /dev/null +++ b/packages/editor/src/lib/utils/getPointerInfo.ts @@ -0,0 +1,20 @@ +/** @public */ +export function getPointerInfo(e: React.PointerEvent | PointerEvent, container: HTMLElement) { + ;(e as any).isKilled = true + + const { top, left } = container.getBoundingClientRect() + + return { + point: { + x: e.clientX - left, + y: e.clientY - top, + z: e.pressure, + }, + shiftKey: e.shiftKey, + altKey: e.altKey, + ctrlKey: e.metaKey || e.ctrlKey, + pointerId: e.pointerId, + button: e.button, + isPen: e.pointerType === 'pen', + } +} diff --git a/packages/editor/src/lib/utils/getSvgPathFromPoints.ts b/packages/editor/src/lib/utils/getSvgPathFromPoints.ts new file mode 100644 index 000000000..d7a60a264 --- /dev/null +++ b/packages/editor/src/lib/utils/getSvgPathFromPoints.ts @@ -0,0 +1,49 @@ +import { VecLike } from '../primitives/Vec2d' +import { average, precise } from '../primitives/utils' + +/** + * Turn an array of points into a path of quadradic curves. + * + * @param points - The points returned from perfect-freehand + * @param closed - Whether the stroke is closed + * + * @public + */ +export function getSvgPathFromPoints(points: VecLike[], closed = true): string { + const len = points.length + + if (len < 2) { + return '' + } + + let a = points[0] + let b = points[1] + + if (len === 2) { + // If only two points, just draw a line + return `M${precise(a)}L${precise(b)}` + } + + let result = '' + + for (let i = 2, max = len - 1; i < max; i++) { + a = points[i] + b = points[i + 1] + result += average(a, b) + } + + if (closed) { + // If closed, draw a curve from the last point to the first + return `M${average(points[0], points[1])}Q${precise(points[1])}${average( + points[1], + points[2] + )}T${result}${average(points[len - 1], points[0])}${average(points[0], points[1])}Z` + } else { + // If not closed, draw a curve starting at the first point and + // ending at the midpoint of the last and second-last point, then + // complete the curve with a line segment to the last point. + return `M${precise(points[0])}Q${precise(points[1])}${average(points[1], points[2])}${ + points.length > 3 ? 'T' : '' + }${result}L${precise(points[len - 1])}` + } +} diff --git a/packages/editor/src/lib/utils/hard-reset.ts b/packages/editor/src/lib/utils/hardResetEditor.ts similarity index 100% rename from packages/editor/src/lib/utils/hard-reset.ts rename to packages/editor/src/lib/utils/hardResetEditor.ts diff --git a/packages/editor/src/lib/hooks/shared.ts b/packages/editor/src/lib/utils/normalizeWheel.ts similarity index 97% rename from packages/editor/src/lib/hooks/shared.ts rename to packages/editor/src/lib/utils/normalizeWheel.ts index 39f7eed83..92e756a16 100644 --- a/packages/editor/src/lib/hooks/shared.ts +++ b/packages/editor/src/lib/utils/normalizeWheel.ts @@ -5,7 +5,7 @@ const IS_DARWIN = /Mac|iPod|iPhone|iPad/.test( ) // Adapted from https://stackoverflow.com/a/13650579 -/** @public */ +/** @internal */ export function normalizeWheel(event: WheelEvent | React.WheelEvent) { let { deltaY, deltaX } = event let deltaZ = 0 diff --git a/packages/editor/src/lib/utils/png.ts b/packages/editor/src/lib/utils/png.ts index 0c2f26283..3746e85e4 100644 --- a/packages/editor/src/lib/utils/png.ts +++ b/packages/editor/src/lib/utils/png.ts @@ -1,6 +1,60 @@ -import { crc } from './crc' +type BufferInput = string | ArrayBuffer | Buffer -export function isPng(view: DataView, offset: number) { +interface CRCCalculator { + (value: T, previous?: number): number +} + +let TABLE: Array | Int32Array = [ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +] + +if (typeof Int32Array !== 'undefined') { + TABLE = new Int32Array(TABLE) +} + +// crc32, https://github.com/alexgorbatchev/crc/blob/master/src/calculators/crc32.ts +const crc: CRCCalculator = (current, previous) => { + let crc = previous === 0 ? 0 : ~~previous! ^ -1 + + for (let index = 0; index < current.length; index++) { + crc = TABLE[(crc ^ current[index]) & 0xff] ^ (crc >>> 8) + } + + return crc ^ -1 +} + +function isPng(view: DataView, offset: number) { if ( view.getUint8(offset + 0) === 0x89 && view.getUint8(offset + 1) === 0x50 && @@ -28,7 +82,7 @@ function getChunkType(view: DataView, offset: number) { const LEN_SIZE = 4 const CRC_SIZE = 4 -export function readChunks(view: DataView, offset = 0) { +function readChunks(view: DataView, offset = 0) { const chunks: Record = {} if (!isPng(view, offset)) { throw new Error('Not a PNG') @@ -61,7 +115,7 @@ export function readChunks(view: DataView, offset = 0) { return chunks } -export function parsePhys(view: DataView, offset: number) { +function parsePhys(view: DataView, offset: number) { return { ppux: view.getUint32(offset), ppuy: view.getUint32(offset + 4), @@ -69,12 +123,12 @@ export function parsePhys(view: DataView, offset: number) { } } -export function findChunk(view: DataView, type: string) { +function findChunk(view: DataView, type: string) { const chunks = readChunks(view) return chunks[type] } -export function setPhysChunk(view: DataView, dpr = 1, options?: BlobPropertyBag) { +function setPhysChunk(view: DataView, dpr = 1, options?: BlobPropertyBag) { let offset = 46 let size = 0 const res1 = findChunk(view, 'pHYs') @@ -113,3 +167,12 @@ export function setPhysChunk(view: DataView, dpr = 1, options?: BlobPropertyBag) return new Blob([startBuf, pHYsData, endBuf], options) } + +/** @public */ +export const png = { + isPng, + readChunks, + parsePhys, + findChunk, + setPhysChunk, +} diff --git a/packages/editor/src/lib/utils/refresh-page.ts b/packages/editor/src/lib/utils/refreshPage.ts similarity index 100% rename from packages/editor/src/lib/utils/refresh-page.ts rename to packages/editor/src/lib/utils/refreshPage.ts diff --git a/packages/editor/src/lib/utils/reorderShapes.ts b/packages/editor/src/lib/utils/reorderShapes.ts index 1d1a09a91..82837c16a 100644 --- a/packages/editor/src/lib/utils/reorderShapes.ts +++ b/packages/editor/src/lib/utils/reorderShapes.ts @@ -1,7 +1,7 @@ -import { getIndicesBetween, sortByIndex } from '@tldraw/indices' import { TLParentId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema' import { compact } from '@tldraw/utils' import { Editor } from '../editor/Editor' +import { getIndicesBetween, sortByIndex } from './reordering/reordering' export function getReorderingShapesChanges( editor: Editor, diff --git a/packages/indices/src/lib/dgreensp/dgreensp.test.ts b/packages/editor/src/lib/utils/reordering/dgreensp/dgreensp.test.ts similarity index 100% rename from packages/indices/src/lib/dgreensp/dgreensp.test.ts rename to packages/editor/src/lib/utils/reordering/dgreensp/dgreensp.test.ts diff --git a/packages/indices/src/lib/dgreensp/dgreensp.ts b/packages/editor/src/lib/utils/reordering/dgreensp/dgreensp.ts similarity index 100% rename from packages/indices/src/lib/dgreensp/dgreensp.ts rename to packages/editor/src/lib/utils/reordering/dgreensp/dgreensp.ts diff --git a/packages/indices/src/lib/dgreensp/index.ts b/packages/editor/src/lib/utils/reordering/dgreensp/index.ts similarity index 100% rename from packages/indices/src/lib/dgreensp/index.ts rename to packages/editor/src/lib/utils/reordering/dgreensp/index.ts diff --git a/packages/indices/src/lib/reordering.test.ts b/packages/editor/src/lib/utils/reordering/reordering.test.ts similarity index 100% rename from packages/indices/src/lib/reordering.test.ts rename to packages/editor/src/lib/utils/reordering/reordering.test.ts diff --git a/packages/indices/src/lib/reordering.ts b/packages/editor/src/lib/utils/reordering/reordering.ts similarity index 100% rename from packages/indices/src/lib/reordering.ts rename to packages/editor/src/lib/utils/reordering/reordering.ts diff --git a/packages/editor/src/lib/utils/rotation.ts b/packages/editor/src/lib/utils/rotation.ts index e83338dfe..0f4c32745 100644 --- a/packages/editor/src/lib/utils/rotation.ts +++ b/packages/editor/src/lib/utils/rotation.ts @@ -1,7 +1,9 @@ -import { canonicalizeRotation, Matrix2d, Vec2d } from '@tldraw/primitives' import { isShapeId, TLShape, TLShapePartial } from '@tldraw/tlschema' import { structuredClone } from '@tldraw/utils' import { Editor } from '../editor/Editor' +import { Matrix2d } from '../primitives/Matrix2d' +import { canonicalizeRotation } from '../primitives/utils' +import { Vec2d } from '../primitives/Vec2d' /** @internal */ export function getRotationSnapshot({ editor }: { editor: Editor }): TLRotationSnapshot | null { diff --git a/packages/editor/src/lib/utils/string.ts b/packages/editor/src/lib/utils/string.ts deleted file mode 100644 index e77b55208..000000000 --- a/packages/editor/src/lib/utils/string.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** @public */ -export function defaultEmptyAs(str: string, dflt: string) { - if (str.match(/^\s*$/)) { - return dflt - } - return str -} - -/** @public */ -export async function blobAsString(blob: Blob) { - return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.addEventListener('loadend', () => { - const text = reader.result - resolve(text as string) - }) - reader.addEventListener('error', () => { - reject(reader.error) - }) - reader.readAsText(blob) - }) -} - -/** @public */ -export async function dataTransferItemAsString(item: DataTransferItem) { - return new Promise((resolve) => { - item.getAsString((text) => { - resolve(text) - }) - }) -} - -/** @public */ -export function correctSpacesToNbsp(input: string) { - return input.replace(/\s/g, '\xa0') -} diff --git a/packages/editor/src/lib/utils/svg.ts b/packages/editor/src/lib/utils/svg.ts deleted file mode 100644 index 7e0cbc848..000000000 --- a/packages/editor/src/lib/utils/svg.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { StrokePoint, toDomPrecision, Vec2d, VecLike } from '@tldraw/primitives' - -/** @internal */ -export function getPointerInfo(e: React.PointerEvent | PointerEvent, container: HTMLElement) { - ;(e as any).isKilled = true - - const { top, left } = container.getBoundingClientRect() - - return { - point: { - x: e.clientX - left, - y: e.clientY - top, - z: e.pressure, - }, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.metaKey || e.ctrlKey, - pointerId: e.pointerId, - button: e.button, - isPen: e.pointerType === 'pen', - } -} - -function precise(A: VecLike) { - return `${toDomPrecision(A.x)},${toDomPrecision(A.y)} ` -} - -function average(A: VecLike, B: VecLike) { - return `${toDomPrecision((A.x + B.x) / 2)},${toDomPrecision((A.y + B.y) / 2)} ` -} - -/** - * Turn an array of points into a path of quadradic curves. - * - * @param points - The points returned from perfect-freehand - * @param closed - Whether the stroke is closed - * @public - */ -export function getSvgPathFromStroke(points: Vec2d[], closed = true): string { - const len = points.length - - if (len < 2) { - return '' - } - - let a = points[0] - let b = points[1] - - if (len === 2) { - // If only two points, just draw a line - return `M${precise(a)}L${precise(b)}` - } - - let result = '' - - for (let i = 2, max = len - 1; i < max; i++) { - a = points[i] - b = points[i + 1] - result += average(a, b) - } - - if (closed) { - // If closed, draw a curve from the last point to the first - return `M${average(points[0], points[1])}Q${precise(points[1])}${average( - points[1], - points[2] - )}T${result}${average(points[len - 1], points[0])}${average(points[0], points[1])}Z` - } else { - // If not closed, draw a curve starting at the first point and - // ending at the midpoint of the last and second-last point, then - // complete the curve with a line segment to the last point. - return `M${precise(points[0])}Q${precise(points[1])}${average(points[1], points[2])}${ - points.length > 3 ? 'T' : '' - }${result}L${precise(points[len - 1])}` - } -} - -/** - * Turn an array of stroke points into a path of quadradic curves. - * - * @param points - The stroke points returned from perfect-freehand - * @param closed - Whether the shape is closed - * @public - */ -export function getSvgPathFromStrokePoints(points: StrokePoint[], closed = false): string { - const len = points.length - - if (len < 2) { - return '' - } - - let a = points[0].point - let b = points[1].point - - if (len === 2) { - return `M${precise(a)}L${precise(b)}` - } - - let result = '' - - for (let i = 2, max = len - 1; i < max; i++) { - a = points[i].point - b = points[i + 1].point - result += average(a, b) - } - - if (closed) { - // If closed, draw a curve from the last point to the first - return `M${average(points[0].point, points[1].point)}Q${precise(points[1].point)}${average( - points[1].point, - points[2].point - )}T${result}${average(points[len - 1].point, points[0].point)}${average( - points[0].point, - points[1].point - )}Z` - } else { - // If not closed, draw a curve starting at the first point and - // ending at the midpoint of the last and second-last point, then - // complete the curve with a line segment to the last point. - return `M${precise(points[0].point)}Q${precise(points[1].point)}${average( - points[1].point, - points[2].point - )}${points.length > 3 ? 'T' : ''}${result}L${precise(points[len - 1].point)}` - } -} diff --git a/packages/editor/src/lib/utils/sync/TLLocalSyncClient.test.ts b/packages/editor/src/lib/utils/sync/TLLocalSyncClient.test.ts index dc03fa0a9..cbc5cfaa4 100644 --- a/packages/editor/src/lib/utils/sync/TLLocalSyncClient.test.ts +++ b/packages/editor/src/lib/utils/sync/TLLocalSyncClient.test.ts @@ -1,7 +1,6 @@ import { PageRecordType } from '@tldraw/tlschema' import { promiseWithResolve } from '@tldraw/utils' import { createTLStore } from '../../config/createTLStore' -import { defaultShapes } from '../../config/defaultShapes' import { TLLocalSyncClient } from './TLLocalSyncClient' import * as idb from './indexedDb' @@ -25,7 +24,7 @@ class BroadcastChannelMock { } function testClient(channel = new BroadcastChannelMock('test')) { - const store = createTLStore({ shapes: defaultShapes }) + const store = createTLStore({ shapeUtils: [] }) const onLoad = jest.fn(() => { return }) diff --git a/packages/editor/src/lib/utils/uniq.ts b/packages/editor/src/lib/utils/uniq.ts new file mode 100644 index 000000000..154791027 --- /dev/null +++ b/packages/editor/src/lib/utils/uniq.ts @@ -0,0 +1,14 @@ +import _uniq from 'lodash.uniq' + +/** @public */ +export function uniq( + array: + | { + readonly length: number + readonly [n: number]: T + } + | null + | undefined +): T[] { + return _uniq(array) +} diff --git a/packages/editor/src/lib/utils/uniqueId.ts b/packages/editor/src/lib/utils/uniqueId.ts new file mode 100644 index 000000000..572fea389 --- /dev/null +++ b/packages/editor/src/lib/utils/uniqueId.ts @@ -0,0 +1,16 @@ +import { nanoid } from 'nanoid' + +/** + * Generate a unique id. + * + * @example + * + * ```ts + * const id = uniqueId() + * ``` + * + * @public + */ +export function uniqueId() { + return nanoid() +} diff --git a/packages/editor/src/version.ts b/packages/editor/src/version.ts index d612daf28..a5a6f192a 100644 --- a/packages/editor/src/version.ts +++ b/packages/editor/src/version.ts @@ -1 +1,2 @@ +/** @internal */ export const version = '2.0.0-alpha.12' diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index be1a289df..7d89ced08 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -11,12 +11,10 @@ "types": ["node", "@types/jest"] }, "references": [ - { "path": "../primitives" }, { "path": "../tlschema" }, { "path": "../store" }, { "path": "../validate" }, { "path": "../utils" }, - { "path": "../indices" }, { "path": "../state" } ] } diff --git a/packages/file-format/CHANGELOG.md b/packages/file-format/CHANGELOG.md deleted file mode 100644 index 1a6bef99d..000000000 --- a/packages/file-format/CHANGELOG.md +++ /dev/null @@ -1,146 +0,0 @@ -# v2.0.0-alpha.14 (Tue Jul 04 2023) - -### Release Notes - -#### [feature] add `meta` property to records ([#1627](https://github.com/tldraw/tldraw/pull/1627)) - -- todo - ---- - -#### 🚀 Enhancement - -- [feature] add `meta` property to records [#1627](https://github.com/tldraw/tldraw/pull/1627) ([@steveruizok](https://github.com/steveruizok)) - -#### Authors: 1 - -- Steve Ruiz ([@steveruizok](https://github.com/steveruizok)) - ---- - -# v2.0.0-alpha.13 (Wed Jun 28 2023) - -### Release Notes - -#### [improvement] store snapshot types ([#1657](https://github.com/tldraw/tldraw/pull/1657)) - -- [dev] Rename `StoreSnapshot` to `SerializedStore` -- [dev] Create new `StoreSnapshot` as type related to `getSnapshot`/`loadSnapshot` - -#### `ShapeUtil` refactor, `Editor` cleanup ([#1611](https://github.com/tldraw/tldraw/pull/1611)) - -- [editor] renames `defaultProps` to `getDefaultProps` -- [editor] removes `outline`, `outlineSegments`, `handles`, `bounds` -- [editor] renames `renderBackground` to `backgroundComponent` - -#### Styles API ([#1580](https://github.com/tldraw/tldraw/pull/1580)) - -- - -#### Add optional generic to `updateShapes` / `createShapes` ([#1579](https://github.com/tldraw/tldraw/pull/1579)) - -- [editor] adds an optional shape generic to `updateShapes` and `createShapes` - -#### mini `defineShape` API ([#1563](https://github.com/tldraw/tldraw/pull/1563)) - -[dev-facing, notes to come] - -#### Renaming types, shape utils, tools ([#1513](https://github.com/tldraw/tldraw/pull/1513)) - -- Renaming of types, shape utils, tools - -#### tlschema cleanup ([#1509](https://github.com/tldraw/tldraw/pull/1509)) - -- [editor] Remove `app.createShapeId` -- [tlschema] Cleans up exports - -#### Rename tlstore to store ([#1507](https://github.com/tldraw/tldraw/pull/1507)) - -- Replace @tldraw/tlstore with @tldraw/store - -#### Rename tlvalidate to validate ([#1508](https://github.com/tldraw/tldraw/pull/1508)) - -- Rename tlvalidate to validate - -#### Filter out unused assets. ([#1502](https://github.com/tldraw/tldraw/pull/1502)) - -- Optimize file size of exported files. - -#### Cleanup @tldraw/ui types / exports ([#1504](https://github.com/tldraw/tldraw/pull/1504)) - -- [editor] clean up / unify types - -#### rename app to editor ([#1503](https://github.com/tldraw/tldraw/pull/1503)) - -- Rename `App` to `Editor` and many other things that reference `app` to `editor`. - -#### [refactor] update record names ([#1473](https://github.com/tldraw/tldraw/pull/1473)) - -- [editor] rename record types - -#### [chore] refactor user preferences ([#1435](https://github.com/tldraw/tldraw/pull/1435)) - -- Add a brief release note for your PR here. - -#### Stricter ID types ([#1439](https://github.com/tldraw/tldraw/pull/1439)) - -[internal only, covered by #1432 changelog] - -#### [refactor] restore createTLSchema ([#1444](https://github.com/tldraw/tldraw/pull/1444)) - -- [editor] Simplifies custom shape definition -- [tldraw] Updates props for component to require a `TldrawEditorConfig`. - -#### avoid lazy race conditions ([#1364](https://github.com/tldraw/tldraw/pull/1364)) - -[internal only] - ---- - -#### 💥 Breaking Change - -- [tweak] migrate store snapshot arguments [#1659](https://github.com/tldraw/tldraw/pull/1659) ([@steveruizok](https://github.com/steveruizok)) -- [improvement] store snapshot types [#1657](https://github.com/tldraw/tldraw/pull/1657) ([@steveruizok](https://github.com/steveruizok)) -- `ShapeUtil` refactor, `Editor` cleanup [#1611](https://github.com/tldraw/tldraw/pull/1611) ([@steveruizok](https://github.com/steveruizok)) -- Styles API [#1580](https://github.com/tldraw/tldraw/pull/1580) ([@SomeHats](https://github.com/SomeHats) [@steveruizok](https://github.com/steveruizok)) -- mini `defineShape` API [#1563](https://github.com/tldraw/tldraw/pull/1563) ([@SomeHats](https://github.com/SomeHats)) -- Independent instance state persistence [#1493](https://github.com/tldraw/tldraw/pull/1493) ([@ds300](https://github.com/ds300)) -- Renaming types, shape utils, tools [#1513](https://github.com/tldraw/tldraw/pull/1513) ([@steveruizok](https://github.com/steveruizok)) -- tlschema cleanup [#1509](https://github.com/tldraw/tldraw/pull/1509) ([@steveruizok](https://github.com/steveruizok)) -- Rename tlstore to store [#1507](https://github.com/tldraw/tldraw/pull/1507) ([@steveruizok](https://github.com/steveruizok)) -- Rename tlvalidate to validate [#1508](https://github.com/tldraw/tldraw/pull/1508) ([@steveruizok](https://github.com/steveruizok)) -- Cleanup @tldraw/ui types / exports [#1504](https://github.com/tldraw/tldraw/pull/1504) ([@steveruizok](https://github.com/steveruizok)) -- rename app to editor [#1503](https://github.com/tldraw/tldraw/pull/1503) ([@steveruizok](https://github.com/steveruizok)) -- [refactor] User-facing APIs [#1478](https://github.com/tldraw/tldraw/pull/1478) ([@steveruizok](https://github.com/steveruizok)) -- [refactor] update record names [#1473](https://github.com/tldraw/tldraw/pull/1473) ([@steveruizok](https://github.com/steveruizok)) -- [chore] refactor user preferences [#1435](https://github.com/tldraw/tldraw/pull/1435) ([@ds300](https://github.com/ds300)) -- [refactor] restore createTLSchema [#1444](https://github.com/tldraw/tldraw/pull/1444) ([@steveruizok](https://github.com/steveruizok)) - -#### 🚀 Enhancement - -- Add optional generic to `updateShapes` / `createShapes` [#1579](https://github.com/tldraw/tldraw/pull/1579) ([@steveruizok](https://github.com/steveruizok)) -- move v1 migration code into file-format [#1499](https://github.com/tldraw/tldraw/pull/1499) ([@steveruizok](https://github.com/steveruizok)) - -#### 🐛 Bug Fix - -- Filter out unused assets. [#1502](https://github.com/tldraw/tldraw/pull/1502) ([@MitjaBezensek](https://github.com/MitjaBezensek)) -- Stricter ID types [#1439](https://github.com/tldraw/tldraw/pull/1439) ([@SomeHats](https://github.com/SomeHats) [@steveruizok](https://github.com/steveruizok)) -- readmes [#1195](https://github.com/tldraw/tldraw/pull/1195) ([@steveruizok](https://github.com/steveruizok)) -- [chore] update lazyrepo [#1211](https://github.com/tldraw/tldraw/pull/1211) ([@ds300](https://github.com/ds300)) -- [lite] upgrade lazyrepo [#1198](https://github.com/tldraw/tldraw/pull/1198) ([@ds300](https://github.com/ds300)) -- transfer-out: transfer out [#1195](https://github.com/tldraw/tldraw/pull/1195) ([@SomeHats](https://github.com/SomeHats)) - -#### ⚠️ Pushed to `main` - -- update lazyrepo ([@ds300](https://github.com/ds300)) - -#### 🏠 Internal - -- avoid lazy race conditions [#1364](https://github.com/tldraw/tldraw/pull/1364) ([@SomeHats](https://github.com/SomeHats)) - -#### Authors: 4 - -- alex ([@SomeHats](https://github.com/SomeHats)) -- David Sheldrick ([@ds300](https://github.com/ds300)) -- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek)) -- Steve Ruiz ([@steveruizok](https://github.com/steveruizok)) diff --git a/packages/file-format/LICENSE b/packages/file-format/LICENSE deleted file mode 100644 index 4f227c380..000000000 --- a/packages/file-format/LICENSE +++ /dev/null @@ -1,190 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -Copyright 2023 tldraw GB Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/file-format/README.md b/packages/file-format/README.md deleted file mode 100644 index 36e9278b7..000000000 --- a/packages/file-format/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @tldraw/file-format - -## License - -The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at [hello@tldraw.com](mailto://hello@tldraw.com). diff --git a/packages/file-format/api-extractor.json b/packages/file-format/api-extractor.json deleted file mode 100644 index f1ed80e93..000000000 --- a/packages/file-format/api-extractor.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "extends": "../../config/api-extractor.json" -} diff --git a/packages/file-format/api-report.md b/packages/file-format/api-report.md deleted file mode 100644 index aa962fb21..000000000 --- a/packages/file-format/api-report.md +++ /dev/null @@ -1,90 +0,0 @@ -## API Report File for "@tldraw/file-format" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { Editor } from '@tldraw/editor'; -import { MigrationFailureReason } from '@tldraw/store'; -import { Result } from '@tldraw/utils'; -import { SerializedSchema } from '@tldraw/store'; -import { TLSchema } from '@tldraw/editor'; -import { TLStore } from '@tldraw/editor'; -import { TLUiToastsContextType } from '@tldraw/ui'; -import { TLUiTranslationKey } from '@tldraw/ui'; -import { UnknownRecord } from '@tldraw/store'; - -// @internal (undocumented) -export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocument): void; - -// @public (undocumented) -export function isV1File(data: any): boolean; - -// @internal (undocumented) -export interface LegacyTldrawDocument { - // (undocumented) - assets: TDAssets; - // (undocumented) - id: string; - // (undocumented) - name: string; - // (undocumented) - pages: Record; - // (undocumented) - pageStates: Record; - // (undocumented) - version: number; -} - -// @internal (undocumented) -export function parseAndLoadDocument(editor: Editor, document: string, msg: (id: TLUiTranslationKey) => string, addToast: TLUiToastsContextType['addToast'], onV1FileLoad?: () => void, forceDarkMode?: boolean): Promise; - -// @public (undocumented) -export function parseTldrawJsonFile({ json, schema, }: { - schema: TLSchema; - json: string; -}): Result; - -// @public (undocumented) -export function serializeTldrawJson(store: TLStore): Promise; - -// @public (undocumented) -export function serializeTldrawJsonBlob(store: TLStore): Promise; - -// @public (undocumented) -export const TLDRAW_FILE_EXTENSION: ".tldr"; - -// @public (undocumented) -export const TLDRAW_FILE_MIMETYPE: "application/vnd.tldraw+json"; - -// @public (undocumented) -export interface TldrawFile { - // (undocumented) - records: UnknownRecord[]; - // (undocumented) - schema: SerializedSchema; - // (undocumented) - tldrawFileFormatVersion: number; -} - -// @public (undocumented) -export type TldrawFileParseError = { - type: 'fileFormatVersionTooNew'; - version: number; -} | { - type: 'invalidRecords'; - cause: unknown; -} | { - type: 'migrationFailed'; - reason: MigrationFailureReason; -} | { - type: 'notATldrawFile'; - cause: unknown; -} | { - type: 'v1File'; - data: any; -}; - -// (No @packageDocumentation comment for this package) - -``` diff --git a/packages/file-format/package.json b/packages/file-format/package.json deleted file mode 100644 index abf84c267..000000000 --- a/packages/file-format/package.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "@tldraw/file-format", - "description": "A tiny little drawing app (file-format).", - "version": "2.0.0-alpha.14", - "packageManager": "yarn@3.5.0", - "author": { - "name": "tldraw GB Ltd.", - "email": "hello@tldraw.com" - }, - "homepage": "https://tldraw.dev", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/tldraw/tldraw" - }, - "bugs": { - "url": "https://github.com/tldraw/tldraw/issues" - }, - "keywords": [ - "tldraw", - "drawing", - "app", - "development", - "whiteboard", - "canvas", - "infinite" - ], - "/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish", - "main": "./src/index.ts", - "types": "./.tsbuild/index.d.ts", - "/* GOTCHA */": "files will include ./dist and index.d.ts by default, add any others you want to include in here", - "files": [], - "scripts": { - "test": "lazy inherit", - "test-coverage": "lazy inherit", - "build": "yarn run -T tsx ../../scripts/build-package.ts", - "build-api": "yarn run -T tsx ../../scripts/build-api.ts", - "prepack": "yarn run -T tsx ../../scripts/prepack.ts", - "postpack": "../../scripts/postpack.sh", - "pack-tarball": "yarn pack", - "lint": "yarn run -T tsx ../../scripts/lint.ts" - }, - "dependencies": { - "@tldraw/editor": "workspace:*", - "@tldraw/primitives": "workspace:*", - "@tldraw/store": "workspace:*", - "@tldraw/ui": "workspace:*", - "@tldraw/utils": "workspace:*", - "@tldraw/validate": "workspace:*" - }, - "jest": { - "preset": "config/jest/node", - "setupFiles": [ - "raf/polyfill" - ], - "moduleNameMapper": { - "^~(.*)": "/src/$1" - }, - "transformIgnorePatterns": [ - "node_modules/(?!(nanoid|escape-string-regexp)/)" - ] - }, - "devDependencies": { - "lazyrepo": "0.0.0-alpha.27" - } -} diff --git a/packages/file-format/src/index.ts b/packages/file-format/src/index.ts deleted file mode 100644 index 9cb2c43e9..000000000 --- a/packages/file-format/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export { buildFromV1Document, type LegacyTldrawDocument } from './lib/buildFromV1Document' -export { - TLDRAW_FILE_EXTENSION, - TLDRAW_FILE_MIMETYPE, - isV1File, - parseAndLoadDocument, - parseTldrawJsonFile, - serializeTldrawJson, - serializeTldrawJsonBlob, - type TldrawFile, - type TldrawFileParseError, -} from './lib/file' diff --git a/packages/file-format/src/test/file.test.ts b/packages/file-format/src/test/file.test.ts deleted file mode 100644 index d0734da58..000000000 --- a/packages/file-format/src/test/file.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { createShapeId, createTLStore, defaultShapes, TLStore } from '@tldraw/editor' -import { MigrationFailureReason, UnknownRecord } from '@tldraw/store' -import { assert } from '@tldraw/utils' -import { parseTldrawJsonFile as _parseTldrawJsonFile, TldrawFile } from '../lib/file' - -const schema = createTLStore({ shapes: defaultShapes }).schema - -const parseTldrawJsonFile = (store: TLStore, json: string) => _parseTldrawJsonFile({ schema, json }) - -function serialize(file: TldrawFile): string { - return JSON.stringify(file) -} - -describe('parseTldrawJsonFile', () => { - it('returns an error if the file is not json', () => { - const store = createTLStore({ shapes: defaultShapes }) - const result = parseTldrawJsonFile(store, 'not json') - assert(!result.ok) - expect(result.error.type).toBe('notATldrawFile') - }) - - it("returns an error if the file doesn't look like a tldraw file", () => { - const store = createTLStore({ shapes: defaultShapes }) - const result = parseTldrawJsonFile(store, JSON.stringify({ not: 'a tldraw file' })) - assert(!result.ok) - expect(result.error.type).toBe('notATldrawFile') - }) - - it('returns an error if the file version is too old', () => { - const store = createTLStore({ shapes: defaultShapes }) - const result = parseTldrawJsonFile( - store, - serialize({ - tldrawFileFormatVersion: 0, - schema: store.schema.serialize(), - records: [], - }) - ) - assert(!result.ok) - expect(result.error.type).toBe('notATldrawFile') - }) - - it('returns an error if the file version is too new', () => { - const store = createTLStore({ shapes: defaultShapes }) - const result = parseTldrawJsonFile( - store, - serialize({ - tldrawFileFormatVersion: 100, - schema: store.schema.serialize(), - records: [], - }) - ) - assert(!result.ok) - expect(result.error.type).toBe('fileFormatVersionTooNew') - }) - - it('returns an error if migrations fail', () => { - const store = createTLStore({ shapes: defaultShapes }) - const serializedSchema = store.schema.serialize() - serializedSchema.storeVersion = 100 - const result = parseTldrawJsonFile( - store, - serialize({ - tldrawFileFormatVersion: 1, - schema: serializedSchema, - records: [], - }) - ) - assert(!result.ok) - assert(result.error.type === 'migrationFailed') - expect(result.error.reason).toBe(MigrationFailureReason.TargetVersionTooOld) - - const store2 = createTLStore({ shapes: defaultShapes }) - const serializedSchema2 = store2.schema.serialize() - serializedSchema2.recordVersions.shape.version = 100 - const result2 = parseTldrawJsonFile( - store2, - serialize({ - tldrawFileFormatVersion: 1, - schema: serializedSchema2, - records: [{ typeName: 'shape', id: createShapeId('shape') }], - }) - ) - - assert(!result2.ok) - assert(result2.error.type === 'migrationFailed') - expect(result2.error.reason).toBe(MigrationFailureReason.TargetVersionTooOld) - }) - - it('returns an error if a record is invalid', () => { - const store = createTLStore({ shapes: defaultShapes }) - const result = parseTldrawJsonFile( - store, - serialize({ - tldrawFileFormatVersion: 1, - schema: store.schema.serialize(), - records: [ - { - typeName: 'shape', - id: createShapeId('shape'), - type: 'geo', - props: {}, - } as UnknownRecord, - ], - }) - ) - - assert(!result.ok) - assert(result.error.type === 'invalidRecords') - expect(result.error.cause).toMatchInlineSnapshot( - `[ValidationError: At shape(id = shape:shape, type = geo).x: Expected number, got undefined]` - ) - }) - - it('returns a store if the file is valid', () => { - const store = createTLStore({ shapes: defaultShapes }) - const result = parseTldrawJsonFile( - store, - serialize({ - tldrawFileFormatVersion: 1, - schema: store.schema.serialize(), - records: [], - }) - ) - assert(result.ok) - }) -}) diff --git a/packages/file-format/tsconfig.json b/packages/file-format/tsconfig.json deleted file mode 100644 index a56829621..000000000 --- a/packages/file-format/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "include": ["src"], - "exclude": ["node_modules", "dist", ".tsbuild*"], - "compilerOptions": { - "outDir": "./.tsbuild", - "rootDir": "src" - }, - "references": [ - { "path": "../editor" }, - { "path": "../store" }, - { "path": "../validate" }, - { "path": "../primitives" }, - { "path": "../utils" }, - { "path": "../ui" } - ] -} diff --git a/packages/indices/CHANGELOG.md b/packages/indices/CHANGELOG.md deleted file mode 100644 index dd7547109..000000000 --- a/packages/indices/CHANGELOG.md +++ /dev/null @@ -1,35 +0,0 @@ -# v2.0.0-alpha.13 (Wed Jun 28 2023) - -### Release Notes - -#### rename app to editor ([#1503](https://github.com/tldraw/tldraw/pull/1503)) - -- Rename `App` to `Editor` and many other things that reference `app` to `editor`. - -#### Create @tldraw/indices package ([#1426](https://github.com/tldraw/tldraw/pull/1426)) - -- [@tldraw/editor] Remove fractional indices code into `@tldraw/indices` -- [@tldraw/indices] Create library for fractional indices code - ---- - -#### 💥 Breaking Change - -- rename app to editor [#1503](https://github.com/tldraw/tldraw/pull/1503) ([@steveruizok](https://github.com/steveruizok)) -- Create @tldraw/indices package [#1426](https://github.com/tldraw/tldraw/pull/1426) ([@steveruizok](https://github.com/steveruizok)) - -#### ⚠️ Pushed to `main` - -- update lazyrepo ([@ds300](https://github.com/ds300)) - -#### 🏠 Internal - -- [chore] remove benchmark [#1489](https://github.com/tldraw/tldraw/pull/1489) ([@steveruizok](https://github.com/steveruizok)) - -#### Authors: 2 - -- David Sheldrick ([@ds300](https://github.com/ds300)) -- Steve Ruiz ([@steveruizok](https://github.com/steveruizok)) - ---- - diff --git a/packages/indices/LICENSE b/packages/indices/LICENSE deleted file mode 100644 index 4f227c380..000000000 --- a/packages/indices/LICENSE +++ /dev/null @@ -1,190 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -Copyright 2023 tldraw GB Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/indices/README.md b/packages/indices/README.md deleted file mode 100644 index 1fd048535..000000000 --- a/packages/indices/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# @tldraw/indices - -Welcome to **indices** by [tldraw](https://tldraw.dev). This library provides functions for features that rely on fractional indexing. It is based on the observable [Implementing Fractional Indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) by [@DavidG](https://twitter.com/DavidG). - -Fractional indexing is a method for creating indices between other indices without running into precision errors. This is useful when items are modeled as object maps rather than as arrays. - -For example, imagine a stack of 5 cards: - -```ts -const cards = { - A: 0, - B: 1, - C: 2, - D: 3, - E: 4, -} -``` - -In a naive implementation of indexing, moving card E to the "bottom" of the stack would mean changing every item in the map. - -```ts -const cards = { - A: 1, - B: 2, - C: 3, - D: 4, - E: 0, // <-- moved to back -} -``` - -each with an index between `0` and `51`. In a naive implementation of indices, moving the top card (index `51`) to the bottom of the deck would mean changing its index to `0`, changing the previous bottom card's index to `1`, and so forth incrementing every other card's index by one. - -## Installation - -```bash -npm i @tldraw/indices -# or -yarn add @tldraw/indices -``` - -## Usage - -### getIndexBetween - -Get an index between two other indices. - -### getIndexAbove - -Get an index above a given index. - -### getIndexBelow - -Get an index below a given index. - -### getIndicesBetween - -Get a number of indices between two indices. - -### getIndicesAbove - -Get a number of indices above a given index. - -### getIndicesBelow - -Get a number of indices below a given index. - -### getIndices - -Get an array of indices with a given length. - -### `sortByIndex` - -Sort an array of objects by their `index` property. - -## License - -The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at [hello@tldraw.com](mailto://hello@tldraw.com). diff --git a/packages/indices/api-extractor.json b/packages/indices/api-extractor.json deleted file mode 100644 index f1ed80e93..000000000 --- a/packages/indices/api-extractor.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "extends": "../../config/api-extractor.json" -} diff --git a/packages/indices/api-report.md b/packages/indices/api-report.md deleted file mode 100644 index 85076cc70..000000000 --- a/packages/indices/api-report.md +++ /dev/null @@ -1,35 +0,0 @@ -## API Report File for "@tldraw/indices" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -// @public -export function getIndexAbove(below: string): string; - -// @public -export function getIndexBelow(above: string): string; - -// @public -export function getIndexBetween(below: string, above?: string): string; - -// @public -export function getIndices(n: number, start?: string): string[]; - -// @public -export function getIndicesAbove(below: string, n: number): string[]; - -// @public -export function getIndicesBelow(above: string, n: number): string[]; - -// @public -export function getIndicesBetween(below: string | undefined, above: string | undefined, n: number): string[]; - -// @public -export function sortByIndex(a: T, b: T): -1 | 0 | 1; - -// (No @packageDocumentation comment for this package) - -``` diff --git a/packages/indices/package.json b/packages/indices/package.json deleted file mode 100644 index b5e719ce2..000000000 --- a/packages/indices/package.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "@tldraw/indices", - "description": "A tiny little drawing app (fractional indices).", - "version": "2.0.0-alpha.14", - "packageManager": "yarn@3.5.0", - "author": { - "name": "tldraw GB Ltd.", - "email": "hello@tldraw.com" - }, - "homepage": "https://tldraw.dev", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/tldraw/tldraw" - }, - "bugs": { - "url": "https://github.com/tldraw/tldraw/issues" - }, - "keywords": [ - "tldraw", - "drawing", - "app", - "development", - "whiteboard", - "canvas", - "infinite", - "fractional", - "index" - ], - "/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish", - "main": "./src/index.ts", - "types": "./.tsbuild/index.d.ts", - "/* GOTCHA */": "files will include ./dist and index.d.ts by default, add any others you want to include in here", - "files": [], - "scripts": { - "test": "lazy inherit", - "test-coverage": "lazy inherit", - "build": "yarn run -T tsx ../../scripts/build-package.ts", - "build-api": "yarn run -T tsx ../../scripts/build-api.ts", - "prepack": "yarn run -T tsx ../../scripts/prepack.ts", - "postpack": "../../scripts/postpack.sh", - "pack-tarball": "yarn pack", - "lint": "yarn run -T tsx ../../scripts/lint.ts" - }, - "jest": { - "preset": "config/jest/node", - "moduleNameMapper": { - "^~(.*)": "/src/$1" - }, - "transformIgnorePatterns": [ - "node_modules/(?!(nanoid|escape-string-regexp)/)" - ] - }, - "devDependencies": { - "lazyrepo": "0.0.0-alpha.27" - } -} diff --git a/packages/indices/src/index.ts b/packages/indices/src/index.ts deleted file mode 100644 index 020568622..000000000 --- a/packages/indices/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { - getIndexAbove, - getIndexBelow, - getIndexBetween, - getIndices, - getIndicesAbove, - getIndicesBelow, - getIndicesBetween, - sortByIndex, -} from './lib/reordering' diff --git a/packages/indices/tsconfig.json b/packages/indices/tsconfig.json deleted file mode 100644 index 5ca07842c..000000000 --- a/packages/indices/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "include": ["src"], - "exclude": ["node_modules", "dist", ".tsbuild*"], - "compilerOptions": { - "outDir": "./.tsbuild", - "rootDir": "src" - }, - "references": [{ "path": "../tlschema" }] -} diff --git a/packages/polyfills/CHANGELOG.md b/packages/polyfills/CHANGELOG.md deleted file mode 100644 index 916d9b19c..000000000 --- a/packages/polyfills/CHANGELOG.md +++ /dev/null @@ -1,162 +0,0 @@ -# v2.0.0-alpha.13 (Wed Jun 28 2023) - -### Release Notes - -#### Revert "Update dependencies (#1613)" ([#1617](https://github.com/tldraw/tldraw/pull/1617)) - -- - -#### avoid lazy race conditions ([#1364](https://github.com/tldraw/tldraw/pull/1364)) - -[internal only] - ---- - -#### 🐛 Bug Fix - -- readmes [#1195](https://github.com/tldraw/tldraw/pull/1195) ([@steveruizok](https://github.com/steveruizok)) -- [chore] update lazyrepo [#1211](https://github.com/tldraw/tldraw/pull/1211) ([@ds300](https://github.com/ds300)) -- [lite] upgrade lazyrepo [#1198](https://github.com/tldraw/tldraw/pull/1198) ([@ds300](https://github.com/ds300)) -- transfer-out: transfer out [#1195](https://github.com/tldraw/tldraw/pull/1195) ([@SomeHats](https://github.com/SomeHats)) - -#### ⚠️ Pushed to `main` - -- update lazyrepo ([@ds300](https://github.com/ds300)) - -#### 🏠 Internal - -- avoid lazy race conditions [#1364](https://github.com/tldraw/tldraw/pull/1364) ([@SomeHats](https://github.com/SomeHats)) - -#### 🔩 Dependency Updates - -- Revert "Update dependencies (#1613)" [#1617](https://github.com/tldraw/tldraw/pull/1617) ([@SomeHats](https://github.com/SomeHats)) -- Update dependencies [#1613](https://github.com/tldraw/tldraw/pull/1613) ([@steveruizok](https://github.com/steveruizok)) - -#### Authors: 3 - -- alex ([@SomeHats](https://github.com/SomeHats)) -- David Sheldrick ([@ds300](https://github.com/ds300)) -- Steve Ruiz ([@steveruizok](https://github.com/steveruizok)) - ---- - -# v2.0.0-alpha.12 (Mon Apr 03 2023) - -#### 🐛 Bug Fix - -- Make sure all types and build stuff get run in CI [#1548](https://github.com/tldraw/tldraw-lite/pull/1548) ([@SomeHats](https://github.com/SomeHats)) -- add pre-commit api report generation [#1517](https://github.com/tldraw/tldraw-lite/pull/1517) ([@SomeHats](https://github.com/SomeHats)) -- [chore] restore api extractor [#1500](https://github.com/tldraw/tldraw-lite/pull/1500) ([@steveruizok](https://github.com/steveruizok)) -- David/publish good [#1488](https://github.com/tldraw/tldraw-lite/pull/1488) ([@ds300](https://github.com/ds300)) -- [chore] alpha 10 [#1486](https://github.com/tldraw/tldraw-lite/pull/1486) ([@ds300](https://github.com/ds300)) -- [chore] bump for alpha 8 [#1485](https://github.com/tldraw/tldraw-lite/pull/1485) ([@steveruizok](https://github.com/steveruizok)) -- stop using broken-af turbo for publishing [#1476](https://github.com/tldraw/tldraw-lite/pull/1476) ([@ds300](https://github.com/ds300)) -- [chore] add canary release script [#1423](https://github.com/tldraw/tldraw-lite/pull/1423) ([@ds300](https://github.com/ds300) [@steveruizok](https://github.com/steveruizok)) -- [chore] upgrade yarn [#1430](https://github.com/tldraw/tldraw-lite/pull/1430) ([@ds300](https://github.com/ds300)) -- repo cleanup [#1426](https://github.com/tldraw/tldraw-lite/pull/1426) ([@steveruizok](https://github.com/steveruizok)) - -#### Authors: 3 - -- alex ([@SomeHats](https://github.com/SomeHats)) -- David Sheldrick ([@ds300](https://github.com/ds300)) -- Steve Ruiz ([@steveruizok](https://github.com/steveruizok)) - ---- - -# @tldraw/polyfills - -## 2.0.0-alpha.10 - -### Patch Changes - -- fix some package build scripting - -## 2.0.0-alpha.9 - -### Patch Changes - -- 4b4399b6e: redeploy with yarn to prevent package version issues - -## 2.0.0-alpha.8 - -### Patch Changes - -- Release day! - -## 2.0.0-alpha.7 - -### Patch Changes - -- Bug fixes. - -## 2.0.0-alpha.6 - -### Patch Changes - -- Add licenses. - -## 2.0.0-alpha.5 - -### Patch Changes - -- Add CSS files to tldraw/tldraw. - -## 2.0.0-alpha.4 - -### Patch Changes - -- Add children to tldraw/tldraw - -## 2.0.0-alpha.3 - -### Patch Changes - -- Change permissions. - -## 2.0.0-alpha.2 - -### Patch Changes - -- Add tldraw, editor - -## 0.1.0-alpha.11 - -### Patch Changes - -- Fix stale reactors. - -## 0.1.0-alpha.10 - -### Patch Changes - -- Fix type export bug. - -## 0.1.0-alpha.9 - -### Patch Changes - -- Fix import bugs. - -## 0.1.0-alpha.8 - -### Patch Changes - -- Changes validation requirements, exports validation helpers. - -## 0.1.0-alpha.7 - -### Patch Changes - -- - Pre-pre-release update - -## 0.0.2-alpha.1 - -### Patch Changes - -- Fix error with HMR - -## 0.0.2-alpha.0 - -### Patch Changes - -- Initial release diff --git a/packages/polyfills/LICENSE b/packages/polyfills/LICENSE deleted file mode 100644 index 4f227c380..000000000 --- a/packages/polyfills/LICENSE +++ /dev/null @@ -1,190 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -Copyright 2023 tldraw GB Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/polyfills/README.md b/packages/polyfills/README.md deleted file mode 100644 index 215f919a6..000000000 --- a/packages/polyfills/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## @tldraw/polyfills - -## License - -The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at [hello@tldraw.com](mailto://hello@tldraw.com). diff --git a/packages/polyfills/api-extractor.json b/packages/polyfills/api-extractor.json deleted file mode 100644 index f1ed80e93..000000000 --- a/packages/polyfills/api-extractor.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "extends": "../../config/api-extractor.json" -} diff --git a/packages/polyfills/api-report.md b/packages/polyfills/api-report.md deleted file mode 100644 index 440a48166..000000000 --- a/packages/polyfills/api-report.md +++ /dev/null @@ -1,9 +0,0 @@ -## API Report File for "@tldraw/polyfills" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -// (No @packageDocumentation comment for this package) - -``` diff --git a/packages/polyfills/package.json b/packages/polyfills/package.json deleted file mode 100644 index 64f37c611..000000000 --- a/packages/polyfills/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@tldraw/polyfills", - "description": "Polyfills for tldraw.", - "version": "2.0.0-alpha.14", - "packageManager": "yarn@3.5.0", - "author": { - "name": "tldraw GB Ltd.", - "email": "hello@tldraw.com" - }, - "homepage": "https://tldraw.dev", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/tldraw/tldraw" - }, - "bugs": { - "url": "https://github.com/tldraw/tldraw/issues" - }, - "keywords": [ - "tldraw", - "drawing", - "app", - "development", - "whiteboard", - "canvas", - "infinite" - ], - "/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish", - "main": "./src/index.ts", - "types": "./.tsbuild/index.d.ts", - "/* GOTCHA */": "files will include ./dist and index.d.ts by default, add any others you want to include in here", - "files": [], - "scripts": { - "build": "yarn run -T tsx ../../scripts/build-package.ts", - "build-api": "yarn run -T tsx ../../scripts/build-api.ts", - "prepack": "yarn run -T tsx ../../scripts/prepack.ts", - "postpack": "../../scripts/postpack.sh", - "pack-tarball": "yarn pack", - "lint": "yarn run -T tsx ../../scripts/lint.ts" - }, - "dependencies": { - "core-js": "^3.27.2" - }, - "devDependencies": { - "lazyrepo": "0.0.0-alpha.27" - } -} diff --git a/packages/polyfills/src/index.ts b/packages/polyfills/src/index.ts deleted file mode 100644 index bcbfa9872..000000000 --- a/packages/polyfills/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './lib/polyfills' diff --git a/packages/polyfills/src/lib/polyfills.ts b/packages/polyfills/src/lib/polyfills.ts deleted file mode 100644 index 917cd647e..000000000 --- a/packages/polyfills/src/lib/polyfills.ts +++ /dev/null @@ -1,5 +0,0 @@ -import 'core-js/stable/array/at' -import 'core-js/stable/array/flat' -import 'core-js/stable/array/flat-map' -import 'core-js/stable/string/at' -import 'core-js/stable/string/replace-all' diff --git a/packages/polyfills/tsconfig.json b/packages/polyfills/tsconfig.json deleted file mode 100644 index 662a89b2d..000000000 --- a/packages/polyfills/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "include": ["src"], - "exclude": ["node_modules", "dist", ".tsbuild*"], - "compilerOptions": { - "outDir": "./.tsbuild", - "rootDir": "src" - } -} diff --git a/packages/primitives/CHANGELOG.md b/packages/primitives/CHANGELOG.md deleted file mode 100644 index da6dc142e..000000000 --- a/packages/primitives/CHANGELOG.md +++ /dev/null @@ -1,223 +0,0 @@ -# v2.0.0-alpha.13 (Wed Jun 28 2023) - -### Release Notes - -#### [refactor] snapping ([#1589](https://github.com/tldraw/tldraw/pull/1589)) - -- [editor] fix bug in snapping - -#### Fix arrows with weird bends crashing ([#1540](https://github.com/tldraw/tldraw/pull/1540)) - -- Fixed a rare crash that could happen when you try to curve an arrow with zero distance. - -#### [2/3] renderer changes to support "sandwich mode" highlighting ([#1418](https://github.com/tldraw/tldraw/pull/1418)) - -[not yet!] - -#### Measure individual words instead of just line breaks for text exports ([#1397](https://github.com/tldraw/tldraw/pull/1397)) - -- Add a brief release note for your PR here. - -#### avoid lazy race conditions ([#1364](https://github.com/tldraw/tldraw/pull/1364)) - -[internal only] - ---- - -#### 🚀 Enhancement - -- [2/3] renderer changes to support "sandwich mode" highlighting [#1418](https://github.com/tldraw/tldraw/pull/1418) ([@SomeHats](https://github.com/SomeHats)) - -#### 🐛 Bug Fix - -- [refactor] snapping [#1589](https://github.com/tldraw/tldraw/pull/1589) ([@steveruizok](https://github.com/steveruizok)) -- Fix arrows with weird bends crashing [#1540](https://github.com/tldraw/tldraw/pull/1540) ([@TodePond](https://github.com/TodePond)) -- ensure that fixed points stay fixed [#1523](https://github.com/tldraw/tldraw/pull/1523) ([@steveruizok](https://github.com/steveruizok)) -- Measure individual words instead of just line breaks for text exports [#1397](https://github.com/tldraw/tldraw/pull/1397) ([@SomeHats](https://github.com/SomeHats)) -- readmes [#1195](https://github.com/tldraw/tldraw/pull/1195) ([@steveruizok](https://github.com/steveruizok)) -- [chore] update lazyrepo [#1211](https://github.com/tldraw/tldraw/pull/1211) ([@ds300](https://github.com/ds300)) -- Use `strokePathData` for `` path to avoid bugs in the inner path algo [#1207](https://github.com/tldraw/tldraw/pull/1207) ([@orangemug](https://github.com/orangemug) [@steveruizok](https://github.com/steveruizok)) -- [lite] upgrade lazyrepo [#1198](https://github.com/tldraw/tldraw/pull/1198) ([@ds300](https://github.com/ds300)) -- transfer-out: transfer out [#1195](https://github.com/tldraw/tldraw/pull/1195) ([@SomeHats](https://github.com/SomeHats)) - -#### ⚠️ Pushed to `main` - -- update lazyrepo ([@ds300](https://github.com/ds300)) - -#### 🏠 Internal - -- replace console.log with nicelog [#1496](https://github.com/tldraw/tldraw/pull/1496) ([@steveruizok](https://github.com/steveruizok)) -- [chore] remove benchmark [#1489](https://github.com/tldraw/tldraw/pull/1489) ([@steveruizok](https://github.com/steveruizok)) -- avoid lazy race conditions [#1364](https://github.com/tldraw/tldraw/pull/1364) ([@SomeHats](https://github.com/SomeHats)) - -#### Authors: 5 - -- alex ([@SomeHats](https://github.com/SomeHats)) -- David Sheldrick ([@ds300](https://github.com/ds300)) -- Lu Wilson ([@TodePond](https://github.com/TodePond)) -- Orange Mug ([@orangemug](https://github.com/orangemug)) -- Steve Ruiz ([@steveruizok](https://github.com/steveruizok)) - ---- - -# v2.0.0-alpha.12 (Mon Apr 03 2023) - -#### 🐛 Bug Fix - -- Make sure all types and build stuff get run in CI [#1548](https://github.com/tldraw/tldraw-lite/pull/1548) ([@SomeHats](https://github.com/SomeHats)) -- add pre-commit api report generation [#1517](https://github.com/tldraw/tldraw-lite/pull/1517) ([@SomeHats](https://github.com/SomeHats)) -- Fix back to content button. [#1519](https://github.com/tldraw/tldraw-lite/pull/1519) ([@MitjaBezensek](https://github.com/MitjaBezensek) [@steveruizok](https://github.com/steveruizok)) -- [chore] restore api extractor [#1500](https://github.com/tldraw/tldraw-lite/pull/1500) ([@steveruizok](https://github.com/steveruizok)) -- David/publish good [#1488](https://github.com/tldraw/tldraw-lite/pull/1488) ([@ds300](https://github.com/ds300)) -- [chore] alpha 10 [#1486](https://github.com/tldraw/tldraw-lite/pull/1486) ([@ds300](https://github.com/ds300)) -- [chore] package build improvements [#1484](https://github.com/tldraw/tldraw-lite/pull/1484) ([@ds300](https://github.com/ds300)) -- [chore] bump for alpha 8 [#1485](https://github.com/tldraw/tldraw-lite/pull/1485) ([@steveruizok](https://github.com/steveruizok)) -- stop using broken-af turbo for publishing [#1476](https://github.com/tldraw/tldraw-lite/pull/1476) ([@ds300](https://github.com/ds300)) -- [chore] add canary release script [#1423](https://github.com/tldraw/tldraw-lite/pull/1423) ([@ds300](https://github.com/ds300) [@steveruizok](https://github.com/steveruizok)) -- [chore] upgrade yarn [#1430](https://github.com/tldraw/tldraw-lite/pull/1430) ([@ds300](https://github.com/ds300)) -- repo cleanup [#1426](https://github.com/tldraw/tldraw-lite/pull/1426) ([@steveruizok](https://github.com/steveruizok)) - -#### Authors: 4 - -- alex ([@SomeHats](https://github.com/SomeHats)) -- David Sheldrick ([@ds300](https://github.com/ds300)) -- Mitja Bezenšek ([@MitjaBezensek](https://github.com/MitjaBezensek)) -- Steve Ruiz ([@steveruizok](https://github.com/steveruizok)) - ---- - -# @tldraw/primitives - -## 2.0.0-alpha.11 - -### Patch Changes - -- fix some package build scripting -- Updated dependencies - - @tldraw/tlschema@2.0.0-alpha.11 - -## 2.0.0-alpha.10 - -### Patch Changes - -- 4b4399b6e: redeploy with yarn to prevent package version issues -- Updated dependencies [4b4399b6e] - - @tldraw/tlschema@2.0.0-alpha.10 - -## 2.0.0-alpha.9 - -### Patch Changes - -- Release day! -- Updated dependencies - - @tldraw/tlschema@2.0.0-alpha.9 - -## 2.0.0-alpha.8 - -### Patch Changes - -- @tldraw/tlschema@2.0.0-alpha.8 - -## 2.0.0-alpha.7 - -### Patch Changes - -- Bug fixes. -- Updated dependencies - - @tldraw/tlschema@2.0.0-alpha.7 - -## 2.0.0-alpha.6 - -### Patch Changes - -- Add licenses. -- Updated dependencies - - @tldraw/tlschema@2.0.0-alpha.6 - -## 2.0.0-alpha.5 - -### Patch Changes - -- Add CSS files to tldraw/tldraw. -- Updated dependencies - - @tldraw/tlschema@2.0.0-alpha.5 - -## 2.0.0-alpha.4 - -### Patch Changes - -- Add children to tldraw/tldraw -- Updated dependencies - - @tldraw/tlschema@2.0.0-alpha.4 - -## 2.0.0-alpha.3 - -### Patch Changes - -- Change permissions. -- Updated dependencies - - @tldraw/tlschema@2.0.0-alpha.3 - -## 2.0.0-alpha.2 - -### Patch Changes - -- Add tldraw, editor -- Updated dependencies - - @tldraw/tlschema@2.0.0-alpha.2 - -## 0.1.0-alpha.11 - -### Patch Changes - -- Fix stale reactors. -- Updated dependencies - - @tldraw/tlschema@0.1.0-alpha.11 - -## 0.1.0-alpha.10 - -### Patch Changes - -- Fix type export bug. -- Updated dependencies - - @tldraw/tlschema@0.1.0-alpha.10 - -## 0.1.0-alpha.9 - -### Patch Changes - -- Fix import bugs. -- Updated dependencies - - @tldraw/tlschema@0.1.0-alpha.9 - -## 0.1.0-alpha.8 - -### Patch Changes - -- Changes validation requirements, exports validation helpers. -- Updated dependencies - - @tldraw/tlschema@0.1.0-alpha.8 - -## 0.1.0-alpha.7 - -### Patch Changes - -- - Pre-pre-release update -- Updated dependencies - - @tldraw/tlschema@0.1.0-alpha.7 - -## 0.0.2-alpha.1 - -### Patch Changes - -- Fix error with HMR -- Updated dependencies - - @tldraw/tlschema@0.0.2-alpha.1 - -## 0.0.2-alpha.0 - -### Patch Changes - -- Initial release -- Updated dependencies - - @tldraw/tlschema@0.0.2-alpha.0 diff --git a/packages/primitives/LICENSE b/packages/primitives/LICENSE deleted file mode 100644 index 4f227c380..000000000 --- a/packages/primitives/LICENSE +++ /dev/null @@ -1,190 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -Copyright 2023 tldraw GB Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/primitives/README.md b/packages/primitives/README.md deleted file mode 100644 index 0ed057ed4..000000000 --- a/packages/primitives/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @tldraw/primitives - -## License - -The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at [hello@tldraw.com](mailto://hello@tldraw.com). diff --git a/packages/primitives/api-extractor.json b/packages/primitives/api-extractor.json deleted file mode 100644 index f1ed80e93..000000000 --- a/packages/primitives/api-extractor.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "extends": "../../config/api-extractor.json" -} diff --git a/packages/primitives/api-report.md b/packages/primitives/api-report.md deleted file mode 100644 index 686f39ff2..000000000 --- a/packages/primitives/api-report.md +++ /dev/null @@ -1,857 +0,0 @@ -## API Report File for "@tldraw/primitives" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { Box2dModel } from '@tldraw/tlschema'; -import { Vec2dModel } from '@tldraw/tlschema'; - -// @public -export function angleDelta(a0: number, a1: number): number; - -// @public -export function approximately(a: number, b: number, precision?: number): boolean; - -// @public -export function areAnglesCompatible(a: number, b: number): boolean; - -// @public (undocumented) -export class Box2d { - constructor(x?: number, y?: number, w?: number, h?: number); - // (undocumented) - get aspectRatio(): number; - // (undocumented) - get center(): Vec2d; - set center(v: Vec2d); - // (undocumented) - clone(): Box2d; - // (undocumented) - static Collides: (A: Box2d, B: Box2d) => boolean; - // (undocumented) - collides(B: Box2d): boolean; - // (undocumented) - static Common: (boxes: Box2d[]) => Box2d; - // (undocumented) - static Contains: (A: Box2d, B: Box2d) => boolean; - // (undocumented) - contains(B: Box2d): boolean; - // (undocumented) - static ContainsPoint: (A: Box2d, B: number | VecLike, y?: number) => boolean; - // (undocumented) - containsPoint(V: number | VecLike, y?: number): boolean; - // (undocumented) - get corners(): Vec2d[]; - // (undocumented) - static Equals(a: Box2d | Box2dModel, b: Box2d | Box2dModel): boolean; - // (undocumented) - equals(other: Box2d | Box2dModel): boolean; - // (undocumented) - static Expand(A: Box2d, B: Box2d): Box2d; - // (undocumented) - expand(A: Box2d): this; - // (undocumented) - static ExpandBy(A: Box2d, n: number): Box2d; - // (undocumented) - expandBy(n: number): this; - // (undocumented) - static From(box: Box2dModel): Box2d; - // (undocumented) - static FromPoints(points: VecLike[]): Box2d; - // (undocumented) - getHandlePoint(handle: SelectionCorner | SelectionEdge): Vec2d; - // (undocumented) - h: number; - // (undocumented) - get height(): number; - set height(n: number); - // (undocumented) - static Includes: (A: Box2d, B: Box2d) => boolean; - // (undocumented) - includes(B: Box2d): boolean; - // (undocumented) - get maxX(): number; - // (undocumented) - get maxY(): number; - // (undocumented) - get midX(): number; - // (undocumented) - get midY(): number; - // (undocumented) - get minX(): number; - set minX(n: number); - // (undocumented) - get minY(): number; - set minY(n: number); - // (undocumented) - get point(): Vec2d; - set point(val: Vec2d); - // (undocumented) - static Resize(box: Box2d, handle: SelectionCorner | SelectionEdge | string, dx: number, dy: number, isAspectRatioLocked?: boolean): { - box: Box2d; - scaleX: number; - scaleY: number; - }; - // (undocumented) - resize(handle: SelectionCorner | SelectionEdge | string, dx: number, dy: number): void; - // (undocumented) - scale(n: number): this; - // (undocumented) - set(x?: number, y?: number, w?: number, h?: number): this; - // (undocumented) - setTo(B: Box2d): this; - // (undocumented) - static Sides: (A: Box2d, inset?: number) => Vec2d[][]; - // (undocumented) - get sides(): Array<[Vec2d, Vec2d]>; - // (undocumented) - get size(): Vec2d; - // (undocumented) - get snapPoints(): Vec2d[]; - // (undocumented) - snapToGrid(size: number): void; - // (undocumented) - toFixed(): this; - // (undocumented) - toJson(): Box2dModel; - // (undocumented) - translate(delta: VecLike): this; - // (undocumented) - union(box: Box2dModel): this; - // (undocumented) - w: number; - // (undocumented) - get width(): number; - set width(n: number); - // (undocumented) - x: number; - // (undocumented) - y: number; -} - -// @public (undocumented) -export function canonicalizeRotation(a: number): number; - -// @public -export function clamp(n: number, min: number): number; - -// @public -export function clamp(n: number, min: number, max: number): number; - -// @public -export function clampRadians(r: number): number; - -// @public (undocumented) -export class CubicSegment2d extends BaseSegment2d { - // (undocumented) - [Symbol.iterator]: (this: InstanceType) => Generator; - constructor(a: VecLike, b: VecLike, c: VecLike, d: VecLike, p?: number); - // (undocumented) - getPath(head?: boolean): string; - // (undocumented) - getPoint(t: number): Vec2d; - // (undocumented) - getX(t: number): number; - // (undocumented) - getY(t: number): number; -} - -// @public (undocumented) -export interface CubicSegment2dModel { - // (undocumented) - a: VecLike; - // (undocumented) - b: VecLike; - // (undocumented) - c: VecLike; - // (undocumented) - d: VecLike; - // (undocumented) - p: number; -} - -// @public (undocumented) -export class CubicSpline2d extends BaseSpline2d { - constructor(points: VecLike[], k?: number, p?: number); - // (undocumented) - static FromPoints(points: Vec2d[]): CubicSpline2d; - // (undocumented) - getSegmentsFromPoints(points: VecLike[], k?: number, p?: number): CubicSegment2d[]; - // (undocumented) - segments: CubicSegment2d[]; -} - -// @public (undocumented) -export function decomposeMatrix2d(m: MatLike): { - x: number; - y: number; - scaleX: number; - scaleY: number; - rotation: number; -}; - -// @public -export function degreesToRadians(d: number): number; - -// @public (undocumented) -export const EASINGS: { - readonly linear: (t: number) => number; - readonly easeInQuad: (t: number) => number; - readonly easeOutQuad: (t: number) => number; - readonly easeInOutQuad: (t: number) => number; - readonly easeInCubic: (t: number) => number; - readonly easeOutCubic: (t: number) => number; - readonly easeInOutCubic: (t: number) => number; - readonly easeInQuart: (t: number) => number; - readonly easeOutQuart: (t: number) => number; - readonly easeInOutQuart: (t: number) => number; - readonly easeInQuint: (t: number) => number; - readonly easeOutQuint: (t: number) => number; - readonly easeInOutQuint: (t: number) => number; - readonly easeInSine: (t: number) => number; - readonly easeOutSine: (t: number) => number; - readonly easeInOutSine: (t: number) => number; - readonly easeInExpo: (t: number) => number; - readonly easeOutExpo: (t: number) => number; - readonly easeInOutExpo: (t: number) => number; -}; - -// @public (undocumented) -export type EasingType = keyof typeof EASINGS; - -// @public (undocumented) -export const EPSILON: number; - -// @public (undocumented) -export function flipSelectionHandleX(handle: SelectionHandle): "bottom_left" | "bottom_right" | "bottom" | "left" | "right" | "top_left" | "top_right" | "top"; - -// @public (undocumented) -export function flipSelectionHandleY(handle: SelectionHandle): "bottom_left" | "bottom_right" | "bottom" | "left" | "right" | "top_left" | "top_right" | "top"; - -// @public -export function getArcLength(C: VecLike, r: number, A: VecLike, B: VecLike): number; - -// @public (undocumented) -export function getDrawLinePathData(id: string, outline: VecLike[], strokeWidth: number): string[]; - -// @public (undocumented) -export function getHeight(pts: VecLike[]): number; - -// @public (undocumented) -export function getMaxX(pts: VecLike[]): number; - -// @public (undocumented) -export function getMaxY(pts: VecLike[]): number; - -// @public (undocumented) -export function getMidX(pts: VecLike[]): number; - -// @public (undocumented) -export function getMidY(pts: VecLike[]): number; - -// @public (undocumented) -export function getMinX(pts: VecLike[]): number; - -// @public (undocumented) -export function getMinY(pts: VecLike[]): number; - -// @public -export function getPointOnCircle(cx: number, cy: number, r: number, a: number): Vec2d; - -// @public (undocumented) -export function getPolygonVertices(width: number, height: number, sides: number): Vec2d[]; - -// @public (undocumented) -export function getRoundedInkyPolygonPath(points: VecLike[]): string; - -// @public (undocumented) -export function getRoundedPolygonPoints(id: string, outline: VecLike[], offset: number, roundness: number, passes: number): VecLike[]; - -// @public -export const getStarBounds: (sides: number, w: number, h: number) => Box2d; - -// @public -export function getStroke(points: VecLike[], options?: StrokeOptions): Vec2d[]; - -// @public -export function getStrokeOutlinePoints(strokePoints: StrokePoint[], options?: StrokeOptions): Vec2d[]; - -// @public -export function getStrokePoints(rawInputPoints: VecLike[], options?: StrokeOptions): StrokePoint[]; - -// @public -export function getSweep(C: VecLike, A: VecLike, B: VecLike): number; - -// @public (undocumented) -export function getWidth(pts: VecLike[]): number; - -// @public -export function intersectCircleCircle(c1: VecLike, r1: number, c2: VecLike, r2: number): Vec2d[]; - -// @public -export function intersectCirclePolygon(c: VecLike, r: number, points: VecLike[]): null | VecLike[]; - -// @public -export function intersectCirclePolyline(c: VecLike, r: number, points: VecLike[]): null | VecLike[]; - -// @public -export function intersectLineSegmentCircle(a1: VecLike, a2: VecLike, c: VecLike, r: number): null | VecLike[]; - -// @public -export function intersectLineSegmentLineSegment(a1: VecLike, a2: VecLike, b1: VecLike, b2: VecLike): null | Vec2d; - -// @public -export function intersectLineSegmentPolygon(a1: VecLike, a2: VecLike, points: VecLike[]): null | VecLike[]; - -// @public -export function intersectLineSegmentPolyline(a1: VecLike, a2: VecLike, points: VecLike[]): null | VecLike[]; - -// @public -export function intersectPolygonBounds(points: VecLike[], bounds: Box2d): null | VecLike[]; - -// @public -export function intersectPolygonPolygon(polygonA: VecLike[], polygonB: VecLike[]): null | VecLike[]; - -// @public -export function isAngleBetween(a: number, b: number, c: number): boolean; - -// @public -export const isSafeFloat: (n: number) => boolean; - -// @public (undocumented) -export function isSelectionCorner(selection: string): selection is SelectionCorner; - -// @public -export function lerpAngles(a0: number, a1: number, t: number): number; - -// @public (undocumented) -export class LineSegment2d extends BaseSegment2d { - constructor(a: VecLike, b: VecLike, p?: number); - // (undocumented) - a: VecLike; - // (undocumented) - static Angle(A: LineSegment2d): number; - // (undocumented) - get angle(): number; - // (undocumented) - b: VecLike; - // (undocumented) - get bounds(): Box2d; - getClosestPointTo(point: VecLike): { - point: Vec2d; - distance: number; - }; - // (undocumented) - getNormal(): Vec2d; - // (undocumented) - getPath(head?: boolean): string; - // (undocumented) - getPoint(t: number): Vec2d; - // (undocumented) - getX(t: number): number; - // (undocumented) - getY(t: number): number; - // (undocumented) - static Length(A: LineSegment2d): number; - // (undocumented) - get length(): number; - // (undocumented) - static Tangent(A: LineSegment2d): Vec2d; - // (undocumented) - get tangent(): Vec2d; -} - -// @public (undocumented) -export interface LineSegment2dModel { - // (undocumented) - a: VecLike; - // (undocumented) - b: VecLike; - // (undocumented) - p: number; -} - -// @public (undocumented) -export function linesIntersect(A: VecLike, B: VecLike, C: VecLike, D: VecLike): boolean; - -// @public -export function longAngleDist(a0: number, a1: number): number; - -// @public (undocumented) -export type MatLike = Matrix2d | Matrix2dModel; - -// @public (undocumented) -export class Matrix2d { - constructor(a: number, b: number, c: number, d: number, e: number, f: number); - // (undocumented) - a: number; - // (undocumented) - static Absolute(m: MatLike): Matrix2dModel; - // (undocumented) - static applyToBounds(m: MatLike, box: Box2d): Box2d; - // (undocumented) - applyToPoint(point: VecLike): Vec2d; - // (undocumented) - static applyToPoint(m: MatLike, point: VecLike): Vec2d; - // (undocumented) - applyToPoints(points: VecLike[]): Vec2d[]; - // (undocumented) - static applyToPoints(m: MatLike, points: VecLike[]): Vec2d[]; - // (undocumented) - static applyToXY(m: MatLike, x: number, y: number): number[]; - // (undocumented) - b: number; - // (undocumented) - c: number; - // (undocumented) - clone(): Matrix2d; - // (undocumented) - static Compose(...matrices: MatLike[]): Matrix2d; - // (undocumented) - d: number; - // (undocumented) - static Decompose(m: MatLike): MatrixInfo; - // (undocumented) - decompose(): MatrixInfo; - // (undocumented) - decomposed(): MatrixInfo; - // (undocumented) - e: number; - // (undocumented) - equals(m: Matrix2d | Matrix2dModel): boolean; - // (undocumented) - f: number; - // (undocumented) - static From(m: MatLike): Matrix2d; - // (undocumented) - static Identity(): Matrix2d; - // (undocumented) - identity(): this; - // (undocumented) - static Inverse(m: Matrix2dModel): Matrix2dModel; - // (undocumented) - invert(): this; - // (undocumented) - static Multiply(m1: Matrix2dModel, m2: Matrix2dModel): Matrix2dModel; - // (undocumented) - multiply(m: Matrix2d | Matrix2dModel): this; - // (undocumented) - static Rotate(r: number, cx?: number, cy?: number): Matrix2d; - // (undocumented) - rotate(r: number, cx?: number, cy?: number): Matrix2d; - // (undocumented) - static Scale: { - (x: number, y: number): Matrix2dModel; - (x: number, y: number, cx: number, cy: number): Matrix2dModel; - }; - // (undocumented) - scale(x: number, y: number): this; - // (undocumented) - setTo(model: Matrix2dModel): this; - // (undocumented) - static Smooth(m: MatLike, precision?: number): MatLike; - // (undocumented) - toCssString(): string; - // (undocumented) - static toCssString(m: MatLike): string; - // (undocumented) - static Translate(x: number, y: number): Matrix2d; - // (undocumented) - translate(x: number, y: number): Matrix2d; -} - -// @public (undocumented) -export interface Matrix2dModel { - // (undocumented) - a: number; - // (undocumented) - b: number; - // (undocumented) - c: number; - // (undocumented) - d: number; - // (undocumented) - e: number; - // (undocumented) - f: number; -} - -// @public (undocumented) -export interface MatrixInfo { - // (undocumented) - rotation: number; - // (undocumented) - scaleX: number; - // (undocumented) - scaleY: number; - // (undocumented) - x: number; - // (undocumented) - y: number; -} - -// @public -export function perimeterOfEllipse(rx: number, ry: number): number; - -// @public (undocumented) -export const PI: number; - -// @public (undocumented) -export const PI2: number; - -// @public -export function pointInBounds(A: VecLike, b: Box2d): boolean; - -// @public -export function pointInCircle(A: VecLike, C: VecLike, r: number): boolean; - -// @public -export function pointInEllipse(A: VecLike, C: VecLike, rx: number, ry: number, rotation?: number): boolean; - -// @public -export function pointInPolygon(A: VecLike, points: VecLike[]): boolean; - -// @public -export function pointInPolyline(A: VecLike, points: VecLike[], distance?: number): boolean; - -// @public -export function pointInRect(A: VecLike, point: VecLike, size: VecLike): boolean; - -// @public -export function pointNearToLineSegment(A: VecLike, p1: VecLike, p2: VecLike, distance?: number): boolean; - -// @public -export function pointNearToPolyline(A: VecLike, points: VecLike[], distance?: number): boolean; - -// @public (undocumented) -export function polygonsIntersect(a: VecLike[], b: VecLike[]): boolean; - -// @public (undocumented) -export class Polyline2d extends BaseSpline2d { - constructor(points: VecLike[], k?: number, p?: number); - // (undocumented) - static FromPoints(points: VecLike[]): Polyline2d; - // (undocumented) - getSegmentsFromPoints(points: VecLike[], p?: number): LineSegment2d[]; - // (undocumented) - segments: LineSegment2d[]; -} - -// @public -export function radiansToDegrees(r: number): number; - -// @public -export function rangeIntersection(a0: number, a1: number, b0: number, b1: number): [number, number] | null; - -// @public (undocumented) -export function rangesOverlap(a0: number, a1: number, b0: number, b1: number): boolean; - -// @public (undocumented) -export const ROTATE_CORNER_TO_SELECTION_CORNER: { - readonly top_left_rotate: "top_left"; - readonly top_right_rotate: "top_right"; - readonly bottom_right_rotate: "bottom_right"; - readonly bottom_left_rotate: "bottom_left"; - readonly mobile_rotate: "top_left"; -}; - -// @public (undocumented) -export type RotateCorner = 'bottom_left_rotate' | 'bottom_right_rotate' | 'mobile_rotate' | 'top_left_rotate' | 'top_right_rotate'; - -// @public (undocumented) -export function rotateSelectionHandle(handle: SelectionHandle, rotation: number): SelectionHandle; - -// @public (undocumented) -export type SelectionCorner = 'bottom_left' | 'bottom_right' | 'top_left' | 'top_right'; - -// @public (undocumented) -export type SelectionEdge = 'bottom' | 'left' | 'right' | 'top'; - -// @public (undocumented) -export type SelectionHandle = SelectionCorner | SelectionEdge; - -// @public (undocumented) -export function setStrokePointRadii(strokePoints: StrokePoint[], options: StrokeOptions): StrokePoint[]; - -// @public -export function shortAngleDist(a0: number, a1: number): number; - -// @public -export function simplify(points: VecLike[], tolerance?: number): VecLike[]; - -// @public (undocumented) -export function simplify2(points: VecLike[], tolerance?: number): VecLike[]; - -// @public (undocumented) -export const SIN: (x: number) => number; - -// @public -export function snapAngle(r: number, segments: number): number; - -// @public -export interface StrokeOptions { - easing?: (pressure: number) => number; - end?: { - cap?: boolean; - taper?: boolean | number; - easing?: (distance: number) => number; - }; - last?: boolean; - simulatePressure?: boolean; - size?: number; - smoothing?: number; - start?: { - cap?: boolean; - taper?: boolean | number; - easing?: (distance: number) => number; - }; - // (undocumented) - streamline?: number; - thinning?: number; -} - -// @public -export interface StrokePoint { - // (undocumented) - distance: number; - // (undocumented) - input: Vec2d; - // (undocumented) - point: Vec2d; - // (undocumented) - pressure: number; - // (undocumented) - radius: number; - // (undocumented) - runningLength: number; - // (undocumented) - vector: Vec2d; -} - -// @public (undocumented) -export const TAU: number; - -// @public -export function toDomPrecision(v: number): number; - -// @public (undocumented) -export function toFixed(v: number): number; - -// @public -export function toPrecision(n: number, precision?: number): number; - -// @public (undocumented) -export class Vec2d { - constructor(x?: number, y?: number, z?: number); - // (undocumented) - static Abs(A: VecLike): Vec2d; - // (undocumented) - abs(): this; - // (undocumented) - static Add(A: VecLike, B: VecLike): Vec2d; - // (undocumented) - add(V: VecLike): this; - // (undocumented) - static AddScalar(A: VecLike, n: number): Vec2d; - // (undocumented) - addScalar(n: number): this; - // (undocumented) - static AddXY(A: VecLike, x: number, y: number): Vec2d; - // (undocumented) - addXY(x: number, y: number): this; - // (undocumented) - static Angle(A: VecLike, B: VecLike): number; - // (undocumented) - angle(B: VecLike): number; - // (undocumented) - static Average(arr: VecLike[]): Vec2d; - // (undocumented) - static Cast(A: VecLike): Vec2d; - // (undocumented) - static Clamp(A: Vec2d, min: number, max?: number): Vec2d; - // (undocumented) - clamp(min: number, max?: number): this; - // (undocumented) - static Clockwise(A: VecLike, B: VecLike, C: VecLike): boolean; - // (undocumented) - clone(): Vec2d; - // (undocumented) - static Cpr(A: VecLike, B: VecLike): number; - // (undocumented) - cpr(V: VecLike): number; - // (undocumented) - static Cross(A: VecLike, V: VecLike): Vec2d; - // (undocumented) - cross(V: VecLike): this; - // (undocumented) - static Dist(A: VecLike, B: VecLike): number; - // (undocumented) - dist(V: VecLike): number; - // (undocumented) - static Dist2(A: VecLike, B: VecLike): number; - // (undocumented) - static DistanceToLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp?: boolean): number; - // (undocumented) - distanceToLineSegment(A: VecLike, B: VecLike): number; - // (undocumented) - static DistanceToLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): number; - // (undocumented) - static Div(A: VecLike, t: number): Vec2d; - // (undocumented) - div(t: number): this; - // (undocumented) - static DivV(A: VecLike, B: VecLike): Vec2d; - // (undocumented) - divV(V: VecLike): this; - // (undocumented) - static Dpr(A: VecLike, B: VecLike): number; - // (undocumented) - dpr(V: VecLike): number; - // (undocumented) - static Equals(A: VecLike, B: VecLike): boolean; - // (undocumented) - equals(B: VecLike): boolean; - // (undocumented) - static EqualsXY(A: VecLike, x: number, y: number): boolean; - // (undocumented) - equalsXY(x: number, y: number): boolean; - // (undocumented) - static From({ x, y, z }: Vec2dModel): Vec2d; - // (undocumented) - static FromAngle(r: number, length?: number): Vec2d; - // (undocumented) - static FromArray(v: number[]): Vec2d; - // (undocumented) - static Len(A: VecLike): number; - // (undocumented) - len(): number; - // (undocumented) - static Len2(A: VecLike): number; - // (undocumented) - len2(): number; - // (undocumented) - static Lrp(A: VecLike, B: VecLike, t: number): Vec2d; - // (undocumented) - lrp(B: VecLike, t: number): Vec2d; - // (undocumented) - static Max(A: VecLike, B: VecLike): Vec2d; - // (undocumented) - static Med(A: VecLike, B: VecLike): Vec2d; - // (undocumented) - static Min(A: VecLike, B: VecLike): Vec2d; - // (undocumented) - static Mul(A: VecLike, t: number): Vec2d; - // (undocumented) - mul(t: number): this; - // (undocumented) - static MulV(A: VecLike, B: VecLike): Vec2d; - // (undocumented) - mulV(V: VecLike): this; - // (undocumented) - static NearestPointOnLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp?: boolean): Vec2d; - static NearestPointOnLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): Vec2d; - // (undocumented) - static Neg(A: VecLike): Vec2d; - // (undocumented) - neg(): this; - // (undocumented) - norm(): this; - // (undocumented) - static Nudge(A: VecLike, B: VecLike, distance: number): Vec2d; - // (undocumented) - nudge(B: VecLike, distance: number): this; - // (undocumented) - static Per(A: VecLike): Vec2d; - // (undocumented) - per(): this; - static PointsBetween(A: Vec2dModel, B: Vec2dModel, steps?: number): Vec2d[]; - // (undocumented) - get pressure(): number; - // (undocumented) - static Pry(A: VecLike, B: VecLike): number; - // (undocumented) - pry(V: VecLike): number; - // (undocumented) - static Rescale(A: VecLike, n: number): Vec2d; - // (undocumented) - static Rot(A: VecLike, r?: number): Vec2d; - // (undocumented) - rot(r: number): this; - // (undocumented) - static RotWith(A: VecLike, C: VecLike, r: number): Vec2d; - // (undocumented) - rotWith(C: VecLike, r: number): this; - // (undocumented) - static ScaleWithOrigin(A: VecLike, scale: number, origin: VecLike): Vec2d; - // (undocumented) - set(x?: number, y?: number, z?: number): this; - // (undocumented) - setTo({ x, y, z }: VecLike): this; - // (undocumented) - static Slope(A: VecLike, B: VecLike): number; - // (undocumented) - slope(B: VecLike): number; - // (undocumented) - static Snap(A: VecLike, step?: number): Vec2d; - // (undocumented) - static SnapToGrid(A: VecLike, gridSize?: number): Vec2d; - // (undocumented) - snapToGrid(gridSize: number): this; - // (undocumented) - static Sub(A: VecLike, B: VecLike): Vec2d; - // (undocumented) - sub(V: VecLike): this; - // (undocumented) - static SubScalar(A: VecLike, n: number): Vec2d; - // (undocumented) - subScalar(n: number): this; - // (undocumented) - static SubXY(A: VecLike, x: number, y: number): Vec2d; - // (undocumented) - subXY(x: number, y: number): this; - // (undocumented) - static Tan(A: VecLike, B: VecLike): Vec2d; - // (undocumented) - tan(V: VecLike): Vec2d; - // (undocumented) - static ToAngle(A: VecLike): number; - // (undocumented) - toAngle(): number; - // (undocumented) - static ToArray(A: VecLike): number[]; - // (undocumented) - toArray(): number[]; - // (undocumented) - static ToFixed(A: VecLike, n?: number): Vec2d; - // (undocumented) - toFixed(): Vec2d; - // (undocumented) - static ToJson(A: VecLike): { - x: number; - y: number; - z: number | undefined; - }; - // (undocumented) - toJson(): Vec2dModel; - // (undocumented) - static ToString(A: VecLike): string; - // (undocumented) - toString(): string; - // (undocumented) - static Uni(A: VecLike): Vec2d; - // (undocumented) - uni(): Vec2d; - // (undocumented) - x: number; - // (undocumented) - y: number; - // (undocumented) - z: number; -} - -// @public (undocumented) -export type VecLike = Vec2d | Vec2dModel; - -// (No @packageDocumentation comment for this package) - -``` diff --git a/packages/primitives/package.json b/packages/primitives/package.json deleted file mode 100644 index b07f5a815..000000000 --- a/packages/primitives/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "@tldraw/primitives", - "description": "A tiny little drawing app (primitives).", - "version": "2.0.0-alpha.14", - "packageManager": "yarn@3.5.0", - "author": { - "name": "tldraw GB Ltd.", - "email": "hello@tldraw.com" - }, - "homepage": "https://tldraw.dev", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/tldraw/tldraw" - }, - "bugs": { - "url": "https://github.com/tldraw/tldraw/issues" - }, - "keywords": [ - "tldraw", - "drawing", - "app", - "development", - "whiteboard", - "canvas", - "infinite" - ], - "/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish", - "main": "./src/index.ts", - "types": "./.tsbuild/index.d.ts", - "/* GOTCHA */": "files will include ./dist and index.d.ts by default, add any others you want to include in here", - "files": [], - "scripts": { - "test": "lazy inherit", - "test-coverage": "lazy inherit", - "build": "yarn run -T tsx ../../scripts/build-package.ts", - "build-api": "yarn run -T tsx ../../scripts/build-api.ts", - "prepack": "yarn run -T tsx ../../scripts/prepack.ts", - "postpack": "../../scripts/postpack.sh", - "pack-tarball": "yarn pack", - "lint": "yarn run -T tsx ../../scripts/lint.ts" - }, - "dependencies": { - "@tldraw/tlschema": "workspace:*" - }, - "jest": { - "preset": "config/jest/node", - "moduleNameMapper": { - "^~(.*)": "/src/$1" - }, - "transformIgnorePatterns": [ - "node_modules/(?!(nanoid|escape-string-regexp)/)" - ] - }, - "devDependencies": { - "lazyrepo": "0.0.0-alpha.27" - } -} diff --git a/packages/primitives/src/index.ts b/packages/primitives/src/index.ts deleted file mode 100644 index 4b3c14b83..000000000 --- a/packages/primitives/src/index.ts +++ /dev/null @@ -1,98 +0,0 @@ -export { - Box2d, - ROTATE_CORNER_TO_SELECTION_CORNER, - flipSelectionHandleX, - flipSelectionHandleY, - isSelectionCorner, - rotateSelectionHandle, - type RotateCorner, - type SelectionCorner, - type SelectionEdge, - type SelectionHandle, -} from './lib/Box2d' -export { CubicSegment2d, type CubicSegment2dModel } from './lib/CubicSegment2d' -export { CubicSpline2d } from './lib/CubicSpline2d' -export { LineSegment2d, type LineSegment2dModel } from './lib/LineSegment2d' -export { - Matrix2d, - decomposeMatrix2d, - type MatLike, - type Matrix2dModel, - type MatrixInfo, -} from './lib/Matrix2d' -export { Polyline2d } from './lib/Polyline2d' -export { Vec2d, type VecLike } from './lib/Vec2d' -export { EASINGS, type EasingType } from './lib/easings' -export { getStroke } from './lib/freehand/getStroke' -export { getStrokeOutlinePoints } from './lib/freehand/getStrokeOutlinePoints' -export { getStrokePoints } from './lib/freehand/getStrokePoints' -export { setStrokePointRadii } from './lib/freehand/setStrokePointRadii' -export { type StrokeOptions, type StrokePoint } from './lib/freehand/types' -export { - intersectCircleCircle, - intersectCirclePolygon, - intersectCirclePolyline, - intersectLineSegmentCircle, - intersectLineSegmentLineSegment, - intersectLineSegmentPolygon, - intersectLineSegmentPolyline, - intersectPolygonBounds, - intersectPolygonPolygon, - linesIntersect, - polygonsIntersect, -} from './lib/intersect' -export { - getDrawLinePathData, - getRoundedInkyPolygonPath, - getRoundedPolygonPoints, -} from './lib/polygon-helpers' -export { - EPSILON, - PI, - PI2, - SIN, - TAU, - angleDelta, - approximately, - areAnglesCompatible, - canonicalizeRotation, - clamp, - clampRadians, - degreesToRadians, - getArcLength, - getHeight, - getMaxX, - getMaxY, - getMidX, - getMidY, - getMinX, - getMinY, - getPointOnCircle, - getPolygonVertices, - getStarBounds, - getSweep, - getWidth, - isAngleBetween, - isSafeFloat, - lerpAngles, - longAngleDist, - perimeterOfEllipse, - pointInBounds, - pointInCircle, - pointInEllipse, - pointInPolygon, - pointInPolyline, - pointInRect, - pointNearToLineSegment, - pointNearToPolyline, - radiansToDegrees, - rangeIntersection, - rangesOverlap, - shortAngleDist, - simplify, - simplify2, - snapAngle, - toDomPrecision, - toFixed, - toPrecision, -} from './lib/utils' diff --git a/packages/primitives/tsconfig.json b/packages/primitives/tsconfig.json deleted file mode 100644 index 5ca07842c..000000000 --- a/packages/primitives/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "include": ["src"], - "exclude": ["node_modules", "dist", ".tsbuild*"], - "compilerOptions": { - "outDir": "./.tsbuild", - "rootDir": "src" - }, - "references": [{ "path": "../tlschema" }] -} diff --git a/packages/state/package.json b/packages/state/package.json index e5aec0631..f1ae3705a 100644 --- a/packages/state/package.json +++ b/packages/state/package.json @@ -49,7 +49,7 @@ "^~(.*)": "/src/$1" }, "transformIgnorePatterns": [ - "node_modules/(?!(nanoid|escape-string-regexp)/)" + "node_modules/(?!(nanoid)/)" ] }, "devDependencies": { diff --git a/packages/store/package.json b/packages/store/package.json index 23d3f4d85..bd6cffb54 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -44,7 +44,7 @@ "@tldraw/state": "workspace:*", "@tldraw/utils": "workspace:*", "lodash.isequal": "^4.5.0", - "nanoid": "4.0.2" + "nanoid": "^3.3.6" }, "devDependencies": { "@peculiar/webcrypto": "^1.4.0", @@ -61,7 +61,7 @@ "^~(.*)": "/src/$1" }, "transformIgnorePatterns": [ - "node_modules/(?!(nanoid|escape-string-regexp)/)" + "node_modules/(?!(nanoid)/)" ] } } diff --git a/packages/store/src/lib/test/testSchema.v0.ts b/packages/store/src/lib/test/testSchema.v0.ts index 7ac7a5cb1..bbba05257 100644 --- a/packages/store/src/lib/test/testSchema.v0.ts +++ b/packages/store/src/lib/test/testSchema.v0.ts @@ -1,4 +1,4 @@ -import { assert } from '@tldraw/utils' +import assert from 'assert' import { BaseRecord, RecordId } from '../BaseRecord' import { createRecordType } from '../RecordType' import { StoreSchema } from '../StoreSchema' diff --git a/packages/tldraw/api-report.md b/packages/tldraw/api-report.md index d8e7351e7..dcb827362 100644 --- a/packages/tldraw/api-report.md +++ b/packages/tldraw/api-report.md @@ -4,16 +4,826 @@ ```ts +/// + +import { BaseBoxShapeTool } from '@tldraw/editor'; +import { Box2d } from '@tldraw/editor'; +import { Editor } from '@tldraw/editor'; +import { EMBED_DEFINITIONS } from '@tldraw/editor'; +import { EmbedDefinition } from '@tldraw/editor'; +import { LANGUAGES } from '@tldraw/editor'; +import { Matrix2d } from '@tldraw/editor'; +import { Matrix2dModel } from '@tldraw/editor'; +import { MigrationFailureReason } from '@tldraw/editor'; +import { NamedExoticComponent } from 'react'; +import { default as React_2 } from 'react'; +import * as React_3 from 'react'; +import { ReactNode } from 'react'; +import { RecursivePartial } from '@tldraw/editor'; +import { Result } from '@tldraw/editor'; +import { SelectionCorner } from '@tldraw/editor'; +import { SelectionEdge } from '@tldraw/editor'; +import { SelectionHandle } from '@tldraw/editor'; +import { SerializedSchema } from '@tldraw/editor'; +import { ShapeUtil } from '@tldraw/editor'; +import { StateNode } from '@tldraw/editor'; +import { TLAnyShapeUtilConstructor } from '@tldraw/editor'; +import { TLArrowShape } from '@tldraw/editor'; +import { TLBaseEventInfo } from '@tldraw/editor'; +import { TLCancelEvent } from '@tldraw/editor'; +import { TLClickEvent } from '@tldraw/editor'; +import { TLClickEventInfo } from '@tldraw/editor'; +import { TLDefaultSizeStyle } from '@tldraw/editor'; import { TldrawEditorProps } from '@tldraw/editor'; -import { TldrawUiProps } from '@tldraw/ui'; +import { TLDrawShape } from '@tldraw/editor'; +import { TLDrawShapeSegment } from '@tldraw/editor'; +import { TLEnterEventHandler } from '@tldraw/editor'; +import { TLEventHandlers } from '@tldraw/editor'; +import { TLExitEventHandler } from '@tldraw/editor'; +import { TLHandle } from '@tldraw/editor'; +import { TLHighlightShape } from '@tldraw/editor'; +import { TLInterruptEvent } from '@tldraw/editor'; +import { TLKeyboardEvent } from '@tldraw/editor'; +import { TLKeyboardEventInfo } from '@tldraw/editor'; +import { TLLanguage } from '@tldraw/editor'; +import { TLLineShape } from '@tldraw/editor'; +import { TLPointerEvent } from '@tldraw/editor'; +import { TLPointerEventInfo } from '@tldraw/editor'; +import { TLPointerEventName } from '@tldraw/editor'; +import { TLRotationSnapshot } from '@tldraw/editor'; +import { TLSchema } from '@tldraw/editor'; +import { TLScribble } from '@tldraw/editor'; +import { TLSelectionHandle } from '@tldraw/editor'; +import { TLShape } from '@tldraw/editor'; +import { TLShapeId } from '@tldraw/editor'; +import { TLStore } from '@tldraw/editor'; +import { TLTextShape } from '@tldraw/editor'; +import { TLTickEvent } from '@tldraw/editor'; +import { TLUnknownShape } from '@tldraw/editor'; +import { UnknownRecord } from '@tldraw/editor'; +import { Vec2d } from '@tldraw/editor'; +import { Vec2dModel } from '@tldraw/editor'; +import { VecLike } from '@tldraw/editor'; // @public (undocumented) -export function Tldraw(props: TldrawEditorProps & TldrawUiProps): JSX.Element; +export const ACCEPTED_IMG_TYPE: string[]; + +// @internal (undocumented) +export function AssetUrlsProvider({ assetUrls, children, }: { + assetUrls: TLUiAssetUrls; + children: React.ReactNode; +}): JSX.Element; + +// @public (undocumented) +function Body_2({ className, children, style, }: { + className?: string; + children: any; + style?: React.CSSProperties; +}): JSX.Element; + +// @public (undocumented) +export function BreakPointProvider({ children }: { + children: any; +}): JSX.Element; + +// @internal (undocumented) +export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocument): void; + +// @public (undocumented) +export const Button: React_3.ForwardRefExoticComponent>; + +// @public (undocumented) +function CheckboxItem({ children, onSelect, ...rest }: DropdownMenuCheckboxItemProps): JSX.Element; + +// @public (undocumented) +function CloseButton(): JSX.Element; + +// @public (undocumented) +export function compactMenuItems(arr: T[]): Exclude[]; + +// @public (undocumented) +function Content({ side, align, sideOffset, alignOffset, children, }: { + children: any; + alignOffset?: number; + sideOffset?: number; + align?: 'center' | 'end' | 'start'; + side?: 'bottom' | 'left' | 'right' | 'top'; +}): JSX.Element; + +// @public (undocumented) +export const ContextMenu: ({ children }: { + children: any; +}) => JSX.Element; + +// @public (undocumented) +export const defaultShapeTools: (typeof ArrowShapeTool | typeof DrawShapeTool | typeof FrameShapeTool | typeof GeoShapeTool | typeof LineShapeTool | typeof NoteShapeTool | typeof TextShapeTool)[]; + +// @public (undocumented) +export const defaultShapeUtils: TLAnyShapeUtilConstructor[]; + +// @public (undocumented) +export const defaultTools: (typeof EraserTool | typeof HandTool | typeof LaserTool | typeof SelectTool | typeof ZoomTool)[]; + +declare namespace Dialog { + export { + Header, + Title, + CloseButton, + Body_2 as Body, + Footer + } +} +export { Dialog } + +declare namespace DropdownMenu { + export { + Root, + Trigger, + Content, + Sub, + SubTrigger, + SubContent, + Group, + Indicator, + Item, + CheckboxItem, + RadioItem, + DropdownMenuItemProps, + DropdownMenuCheckboxItemProps + } +} +export { DropdownMenu } + +// @public (undocumented) +interface DropdownMenuCheckboxItemProps { + // (undocumented) + checked?: boolean; + // (undocumented) + children: any; + // (undocumented) + disabled?: boolean; + // (undocumented) + onSelect?: (e: Event) => void; + // (undocumented) + title: string; +} + +// @public (undocumented) +interface DropdownMenuItemProps extends TLUiButtonProps { + // (undocumented) + noClose?: boolean; +} + +// @public (undocumented) +export function findMenuItem(menu: TLUiMenuSchema, path: string[]): TLUiMenuChild; + +// @public (undocumented) +function Footer({ className, children }: { + className?: string; + children: any; +}): JSX.Element; + +// @public +export function getEmbedInfo(inputUrl: string): TLEmbedResult; + +// @public (undocumented) +export function getFileMetaData(file: File): Promise<{ + isAnimated: boolean; +}>; + +// @public +export function getImageSizeFromSrc(dataURL: string): Promise<{ + w: number; + h: number; +}>; + +// @public +export function getVideoSizeFromSrc(src: string): Promise<{ + w: number; + h: number; +}>; + +// @public (undocumented) +function Group({ children, size, }: { + children: any; + size?: 'medium' | 'small' | 'tiny' | 'wide'; +}): JSX.Element; + +// @public (undocumented) +function Header({ className, children }: { + className?: string; + children: any; +}): JSX.Element; + +// @public (undocumented) +export const Icon: NamedExoticComponent; + +// @public (undocumented) +function Indicator(): JSX.Element; + +// @public (undocumented) +export const Input: React_3.ForwardRefExoticComponent>; + +// @public (undocumented) +export const isImage: (ext: string) => boolean; + +// @public (undocumented) +function Item({ noClose, ...props }: DropdownMenuItemProps): JSX.Element; + +// @internal (undocumented) +export interface LegacyTldrawDocument { + // (undocumented) + assets: TDAssets; + // (undocumented) + id: string; + // (undocumented) + name: string; + // (undocumented) + pages: Record; + // (undocumented) + pageStates: Record; + // (undocumented) + version: number; +} + +// @public (undocumented) +export function menuCustom(id: string, opts?: Partial<{ + readonlyOk: boolean; + disabled: boolean; +}>): { + id: string; + type: "custom"; + disabled: boolean; + readonlyOk: boolean; +}; + +// @public (undocumented) +export function menuGroup(id: string, ...children: (false | null | TLUiMenuChild)[]): null | TLUiMenuGroup; + +// @public (undocumented) +export function menuItem(actionItem: TLUiActionItem | TLUiToolItem, opts?: Partial<{ + checked: boolean; + disabled: boolean; +}>): TLUiMenuItem; + +// @public (undocumented) +export function menuSubmenu(id: string, label: TLUiTranslationKey, ...children: (false | null | TLUiMenuChild)[]): null | TLUiSubMenu; + +// @internal (undocumented) +export function parseAndLoadDocument(editor: Editor, document: string, msg: (id: TLUiTranslationKey) => string, addToast: TLUiToastsContextType['addToast'], onV1FileLoad?: () => void, forceDarkMode?: boolean): Promise; + +// @public (undocumented) +export function parseTldrawJsonFile({ json, schema, }: { + schema: TLSchema; + json: string; +}): Result; + +// @public (undocumented) +function RadioItem({ children, onSelect, ...rest }: DropdownMenuCheckboxItemProps): JSX.Element; + +// @public (undocumented) +function Root({ id, children, modal, }: { + id: string; + children: any; + modal?: boolean; +}): JSX.Element; + +// @public (undocumented) +export function serializeTldrawJson(store: TLStore): Promise; + +// @public (undocumented) +export function serializeTldrawJsonBlob(store: TLStore): Promise; + +// @internal (undocumented) +export function setDefaultUiAssetUrls(urls: TLUiAssetUrls): void; + +// @public (undocumented) +function Sub({ id, children }: { + id: string; + children: any; +}): JSX.Element; + +// @public (undocumented) +function SubContent({ alignOffset, sideOffset, children, }: { + alignOffset?: number; + sideOffset?: number; + children: any; +}): JSX.Element; + +// @public (undocumented) +function SubTrigger({ label, 'data-testid': testId, 'data-direction': dataDirection, }: { + label: TLUiTranslationKey; + 'data-testid'?: string; + 'data-direction'?: 'left' | 'right'; +}): JSX.Element; + +// @public (undocumented) +function Title({ className, children }: { + className?: string; + children: any; +}): JSX.Element; + +// @public (undocumented) +export function Tldraw(props: TldrawEditorProps & TldrawUiProps & { + assetUrls?: RecursivePartial; +}): JSX.Element; + +// @public (undocumented) +export const TLDRAW_FILE_EXTENSION: ".tldr"; + +// @public (undocumented) +export interface TldrawFile { + // (undocumented) + records: UnknownRecord[]; + // (undocumented) + schema: SerializedSchema; + // (undocumented) + tldrawFileFormatVersion: number; +} + +// @public (undocumented) +export const TldrawUi: React_2.NamedExoticComponent; + +// @public +export interface TldrawUiBaseProps { + children?: ReactNode; + hideUi?: boolean; + renderDebugMenuItems?: () => React_2.ReactNode; + shareZone?: ReactNode; + topZone?: ReactNode; +} + +// @public (undocumented) +export function TldrawUiContextProvider({ overrides, assetUrls, onUiEvent, children, }: TldrawUiContextProviderProps): JSX.Element; + +// @public +export interface TldrawUiContextProviderProps { + assetUrls?: RecursivePartial; + children?: any; + onUiEvent?: TLUiEventHandler; + overrides?: TLUiOverrides | TLUiOverrides[]; +} + +// @public +export type TldrawUiProps = TldrawUiBaseProps & TldrawUiContextProviderProps; + +// @public (undocumented) +export interface TLUiActionItem { + // (undocumented) + checkbox?: boolean; + // (undocumented) + contextMenuLabel?: TLUiTranslationKey; + // (undocumented) + icon?: TLUiIconType; + // (undocumented) + id: string; + // (undocumented) + kbd?: string; + // (undocumented) + label?: TLUiTranslationKey; + // (undocumented) + menuLabel?: TLUiTranslationKey; + // (undocumented) + onSelect: (source: TLUiEventSource) => Promise | void; + // (undocumented) + readonlyOk: boolean; + // (undocumented) + shortcutsLabel?: TLUiTranslationKey; + // (undocumented) + title?: string; +} + +// @public (undocumented) +export type TLUiActionsContextType = Record; + +// @public (undocumented) +export type TLUiActionsMenuSchemaContextType = TLUiMenuSchema; + +// @public (undocumented) +export interface TLUiButtonProps extends React_3.HTMLAttributes { + // (undocumented) + disabled?: boolean; + // (undocumented) + icon?: TLUiIconType; + // (undocumented) + iconLeft?: TLUiIconType; + // (undocumented) + invertIcon?: boolean; + // (undocumented) + isChecked?: boolean; + // (undocumented) + kbd?: string; + // (undocumented) + label?: TLUiTranslationKey; + // (undocumented) + loading?: boolean; + // (undocumented) + smallIcon?: boolean; + // (undocumented) + spinner?: boolean; + // (undocumented) + type?: 'danger' | 'normal' | 'primary'; +} + +// @public (undocumented) +export interface TLUiContextMenuProps { + // (undocumented) + children: any; +} + +// @public (undocumented) +export type TLUiContextTTLUiMenuSchemaContextType = TLUiMenuSchema; + +// @public (undocumented) +export type TLUiCustomMenuItem = { + id: string; + type: 'custom'; + disabled: boolean; + readonlyOk: boolean; +}; + +// @public (undocumented) +export interface TLUiDialog { + // (undocumented) + component: (props: TLUiDialogProps) => any; + // (undocumented) + id: string; + // (undocumented) + onClose?: () => void; +} + +// @public (undocumented) +export interface TLUiDialogProps { + // (undocumented) + onClose: () => void; +} + +// @public (undocumented) +export type TLUiDialogsContextType = { + addDialog: (dialog: Omit & { + id?: string; + }) => string; + removeDialog: (id: string) => string; + updateDialog: (id: string, newDialogData: Partial) => string; + clearDialogs: () => void; + dialogs: TLUiDialog[]; +}; + +// @public (undocumented) +export type TLUiEventContextType = TLUiEventHandler; + +// @public (undocumented) +export type TLUiEventHandler = (name: T, data: Join<{ + source: TLUiEventSource; +}, TLUiEventMap[T]>) => void; + +// @public (undocumented) +export type TLUiEventSource = 'actions-menu' | 'context-menu' | 'debug-panel' | 'dialog' | 'export-menu' | 'help-menu' | 'helper-buttons' | 'kbd' | 'menu' | 'navigation-zone' | 'page-menu' | 'people-menu' | 'quick-actions' | 'share-menu' | 'toolbar' | 'unknown' | 'zoom-menu'; + +// @public (undocumented) +export type TLUiHelpMenuSchemaContextType = TLUiMenuSchema; + +// @public (undocumented) +export interface TLUiIconProps extends React.HTMLProps { + // (undocumented) + children?: undefined; + // (undocumented) + color?: string; + // (undocumented) + crossOrigin?: 'anonymous' | 'use-credentials'; + // (undocumented) + icon: TLUiIconType; + // (undocumented) + invertIcon?: boolean; + // (undocumented) + small?: boolean; +} + +// @public (undocumented) +export type TLUiIconType = 'align-bottom-center' | 'align-bottom-left' | 'align-bottom-right' | 'align-bottom' | 'align-center-center' | 'align-center-horizontal' | 'align-center-left' | 'align-center-right' | 'align-center-vertical' | 'align-left' | 'align-right' | 'align-top-center' | 'align-top-left' | 'align-top-right' | 'align-top' | 'arrow-left' | 'arrowhead-arrow' | 'arrowhead-bar' | 'arrowhead-diamond' | 'arrowhead-dot' | 'arrowhead-none' | 'arrowhead-square' | 'arrowhead-triangle-inverted' | 'arrowhead-triangle' | 'aspect-ratio' | 'avatar' | 'blob' | 'bring-forward' | 'bring-to-front' | 'check' | 'checkbox-checked' | 'checkbox-empty' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'chevrons-ne' | 'chevrons-sw' | 'clipboard-copied' | 'clipboard-copy' | 'code' | 'collab' | 'color' | 'comment' | 'cross-2' | 'cross' | 'dash-dashed' | 'dash-dotted' | 'dash-draw' | 'dash-solid' | 'discord' | 'distribute-horizontal' | 'distribute-vertical' | 'dot' | 'dots-horizontal' | 'dots-vertical' | 'drag-handle-dots' | 'duplicate' | 'edit' | 'external-link' | 'file' | 'fill-none' | 'fill-pattern' | 'fill-semi' | 'fill-solid' | 'follow' | 'following' | 'font-draw' | 'font-mono' | 'font-sans' | 'font-serif' | 'geo-arrow-down' | 'geo-arrow-left' | 'geo-arrow-right' | 'geo-arrow-up' | 'geo-check-box' | 'geo-cloud' | 'geo-diamond' | 'geo-ellipse' | 'geo-hexagon' | 'geo-octagon' | 'geo-oval' | 'geo-pentagon' | 'geo-rectangle' | 'geo-rhombus-2' | 'geo-rhombus' | 'geo-star' | 'geo-trapezoid' | 'geo-triangle' | 'geo-x-box' | 'github' | 'group' | 'hidden' | 'image' | 'info-circle' | 'leading' | 'link' | 'lock-small' | 'lock' | 'menu' | 'minus' | 'mixed' | 'pack' | 'page' | 'plus' | 'question-mark-circle' | 'question-mark' | 'redo' | 'reset-zoom' | 'rotate-ccw' | 'rotate-cw' | 'ruler' | 'search' | 'send-backward' | 'send-to-back' | 'settings-horizontal' | 'settings-vertical-1' | 'settings-vertical' | 'share-1' | 'share-2' | 'size-extra-large' | 'size-large' | 'size-medium' | 'size-small' | 'spline-cubic' | 'spline-line' | 'stack-horizontal' | 'stack-vertical' | 'stretch-horizontal' | 'stretch-vertical' | 'text-align-center' | 'text-align-justify' | 'text-align-left' | 'text-align-right' | 'tool-arrow' | 'tool-embed' | 'tool-eraser' | 'tool-frame' | 'tool-hand' | 'tool-highlight' | 'tool-laser' | 'tool-line' | 'tool-media' | 'tool-note' | 'tool-pencil' | 'tool-pointer' | 'tool-text' | 'trash' | 'triangle-down' | 'triangle-up' | 'twitter' | 'undo' | 'ungroup' | 'unlock-small' | 'unlock' | 'vertical-align-center' | 'vertical-align-end' | 'vertical-align-start' | 'visible' | 'warning-triangle' | 'zoom-in' | 'zoom-out'; + +// @public (undocumented) +export interface TLUiInputProps { + // (undocumented) + autofocus?: boolean; + // (undocumented) + autoselect?: boolean; + // (undocumented) + children?: any; + // (undocumented) + className?: string; + // (undocumented) + defaultValue?: string; + // (undocumented) + disabled?: boolean; + // (undocumented) + icon?: TLUiIconType; + // (undocumented) + iconLeft?: TLUiIconType; + // (undocumented) + label?: TLUiTranslationKey; + // (undocumented) + onBlur?: (value: string) => void; + // (undocumented) + onCancel?: (value: string) => void; + // (undocumented) + onComplete?: (value: string) => void; + // (undocumented) + onValueChange?: (value: string) => void; + // (undocumented) + placeholder?: string; + shouldManuallyMaintainScrollPositionWhenFocused?: boolean; + // (undocumented) + value?: string; +} + +// @public (undocumented) +export type TLUiKeyboardShortcutsSchemaContextType = TLUiMenuSchema; + +// @public (undocumented) +export type TLUiKeyboardShortcutsSchemaProviderProps = { + overrides?: (editor: Editor, schema: TLUiKeyboardShortcutsSchemaContextType, more: { + tools: TLUiToolsContextType; + actions: TLUiActionsContextType; + }) => TLUiKeyboardShortcutsSchemaContextType; + children: any; +}; + +// @public (undocumented) +export type TLUiMenuChild = TLUiCustomMenuItem | TLUiMenuGroup | TLUiMenuItem | TLUiSubMenu; + +// @public (undocumented) +export type TLUiMenuGroup = { + id: string; + type: 'group'; + checkbox: boolean; + disabled: boolean; + readonlyOk: boolean; + children: TLUiMenuChild[]; +}; + +// @public (undocumented) +export type TLUiMenuItem = { + id: string; + type: 'item'; + readonlyOk: boolean; + actionItem: TLUiActionItem; + disabled: boolean; + checked: boolean; +}; + +// @public (undocumented) +export type TLUiMenuSchema = (TLUiCustomMenuItem | TLUiMenuGroup | TLUiMenuItem)[]; + +// @public (undocumented) +export type TLUiMenuSchemaContextType = TLUiMenuSchema; + +// @public (undocumented) +export type TLUiMenuSchemaProviderProps = { + overrides?: (editor: Editor, schema: TLUiMenuSchemaContextType, helpers: { + actions: ReturnType; + noneSelected: boolean; + oneSelected: boolean; + twoSelected: boolean; + threeSelected: boolean; + }) => TLUiMenuSchemaContextType; + children: any; +}; + +// @public (undocumented) +export interface TLUiOverrides { + // (undocumented) + actions?: WithDefaultHelpers>; + // (undocumented) + actionsMenu?: WithDefaultHelpers>; + // (undocumented) + contextMenu?: WithDefaultHelpers>; + // (undocumented) + helpMenu?: WithDefaultHelpers>; + // (undocumented) + keyboardShortcutsMenu?: WithDefaultHelpers>; + // (undocumented) + menu?: WithDefaultHelpers>; + // (undocumented) + toolbar?: WithDefaultHelpers>; + // (undocumented) + tools?: WithDefaultHelpers>; + // (undocumented) + translations?: TLUiTranslationProviderProps['overrides']; +} + +// @public (undocumented) +export type TLUiSubMenu = { + id: string; + type: 'submenu'; + label: TLUiTranslationKey; + disabled: boolean; + readonlyOk: boolean; + children: TLUiMenuChild[]; +}; + +// @public (undocumented) +export interface TLUiToast { + // (undocumented) + actions?: TLUiToastAction[]; + // (undocumented) + closeLabel?: string; + // (undocumented) + description?: string; + // (undocumented) + icon?: string; + // (undocumented) + id: string; + // (undocumented) + keepOpen?: boolean; + // (undocumented) + title?: string; +} + +// @public (undocumented) +export interface TLUiToastAction { + // (undocumented) + label: string; + // (undocumented) + onClick: () => void; + // (undocumented) + type: 'primary' | 'secondary' | 'warn'; +} + +// @public (undocumented) +export type TLUiToastsContextType = { + addToast: (toast: Omit & { + id?: string; + }) => string; + removeToast: (id: TLUiToast['id']) => string; + clearToasts: () => void; + toasts: TLUiToast[]; +}; + +// @public (undocumented) +export type TLUiToolbarItem = { + id: string; + type: 'item'; + readonlyOk: boolean; + toolItem: TLUiToolItem; +}; + +// @public (undocumented) +export type TLUiToolbarSchemaContextType = TLUiToolbarItem[]; + +// @public (undocumented) +export interface TLUiToolItem { + // (undocumented) + icon: TLUiIconType; + // (undocumented) + id: string; + // (undocumented) + kbd?: string; + // (undocumented) + label: TLUiTranslationKey; + // (undocumented) + meta?: { + [key: string]: any; + }; + // (undocumented) + onSelect: (source: TLUiEventSource) => void; + // (undocumented) + readonlyOk: boolean; + // (undocumented) + shortcutsLabel?: TLUiTranslationKey; +} + +// @public (undocumented) +export type TLUiToolsContextType = Record; + +// @public (undocumented) +export type TLUiToolsProviderProps = { + overrides?: (editor: Editor, tools: TLUiToolsContextType, helpers: { + insertMedia: () => void; + }) => TLUiToolsContextType; + children: any; +}; + +// @public (undocumented) +export type TLUiTranslation = { + readonly locale: string; + readonly label: string; + readonly messages: Record; +}; + +// @public (undocumented) +export type TLUiTranslationContextType = TLUiTranslation; + +// @public (undocumented) +export type TLUiTranslationKey = 'action.align-bottom' | 'action.align-center-horizontal.short' | 'action.align-center-horizontal' | 'action.align-center-vertical.short' | 'action.align-center-vertical' | 'action.align-left' | 'action.align-right' | 'action.align-top' | 'action.back-to-content' | 'action.bring-forward' | 'action.bring-to-front' | 'action.convert-to-bookmark' | 'action.convert-to-embed' | 'action.copy-as-json.short' | 'action.copy-as-json' | 'action.copy-as-png.short' | 'action.copy-as-png' | 'action.copy-as-svg.short' | 'action.copy-as-svg' | 'action.copy' | 'action.cut' | 'action.delete' | 'action.distribute-horizontal.short' | 'action.distribute-horizontal' | 'action.distribute-vertical.short' | 'action.distribute-vertical' | 'action.duplicate' | 'action.edit-link' | 'action.exit-pen-mode' | 'action.export-as-json.short' | 'action.export-as-json' | 'action.export-as-png.short' | 'action.export-as-png' | 'action.export-as-svg.short' | 'action.export-as-svg' | 'action.flip-horizontal.short' | 'action.flip-horizontal' | 'action.flip-vertical.short' | 'action.flip-vertical' | 'action.fork-project' | 'action.group' | 'action.insert-embed' | 'action.insert-media' | 'action.leave-shared-project' | 'action.new-project' | 'action.new-shared-project' | 'action.open-cursor-chat' | 'action.open-embed-link' | 'action.open-file' | 'action.pack' | 'action.paste' | 'action.print' | 'action.redo' | 'action.rotate-ccw' | 'action.rotate-cw' | 'action.save-copy' | 'action.select-all' | 'action.select-none' | 'action.send-backward' | 'action.send-to-back' | 'action.share-project' | 'action.stack-horizontal.short' | 'action.stack-horizontal' | 'action.stack-vertical.short' | 'action.stack-vertical' | 'action.stop-following' | 'action.stretch-horizontal.short' | 'action.stretch-horizontal' | 'action.stretch-vertical.short' | 'action.stretch-vertical' | 'action.toggle-auto-size' | 'action.toggle-dark-mode.menu' | 'action.toggle-dark-mode' | 'action.toggle-debug-mode.menu' | 'action.toggle-debug-mode' | 'action.toggle-focus-mode.menu' | 'action.toggle-focus-mode' | 'action.toggle-grid.menu' | 'action.toggle-grid' | 'action.toggle-lock' | 'action.toggle-reduce-motion.menu' | 'action.toggle-reduce-motion' | 'action.toggle-snap-mode.menu' | 'action.toggle-snap-mode' | 'action.toggle-tool-lock.menu' | 'action.toggle-tool-lock' | 'action.toggle-transparent.context-menu' | 'action.toggle-transparent.menu' | 'action.toggle-transparent' | 'action.undo' | 'action.ungroup' | 'action.zoom-in' | 'action.zoom-out' | 'action.zoom-to-100' | 'action.zoom-to-fit' | 'action.zoom-to-selection' | 'actions-menu.title' | 'align-style.end' | 'align-style.justify' | 'align-style.middle' | 'align-style.start' | 'arrowheadEnd-style.arrow' | 'arrowheadEnd-style.bar' | 'arrowheadEnd-style.diamond' | 'arrowheadEnd-style.dot' | 'arrowheadEnd-style.inverted' | 'arrowheadEnd-style.none' | 'arrowheadEnd-style.pipe' | 'arrowheadEnd-style.square' | 'arrowheadEnd-style.triangle' | 'arrowheadStart-style.arrow' | 'arrowheadStart-style.bar' | 'arrowheadStart-style.diamond' | 'arrowheadStart-style.dot' | 'arrowheadStart-style.inverted' | 'arrowheadStart-style.none' | 'arrowheadStart-style.pipe' | 'arrowheadStart-style.square' | 'arrowheadStart-style.triangle' | 'color-style.black' | 'color-style.blue' | 'color-style.green' | 'color-style.grey' | 'color-style.light-blue' | 'color-style.light-green' | 'color-style.light-red' | 'color-style.light-violet' | 'color-style.orange' | 'color-style.red' | 'color-style.violet' | 'color-style.yellow' | 'context-menu.arrange' | 'context-menu.copy-as' | 'context-menu.export-as' | 'context-menu.move-to-page' | 'context-menu.reorder' | 'context.pages.new-page' | 'cursor-chat.type-to-chat' | 'dash-style.dashed' | 'dash-style.dotted' | 'dash-style.draw' | 'dash-style.solid' | 'debug-panel.more' | 'edit-link-dialog.cancel' | 'edit-link-dialog.clear' | 'edit-link-dialog.detail' | 'edit-link-dialog.invalid-url' | 'edit-link-dialog.save' | 'edit-link-dialog.title' | 'edit-link-dialog.url' | 'edit-pages-dialog.move-down' | 'edit-pages-dialog.move-up' | 'embed-dialog.back' | 'embed-dialog.cancel' | 'embed-dialog.create' | 'embed-dialog.instruction' | 'embed-dialog.invalid-url' | 'embed-dialog.title' | 'embed-dialog.url' | 'file-system.confirm-clear.cancel' | 'file-system.confirm-clear.continue' | 'file-system.confirm-clear.description' | 'file-system.confirm-clear.dont-show-again' | 'file-system.confirm-clear.title' | 'file-system.confirm-open.cancel' | 'file-system.confirm-open.description' | 'file-system.confirm-open.dont-show-again' | 'file-system.confirm-open.open' | 'file-system.confirm-open.title' | 'file-system.file-open-error.file-format-version-too-new' | 'file-system.file-open-error.generic-corrupted-file' | 'file-system.file-open-error.not-a-tldraw-file' | 'file-system.file-open-error.title' | 'file-system.shared-document-file-open-error.description' | 'file-system.shared-document-file-open-error.title' | 'fill-style.none' | 'fill-style.pattern' | 'fill-style.semi' | 'fill-style.solid' | 'focus-mode.toggle-focus-mode' | 'font-style.draw' | 'font-style.mono' | 'font-style.sans' | 'font-style.serif' | 'geo-style.arrow-down' | 'geo-style.arrow-left' | 'geo-style.arrow-right' | 'geo-style.arrow-up' | 'geo-style.check-box' | 'geo-style.cloud' | 'geo-style.diamond' | 'geo-style.ellipse' | 'geo-style.hexagon' | 'geo-style.octagon' | 'geo-style.oval' | 'geo-style.pentagon' | 'geo-style.rectangle' | 'geo-style.rhombus-2' | 'geo-style.rhombus' | 'geo-style.star' | 'geo-style.trapezoid' | 'geo-style.triangle' | 'geo-style.x-box' | 'help-menu.about' | 'help-menu.discord' | 'help-menu.github' | 'help-menu.keyboard-shortcuts' | 'help-menu.title' | 'help-menu.twitter' | 'home-project-dialog.description' | 'home-project-dialog.ok' | 'home-project-dialog.title' | 'menu.copy-as' | 'menu.edit' | 'menu.export-as' | 'menu.file' | 'menu.language' | 'menu.preferences' | 'menu.title' | 'menu.view' | 'navigation-zone.toggle-minimap' | 'navigation-zone.zoom' | 'opacity-style.0.1' | 'opacity-style.0.25' | 'opacity-style.0.5' | 'opacity-style.0.75' | 'opacity-style.1' | 'page-menu.create-new-page' | 'page-menu.edit-done' | 'page-menu.edit-start' | 'page-menu.go-to-page' | 'page-menu.max-page-count-reached' | 'page-menu.new-page-initial-name' | 'page-menu.submenu.delete' | 'page-menu.submenu.duplicate-page' | 'page-menu.submenu.move-down' | 'page-menu.submenu.move-up' | 'page-menu.submenu.rename' | 'page-menu.submenu.title' | 'page-menu.title' | 'people-menu.change-color' | 'people-menu.change-name' | 'people-menu.follow' | 'people-menu.following' | 'people-menu.invite' | 'people-menu.leading' | 'people-menu.title' | 'people-menu.user' | 'rename-project-dialog.cancel' | 'rename-project-dialog.rename' | 'rename-project-dialog.title' | 'share-menu.copy-link-note' | 'share-menu.copy-link' | 'share-menu.copy-readonly-link-note' | 'share-menu.copy-readonly-link' | 'share-menu.create-snapshot-link' | 'share-menu.default-project-name' | 'share-menu.fork-note' | 'share-menu.offline-note' | 'share-menu.project-too-large' | 'share-menu.readonly-link' | 'share-menu.save-note' | 'share-menu.share-project' | 'share-menu.snapshot-link-note' | 'share-menu.title' | 'share-menu.upload-failed' | 'sharing.confirm-leave.cancel' | 'sharing.confirm-leave.description' | 'sharing.confirm-leave.dont-show-again' | 'sharing.confirm-leave.leave' | 'sharing.confirm-leave.title' | 'shortcuts-dialog.collaboration' | 'shortcuts-dialog.edit' | 'shortcuts-dialog.file' | 'shortcuts-dialog.preferences' | 'shortcuts-dialog.title' | 'shortcuts-dialog.tools' | 'shortcuts-dialog.transform' | 'shortcuts-dialog.view' | 'size-style.l' | 'size-style.m' | 'size-style.s' | 'size-style.xl' | 'spline-style.cubic' | 'spline-style.line' | 'style-panel.align' | 'style-panel.arrowhead-end' | 'style-panel.arrowhead-start' | 'style-panel.arrowheads' | 'style-panel.color' | 'style-panel.dash' | 'style-panel.fill' | 'style-panel.font' | 'style-panel.geo' | 'style-panel.mixed' | 'style-panel.opacity' | 'style-panel.position' | 'style-panel.size' | 'style-panel.spline' | 'style-panel.title' | 'style-panel.vertical-align' | 'toast.close' | 'toast.error.copy-fail.desc' | 'toast.error.copy-fail.title' | 'toast.error.export-fail.desc' | 'toast.error.export-fail.title' | 'tool-panel.drawing' | 'tool-panel.more' | 'tool-panel.shapes' | 'tool.arrow-down' | 'tool.arrow-left' | 'tool.arrow-right' | 'tool.arrow-up' | 'tool.arrow' | 'tool.asset' | 'tool.check-box' | 'tool.cloud' | 'tool.diamond' | 'tool.draw' | 'tool.ellipse' | 'tool.embed' | 'tool.eraser' | 'tool.frame' | 'tool.hand' | 'tool.hexagon' | 'tool.highlight' | 'tool.laser' | 'tool.line' | 'tool.note' | 'tool.octagon' | 'tool.oval' | 'tool.pentagon' | 'tool.rectangle' | 'tool.rhombus' | 'tool.select' | 'tool.star' | 'tool.text' | 'tool.trapezoid' | 'tool.triangle' | 'tool.x-box' | 'vscode.file-open.backup-failed' | 'vscode.file-open.backup-saved' | 'vscode.file-open.backup' | 'vscode.file-open.desc' | 'vscode.file-open.dont-show-again' | 'vscode.file-open.open'; + +// @public (undocumented) +export function toolbarItem(toolItem: TLUiToolItem): TLUiToolbarItem; + +// @public (undocumented) +function Trigger({ children, 'data-testid': testId, }: { + children: any; + 'data-testid'?: string; +}): JSX.Element; + +// @public (undocumented) +export const truncateStringWithEllipsis: (str: string, maxLength: number) => string; + +// @public (undocumented) +export function useActions(): TLUiActionsContextType; + +// @public (undocumented) +export function useActionsMenuSchema(): TLUiMenuSchema; + +// @internal (undocumented) +export function useAssetUrls(): TLUiAssetUrls; + +// @public (undocumented) +export function useBreakpoint(): number; + +// @public (undocumented) +export function useCanRedo(): boolean; + +// @public (undocumented) +export function useCanUndo(): boolean; + +// @public (undocumented) +export function useContextMenuSchema(): TLUiMenuSchema; + +// @public (undocumented) +export function useCopyAs(): (ids?: TLShapeId[], format?: TLCopyType) => void; + +// @public (undocumented) +export function useDefaultHelpers(): { + addToast: (toast: Omit & { + id?: string | undefined; + }) => string; + removeToast: (id: string) => string; + clearToasts: () => void; + addDialog: (dialog: Omit & { + id?: string | undefined; + }) => string; + clearDialogs: () => void; + removeDialog: (id: string) => string; + updateDialog: (id: string, newDialogData: Partial) => string; + msg: (id: TLUiTranslationKey) => string; + isMobile: boolean; +}; + +// @public (undocumented) +export function useDialogs(): TLUiDialogsContextType; + +// @public (undocumented) +export function useEvents(): TLUiEventContextType; + +// @public (undocumented) +export function useExportAs(): (ids?: TLShapeId[], format?: TLExportType) => Promise; + +// @public (undocumented) +export function useHelpMenuSchema(): TLUiMenuSchema; + +// @public (undocumented) +export function useKeyboardShortcuts(): void; + +// @public (undocumented) +export function useKeyboardShortcutsSchema(): TLUiKeyboardShortcutsSchemaContextType; + +// @public (undocumented) +export function useLocalStorageState(key: string, defaultValue: T): readonly [T, (setter: ((value: T) => T) | T) => void]; + +// @public (undocumented) +export function useMenuClipboardEvents(): { + copy: (source: TLUiEventSource) => void; + cut: (source: TLUiEventSource) => void; + paste: (data: ClipboardItem[] | DataTransfer, source: TLUiEventSource, point?: VecLike) => Promise; +}; + +// @public (undocumented) +export function useMenuIsOpen(id: string, cb?: (isOpen: boolean) => void): readonly [boolean, (isOpen: boolean) => void]; + +// @public (undocumented) +export function useMenuSchema(): TLUiMenuSchema; + +// @public (undocumented) +export function useNativeClipboardEvents(): void; + +// @public (undocumented) +export function useReadonly(): boolean; + +// @public (undocumented) +export function useToasts(): TLUiToastsContextType; + +// @public (undocumented) +export function useToolbarSchema(): TLUiToolbarSchemaContextType; + +// @public (undocumented) +export function useTools(): TLUiToolsContextType; + +// @public +export function useTranslation(): (id: TLUiTranslationKey) => string; export * from "@tldraw/editor"; -export * from "@tldraw/primitives"; -export * from "@tldraw/ui"; // (No @packageDocumentation comment for this package) diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json index 15bcf9ab0..eecddf3c6 100644 --- a/packages/tldraw/package.json +++ b/packages/tldraw/package.json @@ -44,11 +44,19 @@ "tldraw.css" ], "dependencies": { + "@radix-ui/react-alert-dialog": "^1.0.0", + "@radix-ui/react-context-menu": "^2.1.1", + "@radix-ui/react-dialog": "^1.0.2", + "@radix-ui/react-dropdown-menu": "^2.0.1", + "@radix-ui/react-popover": "1.0.6-rc.5", + "@radix-ui/react-select": "^1.2.0", + "@radix-ui/react-slider": "^1.1.0", + "@radix-ui/react-toast": "^1.1.1", "@tldraw/editor": "workspace:*", - "@tldraw/polyfills": "workspace:*", - "@tldraw/primitives": "workspace:*", - "@tldraw/store": "workspace:*", - "@tldraw/ui": "workspace:*" + "canvas-size": "^1.2.6", + "classnames": "^2.3.2", + "hotkeys-js": "^3.11.2", + "lz-string": "^1.4.4" }, "peerDependencies": { "react": "^18", @@ -58,8 +66,11 @@ "@peculiar/webcrypto": "^1.4.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^14.0.0", + "@types/canvas-size": "^1.2.0", + "@types/classnames": "^2.3.1", + "@types/lz-string": "^1.3.34", "chokidar-cli": "^3.0.0", - "jest-canvas-mock": "^2.4.0", + "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^28.1.2", "lazyrepo": "0.0.0-alpha.27", "resize-observer-polyfill": "^1.5.1" @@ -74,7 +85,7 @@ "^.+\\.*.css$" ], "transformIgnorePatterns": [ - "node_modules/(?!(nanoid|escape-string-regexp)/)" + "node_modules/(?!(nanoid)/)" ], "moduleNameMapper": { "^~(.*)": "/src/$1", @@ -82,6 +93,7 @@ }, "setupFiles": [ "raf/polyfill", + "jest-canvas-mock", "/setupTests.js" ], "setupFilesAfterEnv": [ diff --git a/packages/tldraw/scripts/copy-css-files.mjs b/packages/tldraw/scripts/copy-css-files.mjs index fad1c4759..0e7cd4b54 100644 --- a/packages/tldraw/scripts/copy-css-files.mjs +++ b/packages/tldraw/scripts/copy-css-files.mjs @@ -9,7 +9,7 @@ const packageDir = join(__dirname, '..') let combinedContent = [ join(packageDir, '..', 'editor', 'editor.css'), - join(packageDir, '..', 'ui', 'ui.css'), + join(packageDir, 'src', 'lib', 'ui.css'), ].reduce( (acc, path) => { const content = readFileSync(path, 'utf8') @@ -18,7 +18,7 @@ let combinedContent = [ }, `/* THIS CSS FILE IS GENERATED! DO NOT EDIT. OR EDIT. I'M A COMMENT NOT A COP */ /* This file is created by the copy-css-files.mjs script in @tldraw/tldraw. */ -/* It combines @tldraw/editor/editor.css and @tldraw/ui/ui.css */ +/* It combines @tldraw/editor's editor.css and @tldraw/tldraw's ui.css */ ` ) diff --git a/packages/tldraw/setupTests.js b/packages/tldraw/setupTests.js index 3d6e91eb9..d5560e5a7 100644 --- a/packages/tldraw/setupTests.js +++ b/packages/tldraw/setupTests.js @@ -7,10 +7,17 @@ global.FontFace = class FontFace { return Promise.resolve() } } + document.fonts = { add: () => {}, delete: () => {}, forEach: () => {}, + [Symbol.iterator]: () => [][Symbol.iterator](), } global.matchMedia = () => false + +Object.defineProperty(global.URL, 'createObjectURL', { + writable: true, + value: jest.fn(), +}) diff --git a/packages/tldraw/src/index.ts b/packages/tldraw/src/index.ts index e460fa35e..93622b0a7 100644 --- a/packages/tldraw/src/index.ts +++ b/packages/tldraw/src/index.ts @@ -1,11 +1,131 @@ /// -/** @internal */ -import '@tldraw/polyfills' +import * as Dialog from './lib/ui/components/primitives/Dialog' +import * as DropdownMenu from './lib/ui/components/primitives/DropdownMenu' + // eslint-disable-next-line local/no-export-star export * from '@tldraw/editor' -// eslint-disable-next-line local/no-export-star -export * from '@tldraw/primitives' -// eslint-disable-next-line local/no-export-star -export * from '@tldraw/ui' export { Tldraw } from './lib/Tldraw' +export { defaultShapeTools } from './lib/defaultShapeTools' +export { defaultShapeUtils } from './lib/defaultShapeUtils' +export { defaultTools } from './lib/defaultTools' +// UI +export { TldrawUi, type TldrawUiBaseProps, type TldrawUiProps } from './lib/ui/TldrawUi' +export { + TldrawUiContextProvider, + type TldrawUiContextProviderProps, +} from './lib/ui/TldrawUiContextProvider' +export { setDefaultUiAssetUrls } from './lib/ui/assetUrls' +export { ContextMenu, type TLUiContextMenuProps } from './lib/ui/components/ContextMenu' +export { Button, type TLUiButtonProps } from './lib/ui/components/primitives/Button' +export { Icon, type TLUiIconProps } from './lib/ui/components/primitives/Icon' +export { Input, type TLUiInputProps } from './lib/ui/components/primitives/Input' +export { + compactMenuItems, + findMenuItem, + menuCustom, + menuGroup, + menuItem, + menuSubmenu, + type TLUiCustomMenuItem, + type TLUiMenuChild, + type TLUiMenuGroup, + type TLUiMenuItem, + type TLUiMenuSchema, + type TLUiSubMenu, +} from './lib/ui/hooks/menuHelpers' +export { + useActions, + type TLUiActionItem, + type TLUiActionsContextType, +} from './lib/ui/hooks/useActions' +export { + useActionsMenuSchema, + type TLUiActionsMenuSchemaContextType, +} from './lib/ui/hooks/useActionsMenuSchema' +export { AssetUrlsProvider, useAssetUrls } from './lib/ui/hooks/useAssetUrls' +export { BreakPointProvider, useBreakpoint } from './lib/ui/hooks/useBreakpoint' +export { useCanRedo } from './lib/ui/hooks/useCanRedo' +export { useCanUndo } from './lib/ui/hooks/useCanUndo' +export { useMenuClipboardEvents, useNativeClipboardEvents } from './lib/ui/hooks/useClipboardEvents' +export { + useContextMenuSchema, + type TLUiContextTTLUiMenuSchemaContextType, +} from './lib/ui/hooks/useContextMenuSchema' +export { useCopyAs } from './lib/ui/hooks/useCopyAs' +export { + useDialogs, + type TLUiDialog, + type TLUiDialogProps, + type TLUiDialogsContextType, +} from './lib/ui/hooks/useDialogsProvider' +export { + useEvents, + type TLUiEventContextType, + type TLUiEventHandler, + type TLUiEventSource, +} from './lib/ui/hooks/useEventsProvider' +export { useExportAs } from './lib/ui/hooks/useExportAs' +export { + useHelpMenuSchema, + type TLUiHelpMenuSchemaContextType, +} from './lib/ui/hooks/useHelpMenuSchema' +export { useKeyboardShortcuts } from './lib/ui/hooks/useKeyboardShortcuts' +export { + useKeyboardShortcutsSchema, + type TLUiKeyboardShortcutsSchemaContextType, + type TLUiKeyboardShortcutsSchemaProviderProps, +} from './lib/ui/hooks/useKeyboardShortcutsSchema' +export { useLocalStorageState } from './lib/ui/hooks/useLocalStorageState' +export { useMenuIsOpen } from './lib/ui/hooks/useMenuIsOpen' +export { + useMenuSchema, + type TLUiMenuSchemaContextType, + type TLUiMenuSchemaProviderProps, +} from './lib/ui/hooks/useMenuSchema' +export { useReadonly } from './lib/ui/hooks/useReadonly' +export { + useToasts, + type TLUiToast, + type TLUiToastAction, + type TLUiToastsContextType, +} from './lib/ui/hooks/useToastsProvider' +export { + toolbarItem, + useToolbarSchema, + type TLUiToolbarItem, + type TLUiToolbarSchemaContextType, +} from './lib/ui/hooks/useToolbarSchema' +export { + useTools, + type TLUiToolItem, + type TLUiToolsContextType, + type TLUiToolsProviderProps, +} from './lib/ui/hooks/useTools' +export { type TLUiTranslationKey } from './lib/ui/hooks/useTranslation/TLUiTranslationKey' +export { type TLUiTranslation } from './lib/ui/hooks/useTranslation/translations' +export { + useTranslation as useTranslation, + type TLUiTranslationContextType, +} from './lib/ui/hooks/useTranslation/useTranslation' +export { type TLUiIconType } from './lib/ui/icon-types' +export { useDefaultHelpers, type TLUiOverrides } from './lib/ui/overrides' +export { + ACCEPTED_IMG_TYPE, + getFileMetaData, + getImageSizeFromSrc, + getVideoSizeFromSrc, + isImage, +} from './lib/utils/assets' +export { buildFromV1Document, type LegacyTldrawDocument } from './lib/utils/buildFromV1Document' +export { getEmbedInfo } from './lib/utils/embeds' +export { + TLDRAW_FILE_EXTENSION, + parseAndLoadDocument, + parseTldrawJsonFile, + serializeTldrawJson, + serializeTldrawJsonBlob, + type TldrawFile, +} from './lib/utils/file' +export { truncateStringWithEllipsis } from './lib/utils/text' +export { Dialog, DropdownMenu } diff --git a/packages/tldraw/src/lib/Tldraw.tsx b/packages/tldraw/src/lib/Tldraw.tsx index 511b44390..21f1a5faf 100644 --- a/packages/tldraw/src/lib/Tldraw.tsx +++ b/packages/tldraw/src/lib/Tldraw.tsx @@ -1,21 +1,66 @@ import { Canvas, + ErrorScreen, + LoadingScreen, + RecursivePartial, TldrawEditor, TldrawEditorProps, - defaultShapes, - defaultTools, } from '@tldraw/editor' -import { ContextMenu, TldrawUi, TldrawUiProps } from '@tldraw/ui' import { useMemo } from 'react' +import { TldrawScribble } from './canvas/TldrawScribble' +import { TldrawSelectionForeground } from './canvas/TldrawSelectionForeground' +import { defaultShapeTools } from './defaultShapeTools' +import { defaultShapeUtils } from './defaultShapeUtils' +import { defaultTools } from './defaultTools' +import { TldrawUi, TldrawUiProps } from './ui/TldrawUi' +import { ContextMenu } from './ui/components/ContextMenu' +import { useRegisterExternalContentHandlers } from './useRegisterExternalContentHandlers' +import { TLEditorAssetUrls, useDefaultEditorAssetsWithOverrides } from './utils/assetUrls' +import { usePreloadAssets } from './utils/usePreloadAssets' /** @public */ -export function Tldraw(props: TldrawEditorProps & TldrawUiProps) { +export function Tldraw( + props: TldrawEditorProps & + TldrawUiProps & { + /** + * Urls for the editor to find fonts and other assets. + */ + assetUrls?: RecursivePartial + } +) { const { children, ...rest } = props - const withDefaults = { + const withDefaults: TldrawEditorProps = { + initialState: 'select', ...rest, - shapes: useMemo(() => [...defaultShapes, ...(rest.shapes ?? [])], [rest.shapes]), - tools: useMemo(() => [...defaultTools, ...(rest.tools ?? [])], [rest.tools]), + components: useMemo( + () => ({ + Scribble: TldrawScribble, + SelectionForeground: TldrawSelectionForeground, + ...rest.components, + }), + [rest.components] + ), + shapeUtils: useMemo( + () => [...defaultShapeUtils, ...(rest.shapeUtils ?? [])], + [rest.shapeUtils] + ), + tools: useMemo( + () => [...defaultTools, ...defaultShapeTools, ...(rest.tools ?? [])], + [rest.tools] + ), + } + + const assets = useDefaultEditorAssetsWithOverrides(rest.assetUrls) + + const { done: preloadingComplete, error: preloadingError } = usePreloadAssets(assets) + + if (preloadingError) { + return Could not load assets. Please refresh the page. + } + + if (!preloadingComplete) { + return Loading assets... } return ( @@ -25,7 +70,14 @@ export function Tldraw(props: TldrawEditorProps & TldrawUiProps) { {children} + ) } + +function Hacks() { + useRegisterExternalContentHandlers() + + return null +} diff --git a/packages/editor/src/lib/components/CropHandles.tsx b/packages/tldraw/src/lib/canvas/CropHandles.tsx similarity index 98% rename from packages/editor/src/lib/components/CropHandles.tsx rename to packages/tldraw/src/lib/canvas/CropHandles.tsx index 36b1b5f27..05e0d328b 100644 --- a/packages/editor/src/lib/components/CropHandles.tsx +++ b/packages/tldraw/src/lib/canvas/CropHandles.tsx @@ -1,4 +1,4 @@ -import { toDomPrecision } from '@tldraw/primitives' +import { toDomPrecision } from '@tldraw/editor' import classNames from 'classnames' interface CropHandlesProps { diff --git a/packages/editor/src/lib/components/DefaultScribble.tsx b/packages/tldraw/src/lib/canvas/TldrawScribble.tsx similarity index 75% rename from packages/editor/src/lib/components/DefaultScribble.tsx rename to packages/tldraw/src/lib/canvas/TldrawScribble.tsx index 1f090b415..873c8dea6 100644 --- a/packages/editor/src/lib/components/DefaultScribble.tsx +++ b/packages/tldraw/src/lib/canvas/TldrawScribble.tsx @@ -1,7 +1,6 @@ -import { EASINGS, getStroke } from '@tldraw/primitives' -import { TLScribble } from '@tldraw/tlschema' +import { EASINGS, TLScribble, getSvgPathFromPoints } from '@tldraw/editor' import classNames from 'classnames' -import { getSvgPathFromStroke } from '../utils/svg' +import { getStroke } from '../shapes/shared/freehand/getStroke' /** @public */ export type TLScribbleComponent = (props: { @@ -12,7 +11,7 @@ export type TLScribbleComponent = (props: { className?: string }) => any -export const DefaultScribble: TLScribbleComponent = ({ +export const TldrawScribble: TLScribbleComponent = ({ scribble, zoom, color, @@ -21,7 +20,7 @@ export const DefaultScribble: TLScribbleComponent = ({ }) => { if (!scribble.points.length) return - const d = getSvgPathFromStroke( + const d = getSvgPathFromPoints( getStroke(scribble.points, { size: scribble.size / zoom, start: { taper: true, easing: EASINGS.linear }, diff --git a/packages/editor/src/lib/components/SelectionFg.tsx b/packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx similarity index 97% rename from packages/editor/src/lib/components/SelectionFg.tsx rename to packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx index 4a25a24d9..ee357367f 100644 --- a/packages/editor/src/lib/components/SelectionFg.tsx +++ b/packages/tldraw/src/lib/canvas/TldrawSelectionForeground.tsx @@ -1,12 +1,16 @@ -import { RotateCorner, toDomPrecision } from '@tldraw/primitives' -import { track } from '@tldraw/state' -import { TLEmbedShape, TLTextShape } from '@tldraw/tlschema' +import { + RotateCorner, + TLEmbedShape, + TLTextShape, + getCursor, + toDomPrecision, + track, + useEditor, + useSelectionEvents, + useTransform, +} from '@tldraw/editor' import classNames from 'classnames' import { useRef } from 'react' -import { getCursor } from '../hooks/useCursor' -import { useEditor } from '../hooks/useEditor' -import { useSelectionEvents } from '../hooks/useSelectionEvents' -import { useTransform } from '../hooks/useTransform' import { CropHandles } from './CropHandles' const IS_FIREFOX = @@ -14,7 +18,7 @@ const IS_FIREFOX = navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1 -export const SelectionFg = track(function SelectionFg() { +export const TldrawSelectionForeground = track(function SelectionFg() { const editor = useEditor() const rSvg = useRef(null) diff --git a/packages/tldraw/src/lib/defaultShapeTools.ts b/packages/tldraw/src/lib/defaultShapeTools.ts new file mode 100644 index 000000000..d8c8d2424 --- /dev/null +++ b/packages/tldraw/src/lib/defaultShapeTools.ts @@ -0,0 +1,20 @@ +import { ArrowShapeTool } from './shapes/arrow/ArrowShapeTool' +import { DrawShapeTool } from './shapes/draw/DrawShapeTool' +import { FrameShapeTool } from './shapes/frame/FrameShapeTool' +import { GeoShapeTool } from './shapes/geo/GeoShapeTool' +import { HighlightShapeTool } from './shapes/highlight/HighlightShapeTool' +import { LineShapeTool } from './shapes/line/LineShapeTool' +import { NoteShapeTool } from './shapes/note/NoteShapeTool' +import { TextShapeTool } from './shapes/text/TextShapeTool' + +/** @public */ +export const defaultShapeTools = [ + TextShapeTool, + DrawShapeTool, + GeoShapeTool, + NoteShapeTool, + LineShapeTool, + FrameShapeTool, + ArrowShapeTool, + HighlightShapeTool, +] diff --git a/packages/tldraw/src/lib/defaultShapeUtils.ts b/packages/tldraw/src/lib/defaultShapeUtils.ts new file mode 100644 index 000000000..d7ff57648 --- /dev/null +++ b/packages/tldraw/src/lib/defaultShapeUtils.ts @@ -0,0 +1,29 @@ +import { TLAnyShapeUtilConstructor } from '@tldraw/editor' +import { ArrowShapeUtil } from './shapes/arrow/ArrowShapeUtil' +import { BookmarkShapeUtil } from './shapes/bookmark/BookmarkShapeUtil' +import { DrawShapeUtil } from './shapes/draw/DrawShapeUtil' +import { EmbedShapeUtil } from './shapes/embed/EmbedShapeUtil' +import { FrameShapeUtil } from './shapes/frame/FrameShapeUtil' +import { GeoShapeUtil } from './shapes/geo/GeoShapeUtil' +import { HighlightShapeUtil } from './shapes/highlight/HighlightShapeUtil' +import { ImageShapeUtil } from './shapes/image/ImageShapeUtil' +import { LineShapeUtil } from './shapes/line/LineShapeUtil' +import { NoteShapeUtil } from './shapes/note/NoteShapeUtil' +import { TextShapeUtil } from './shapes/text/TextShapeUtil' +import { VideoShapeUtil } from './shapes/video/VideoShapeUtil' + +/** @public */ +export const defaultShapeUtils: TLAnyShapeUtilConstructor[] = [ + TextShapeUtil, + BookmarkShapeUtil, + DrawShapeUtil, + GeoShapeUtil, + NoteShapeUtil, + LineShapeUtil, + FrameShapeUtil, + ArrowShapeUtil, + HighlightShapeUtil, + EmbedShapeUtil, + ImageShapeUtil, + VideoShapeUtil, +] diff --git a/packages/tldraw/src/lib/defaultTools.ts b/packages/tldraw/src/lib/defaultTools.ts new file mode 100644 index 000000000..0e75addb0 --- /dev/null +++ b/packages/tldraw/src/lib/defaultTools.ts @@ -0,0 +1,8 @@ +import { EraserTool } from './tools/EraserTool/EraserTool' +import { HandTool } from './tools/HandTool/HandTool' +import { LaserTool } from './tools/LaserTool/LaserTool' +import { SelectTool } from './tools/SelectTool/SelectTool' +import { ZoomTool } from './tools/ZoomTool/ZoomTool' + +/** @public */ +export const defaultTools = [EraserTool, HandTool, LaserTool, ZoomTool, SelectTool] diff --git a/packages/editor/src/lib/editor/shapes/arrow/ArrowShapeTool.test.ts b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeTool.test.ts similarity index 92% rename from packages/editor/src/lib/editor/shapes/arrow/ArrowShapeTool.test.ts rename to packages/tldraw/src/lib/shapes/arrow/ArrowShapeTool.test.ts index b451c227e..3a60f78f1 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/ArrowShapeTool.test.ts +++ b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeTool.test.ts @@ -1,5 +1,4 @@ -import { Vec2d } from '@tldraw/primitives' -import { createShapeId } from '@tldraw/tlschema' +import { Vec2d, createShapeId } from '@tldraw/editor' import { TestEditor } from '../../../test/TestEditor' let editor: TestEditor @@ -33,7 +32,7 @@ beforeEach(() => { }) it('enters the arrow state', () => { - editor.setSelectedTool('arrow') + editor.setCurrentTool('arrow') expect(editor.currentToolId).toBe('arrow') editor.expectPathToBe('root.arrow.idle') }) @@ -41,14 +40,14 @@ it('enters the arrow state', () => { describe('When in the idle state', () => { it('enters the pointing state and creates a shape on pointer down', () => { const shapesBefore = editor.shapesArray.length - editor.setSelectedTool('arrow').pointerDown(0, 0, { target: 'canvas' }) + editor.setCurrentTool('arrow').pointerDown(0, 0, { target: 'canvas' }) const shapesAfter = editor.shapesArray.length expect(shapesAfter).toBe(shapesBefore + 1) editor.expectPathToBe('root.arrow.pointing') }) it('returns to select on cancel', () => { - editor.setSelectedTool('arrow') + editor.setCurrentTool('arrow') editor.cancel() editor.expectToBeIn('select.idle') }) @@ -57,7 +56,7 @@ describe('When in the idle state', () => { describe('When in the pointing state', () => { it('cancels on pointer up', () => { const shapesBefore = editor.shapesArray.length - editor.setSelectedTool('arrow').pointerDown(0, 0, { target: 'canvas' }).pointerUp(0, 0) + editor.setCurrentTool('arrow').pointerDown(0, 0, { target: 'canvas' }).pointerUp(0, 0) const shapesAfter = editor.shapesArray.length expect(shapesAfter).toBe(shapesBefore) expect(editor.hintingIds.length).toBe(0) @@ -66,7 +65,7 @@ describe('When in the pointing state', () => { it('bails on cancel', () => { const shapesBefore = editor.shapesArray.length - editor.setSelectedTool('arrow').pointerDown(0, 0, { target: 'canvas' }).cancel() + editor.setCurrentTool('arrow').pointerDown(0, 0, { target: 'canvas' }).cancel() const shapesAfter = editor.shapesArray.length expect(shapesAfter).toBe(shapesBefore) expect(editor.hintingIds.length).toBe(0) @@ -74,7 +73,7 @@ describe('When in the pointing state', () => { }) it('enters the dragging state on pointer move', () => { - editor.setSelectedTool('arrow').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) + editor.setCurrentTool('arrow').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) editor.expectPathToBe('root.select.dragging_handle') }) }) @@ -82,7 +81,7 @@ describe('When in the pointing state', () => { // This could be moved to dragging_handle describe('When dragging the arrow', () => { it('updates the arrow on pointer move', () => { - editor.setSelectedTool('arrow').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) + editor.setCurrentTool('arrow').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) const arrow = editor.shapesArray[editor.shapesArray.length - 1] editor.expectShapeToMatch(arrow, { id: arrow.id, @@ -100,7 +99,7 @@ describe('When dragging the arrow', () => { it('returns to select.idle, keeping shape, on pointer up', () => { const shapesBefore = editor.shapesArray.length editor - .setSelectedTool('arrow') + .setCurrentTool('arrow') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) @@ -111,10 +110,10 @@ describe('When dragging the arrow', () => { }) it('returns to arrow.idle, keeping shape, on pointer up when tool lock is active', () => { - editor.setToolLocked(true) + editor.isToolLocked = true const shapesBefore = editor.shapesArray.length editor - .setSelectedTool('arrow') + .setCurrentTool('arrow') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) @@ -127,7 +126,7 @@ describe('When dragging the arrow', () => { it('bails on cancel', () => { const shapesBefore = editor.shapesArray.length editor - .setSelectedTool('arrow') + .setCurrentTool('arrow') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .cancel() @@ -139,7 +138,7 @@ describe('When dragging the arrow', () => { describe('When pointing a start shape', () => { it('binds to the top shape', () => { - editor.setSelectedTool('arrow').pointerDown(375, 375) + editor.setCurrentTool('arrow').pointerDown(375, 375) // Set hinting ids when moving away expect(editor.hintingIds.length).toBe(1) @@ -178,7 +177,7 @@ describe('When pointing a start shape', () => { describe('When pointing an end shape', () => { it('binds to the top shape', () => { - editor.setSelectedTool('arrow') + editor.setCurrentTool('arrow') editor.pointerDown(0, 0) expect(editor.hintingIds.length).toBe(0) @@ -215,7 +214,7 @@ describe('When pointing an end shape', () => { }) it('unbinds and rebinds', () => { - editor.setSelectedTool('arrow').pointerDown(0, 0) + editor.setCurrentTool('arrow').pointerDown(0, 0) editor.inputs.pointerVelocity = new Vec2d(1, 1) @@ -325,7 +324,7 @@ describe('When pointing an end shape', () => { }) it('begins imprecise when moving quickly', () => { - editor.setSelectedTool('arrow').pointerDown(0, 0) + editor.setCurrentTool('arrow').pointerDown(0, 0) editor.inputs.pointerVelocity = new Vec2d(1, 1) editor.pointerMove(370, 370) @@ -351,7 +350,7 @@ describe('When pointing an end shape', () => { }) it('begins precise when moving slowly', () => { - editor.setSelectedTool('arrow').pointerDown(0, 0) + editor.setCurrentTool('arrow').pointerDown(0, 0) let arrow = editor.shapesArray[editor.shapesArray.length - 1] @@ -398,7 +397,7 @@ describe('reparenting issue', () => { editor.selectAll().deleteShapes() // Create an arrow! - editor.setSelectedTool('arrow') + editor.setCurrentTool('arrow') editor.pointerDown(0, 0) editor.pointerMove(100, 100) editor.pointerUp() diff --git a/packages/editor/src/lib/editor/shapes/arrow/ArrowShapeTool.ts b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeTool.ts similarity index 52% rename from packages/editor/src/lib/editor/shapes/arrow/ArrowShapeTool.ts rename to packages/tldraw/src/lib/shapes/arrow/ArrowShapeTool.ts index b938e597b..c9c0c7dc0 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/ArrowShapeTool.ts +++ b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeTool.ts @@ -1,11 +1,11 @@ -import { StateNode } from '../../tools/StateNode' +import { StateNode } from '@tldraw/editor' import { Idle } from './toolStates/Idle' import { Pointing } from './toolStates/Pointing' export class ArrowShapeTool extends StateNode { static override id = 'arrow' - static initial = 'idle' - static children = () => [Idle, Pointing] + static override initial = 'idle' + static override children = () => [Idle, Pointing] - shapeType = 'arrow' + overrideshapeType = 'arrow' } diff --git a/packages/editor/src/lib/editor/shapes/arrow/ArrowShapeUtil.test.ts b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts similarity index 94% rename from packages/editor/src/lib/editor/shapes/arrow/ArrowShapeUtil.test.ts rename to packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts index 56f0bf98c..29bb242e1 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/ArrowShapeUtil.test.ts +++ b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts @@ -1,6 +1,11 @@ -import { TAU } from '@tldraw/primitives' -import { TLArrowShape, TLArrowShapeTerminal, TLShapeId, createShapeId } from '@tldraw/tlschema' -import { assert } from '@tldraw/utils' +import { + assert, + createShapeId, + TAU, + TLArrowShape, + TLArrowShapeTerminal, + TLShapeId, +} from '@tldraw/editor' import { TestEditor } from '../../../test/TestEditor' let editor: TestEditor @@ -296,7 +301,7 @@ describe('Other cases when arrow are moved', () => { .selectAll() .groupShapes() - editor.setSelectedTool('arrow').pointerDown(1000, 1000).pointerMove(50, 350).pointerUp(50, 350) + editor.setCurrentTool('arrow').pointerDown(1000, 1000).pointerMove(50, 350).pointerUp(50, 350) let arrow = editor.shapesArray[editor.shapesArray.length - 1] assert(editor.isShapeOfType(arrow, 'arrow')) assert(arrow.props.end.type === 'binding') @@ -315,7 +320,7 @@ describe('Other cases when arrow are moved', () => { describe('When a shape it rotated', () => { it('binds correctly', () => { - editor.setSelectedTool('arrow').pointerDown(0, 0).pointerMove(375, 375) + editor.setCurrentTool('arrow').pointerDown(0, 0).pointerMove(375, 375) const arrow = editor.shapesArray[editor.shapesArray.length - 1] @@ -356,15 +361,15 @@ describe('resizing', () => { editor .selectAll() .deleteShapes() - .setSelectedTool('arrow') + .setCurrentTool('arrow') .pointerDown(0, 0) .pointerMove(200, 200) .pointerUp() - .setSelectedTool('arrow') + .setCurrentTool('arrow') .pointerDown(100, 100) .pointerMove(300, 300) .pointerUp() - .setSelectedTool('select') + .setCurrentTool('select') const arrow1 = editor.shapesArray.at(-2)! const arrow2 = editor.shapesArray.at(-1)! @@ -411,15 +416,15 @@ describe('resizing', () => { editor .selectAll() .deleteShapes() - .setSelectedTool('arrow') + .setCurrentTool('arrow') .pointerDown(0, 0) .pointerMove(200, 200) .pointerUp() - .setSelectedTool('arrow') + .setCurrentTool('arrow') .pointerDown(100, 100) .pointerMove(300, 300) .pointerUp() - .setSelectedTool('select') + .setCurrentTool('select') const arrow1 = editor.shapesArray.at(-2)! const arrow2 = editor.shapesArray.at(-1)! @@ -481,24 +486,24 @@ describe("an arrow's parents", () => { beforeEach(() => { editor.selectAll().deleteShapes() - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(0, 0).pointerMove(100, 100).pointerUp() frameId = editor.onlySelectedShape!.id - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(10, 10).pointerMove(20, 20).pointerUp() boxAid = editor.onlySelectedShape!.id - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(10, 80).pointerMove(20, 90).pointerUp() boxBid = editor.onlySelectedShape!.id - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(110, 10).pointerMove(120, 20).pointerUp() boxCid = editor.onlySelectedShape!.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.setSelectedTool('arrow') + editor.setCurrentTool('arrow') editor.pointerDown(15, 15).pointerMove(50, 50) const arrowId = editor.onlySelectedShape!.id @@ -533,7 +538,7 @@ describe("an arrow's parents", () => { it('reparents when one of the shapes is moved outside of the frame', () => { // draw arrow from a to b - editor.setSelectedTool('arrow') + editor.setCurrentTool('arrow') editor.pointerDown(15, 15).pointerMove(15, 85).pointerUp() const arrowId = editor.onlySelectedShape!.id @@ -557,7 +562,7 @@ describe("an arrow's parents", () => { it('reparents to the frame when an arrow created outside has both its parents moved inside', () => { // draw arrow from a to c - editor.setSelectedTool('arrow') + editor.setCurrentTool('arrow') editor.pointerDown(15, 15).pointerMove(115, 15).pointerUp() const arrowId = editor.onlySelectedShape!.id expect(editor.getShapeById(arrowId)).toMatchObject({ diff --git a/packages/editor/src/lib/editor/shapes/arrow/ArrowShapeUtil.tsx b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx similarity index 94% rename from packages/editor/src/lib/editor/shapes/arrow/ArrowShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx index 443cd9317..c149bee87 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/ArrowShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx @@ -1,42 +1,51 @@ import { Box2d, - getPointOnCircle, - linesIntersect, - longAngleDist, - Matrix2d, - pointInPolygon, - shortAngleDist, - toDomPrecision, - Vec2d, - VecLike, -} from '@tldraw/primitives' -import { computed, EMPTY_ARRAY } from '@tldraw/state' -import { ComputedCache } from '@tldraw/store' -import { + ComputedCache, DefaultFontFamilies, - getDefaultColorTheme, + EMPTY_ARRAY, + Matrix2d, + SVGContainer, + ShapeUtil, + SvgExportContext, TLArrowShape, TLArrowShapeArrowheadStyle, TLDefaultColorStyle, TLDefaultColorTheme, TLDefaultFillStyle, TLHandle, - TLShapeId, - TLShapePartial, - Vec2dModel, -} from '@tldraw/tlschema' -import { deepCopy, last, minBy } from '@tldraw/utils' -import * as React from 'react' -import { SVGContainer } from '../../../components/SVGContainer' -import { - ShapeUtil, TLOnEditEndHandler, TLOnHandleChangeHandler, TLOnResizeHandler, TLOnTranslateStartHandler, + TLShapeId, + TLShapePartial, TLShapeUtilCanvasSvgDef, TLShapeUtilFlag, -} from '../ShapeUtil' + Vec2d, + Vec2dModel, + VecLike, + arrowShapeMigrations, + arrowShapeProps, + computed, + deepCopy, + getArrowTerminalsInArrowSpace, + getArrowheadPathForType, + getCurvedArrowHandlePath, + getDefaultColorTheme, + getPointOnCircle, + getSolidCurvedArrowPath, + getSolidStraightArrowPath, + getStraightArrowHandlePath, + last, + linesIntersect, + longAngleDist, + minBy, + pointInPolygon, + shortAngleDist, + toDomPrecision, +} from '@tldraw/editor' +import React from 'react' +import { ShapeFill, getShapeFillSvg, useDefaultColorTheme } from '../shared/ShapeFill' import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans' import { ARROW_LABEL_FONT_SIZES, @@ -50,12 +59,6 @@ import { getFontDefForExport, } from '../shared/defaultStyleDefs' import { getPerfectDashProps } from '../shared/getPerfectDashProps' -import { getShapeFillSvg, ShapeFill, useDefaultColorTheme } from '../shared/ShapeFill' -import { SvgExportContext } from '../shared/SvgExportContext' -import { getArrowheadPathForType } from './arrow/arrowheads' -import { getCurvedArrowHandlePath, getSolidCurvedArrowPath } from './arrow/curved-arrow' -import { getArrowTerminalsInArrowSpace } from './arrow/shared' -import { getSolidStraightArrowPath, getStraightArrowHandlePath } from './arrow/straight-arrow' import { ArrowTextLabel } from './components/ArrowTextLabel' let globalRenderIndex = 0 @@ -63,6 +66,8 @@ let globalRenderIndex = 0 /** @public */ export class ArrowShapeUtil extends ShapeUtil { static override type = 'arrow' as const + static override props = arrowShapeProps + static override migrations = arrowShapeMigrations override canEdit = () => true override canBind = () => false @@ -90,10 +95,6 @@ export class ArrowShapeUtil extends ShapeUtil { } } - getCenter(shape: TLArrowShape): Vec2d { - return this.editor.getBounds(shape).center - } - getBounds(shape: TLArrowShape) { return Box2d.FromPoints(this.getOutlineWithoutLabel(shape)) } @@ -144,7 +145,7 @@ export class ArrowShapeUtil extends ShapeUtil { return results } - getOutline(shape: TLArrowShape): Vec2d[] { + override getOutline(shape: TLArrowShape): Vec2d[] { const outlineWithoutLabel = this.getOutlineWithoutLabel(shape) const labelBounds = this.getLabelBounds(shape) @@ -200,11 +201,11 @@ export class ArrowShapeUtil extends ShapeUtil { return result } - snapPoints(_shape: TLArrowShape): Vec2d[] { + override snapPoints(_shape: TLArrowShape): Vec2d[] { return EMPTY_ARRAY } - getHandles(shape: TLArrowShape): TLHandle[] { + override getHandles(shape: TLArrowShape): TLHandle[] { const info = this.editor.getArrowInfo(shape)! return [ { @@ -234,7 +235,10 @@ export class ArrowShapeUtil extends ShapeUtil { ] } - onHandleChange: TLOnHandleChangeHandler = (shape, { handle, isPrecise }) => { + override onHandleChange: TLOnHandleChangeHandler = ( + shape, + { handle, isPrecise } + ) => { const next = deepCopy(shape) switch (handle.id) { @@ -365,7 +369,7 @@ export class ArrowShapeUtil extends ShapeUtil { return next } - onTranslateStart: TLOnTranslateStartHandler = (shape) => { + override onTranslateStart: TLOnTranslateStartHandler = (shape) => { let startBinding: TLShapeId | null = shape.props.start.type === 'binding' ? shape.props.start.boundShapeId : null let endBinding: TLShapeId | null = @@ -375,8 +379,10 @@ export class ArrowShapeUtil extends ShapeUtil { // If no bound shapes are in the selection, unbind any bound shapes if ( - (startBinding && this.editor.isWithinSelection(startBinding)) || - (endBinding && this.editor.isWithinSelection(endBinding)) + (startBinding && + (this.editor.isSelected(startBinding) || this.editor.isAncestorSelected(startBinding))) || + (endBinding && + (this.editor.isSelected(endBinding) || this.editor.isAncestorSelected(endBinding))) ) { return } @@ -405,7 +411,7 @@ export class ArrowShapeUtil extends ShapeUtil { } } - onResize: TLOnResizeHandler = (shape, info) => { + override onResize: TLOnResizeHandler = (shape, info) => { const { scaleX, scaleY } = info const terminals = getArrowTerminalsInArrowSpace(this.editor, shape) @@ -488,7 +494,7 @@ export class ArrowShapeUtil extends ShapeUtil { return next } - onDoubleClickHandle = ( + override onDoubleClickHandle = ( shape: TLArrowShape, handle: TLHandle ): TLShapePartial | void => { @@ -516,7 +522,7 @@ export class ArrowShapeUtil extends ShapeUtil { } } - hitTestPoint(shape: TLArrowShape, point: VecLike): boolean { + override hitTestPoint(shape: TLArrowShape, point: VecLike): boolean { const outline = this.editor.getOutline(shape) const zoomLevel = this.editor.zoomLevel const offsetDist = STROKE_SIZES[shape.props.size] / zoomLevel @@ -531,7 +537,7 @@ export class ArrowShapeUtil extends ShapeUtil { return false } - hitTestLineSegment(shape: TLArrowShape, A: VecLike, B: VecLike): boolean { + override hitTestLineSegment(shape: TLArrowShape, A: VecLike, B: VecLike): boolean { const outline = this.editor.getOutline(shape) for (let i = 0; i < outline.length - 1; i++) { @@ -887,7 +893,7 @@ export class ArrowShapeUtil extends ShapeUtil { return this.labelBoundsCache.get(shape.id) || null } - onEditEnd: TLOnEditEndHandler = (shape) => { + override onEditEnd: TLOnEditEndHandler = (shape) => { const { id, type, @@ -907,7 +913,7 @@ export class ArrowShapeUtil extends ShapeUtil { } } - toSvg(shape: TLArrowShape, ctx: SvgExportContext) { + override toSvg(shape: TLArrowShape, ctx: SvgExportContext) { const theme = getDefaultColorTheme(this.editor) ctx.addExportDef(getFillDefForExport(shape.props.fill, theme)) @@ -1079,7 +1085,7 @@ export class ArrowShapeUtil extends ShapeUtil { return g } - getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] { + override getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] { return [getFillDefForCanvas()] } } diff --git a/packages/editor/src/lib/editor/shapes/arrow/components/ArrowTextLabel.tsx b/packages/tldraw/src/lib/shapes/arrow/components/ArrowTextLabel.tsx similarity index 89% rename from packages/editor/src/lib/editor/shapes/arrow/components/ArrowTextLabel.tsx rename to packages/tldraw/src/lib/shapes/arrow/components/ArrowTextLabel.tsx index 47b8cd5a6..eb214dc06 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/components/ArrowTextLabel.tsx +++ b/packages/tldraw/src/lib/shapes/arrow/components/ArrowTextLabel.tsx @@ -1,10 +1,8 @@ -import { VecLike } from '@tldraw/primitives' -import { TLArrowShape, TLShapeId } from '@tldraw/tlschema' +import { TLArrowShape, TLShapeId, VecLike, stopEventPropagation } from '@tldraw/editor' import * as React from 'react' -import { stopEventPropagation } from '../../../../utils/dom' +import { TextHelpers } from '../../shared/TextHelpers' import { ARROW_LABEL_FONT_SIZES, TEXT_PROPS } from '../../shared/default-shape-constants' import { useEditableText } from '../../shared/useEditableText' -import { TextHelpers } from '../../text/TextHelpers' export const ArrowTextLabel = React.memo(function ArrowTextLabel({ id, diff --git a/packages/tldraw/src/lib/shapes/arrow/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/arrow/toolStates/Idle.ts new file mode 100644 index 000000000..d66a59b04 --- /dev/null +++ b/packages/tldraw/src/lib/shapes/arrow/toolStates/Idle.ts @@ -0,0 +1,17 @@ +import { StateNode, TLEventHandlers } from '@tldraw/editor' + +export class Idle extends StateNode { + static override id = 'idle' + + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + this.parent.transition('pointing', info) + } + + override onEnter = () => { + this.editor.cursor = { type: 'cross', rotation: 0 } + } + + override onCancel = () => { + this.editor.setCurrentTool('select') + } +} diff --git a/packages/editor/src/lib/editor/shapes/arrow/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/arrow/toolStates/Pointing.ts similarity index 86% rename from packages/editor/src/lib/editor/shapes/arrow/toolStates/Pointing.ts rename to packages/tldraw/src/lib/shapes/arrow/toolStates/Pointing.ts index 03e9143ce..eea49469d 100644 --- a/packages/editor/src/lib/editor/shapes/arrow/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/arrow/toolStates/Pointing.ts @@ -1,6 +1,4 @@ -import { createShapeId, TLArrowShape } from '@tldraw/tlschema' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' +import { StateNode, TLArrowShape, TLEventHandlers, createShapeId } from '@tldraw/editor' export class Pointing extends StateNode { static override id = 'pointing' @@ -22,7 +20,7 @@ export class Pointing extends StateNode { clearTimeout(this.preciseTimeout) } - onEnter = () => { + override onEnter = () => { const { inputs: { currentPagePoint }, } = this.editor @@ -60,7 +58,7 @@ export class Pointing extends StateNode { if (change) { const startTerminal = change.props?.start if (startTerminal?.type === 'binding') { - this.editor.setHintingIds([startTerminal.boundShapeId]) + this.editor.hintingIds = [startTerminal.boundShapeId] } this.editor.updateShapes([change], true) } @@ -73,11 +71,11 @@ export class Pointing extends StateNode { this.startPreciseTimeout() } - onExit = () => { + override onExit = () => { this.clearPreciseTimeout() } - onPointerMove: TLEventHandlers['onPointerMove'] = () => { + override onPointerMove: TLEventHandlers['onPointerMove'] = () => { if (!this.shape) return if (this.editor.inputs.isDragging) { @@ -114,7 +112,7 @@ export class Pointing extends StateNode { } } - this.editor.setSelectedTool('select.dragging_handle', { + this.editor.setCurrentTool('select.dragging_handle', { shape: this.shape, handle: handles.find((h) => h.id === 'end')! /* end */, isCreating: true, @@ -141,7 +139,7 @@ export class Pointing extends StateNode { cancel() { this.editor.bailToMark('creating') - this.editor.setHintingIds([]) + this.editor.hintingIds = [] this.parent.transition('idle', {}) } } diff --git a/packages/editor/src/lib/editor/shapes/bookmark/BookmarkShapeUtil.tsx b/packages/tldraw/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx similarity index 87% rename from packages/editor/src/lib/editor/shapes/bookmark/BookmarkShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx index 865b64b94..de52b6eba 100644 --- a/packages/editor/src/lib/editor/shapes/bookmark/BookmarkShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx @@ -1,22 +1,30 @@ -import { toDomPrecision } from '@tldraw/primitives' -import { AssetRecordType, TLAssetId, TLBookmarkAsset, TLBookmarkShape } from '@tldraw/tlschema' -import { debounce, getHashForString } from '@tldraw/utils' -import { HTMLContainer } from '../../../components/HTMLContainer' - -import { isValidUrl } from '../../../utils/data' import { - getRotatedBoxShadow, + AssetRecordType, + BaseBoxShapeUtil, + Editor, + HTMLContainer, + TLAssetId, + TLBookmarkAsset, + TLBookmarkShape, + TLOnBeforeCreateHandler, + TLOnBeforeUpdateHandler, + bookmarkShapeMigrations, + bookmarkShapeProps, + debounce, + getHashForString, + isValidUrl, stopEventPropagation, - truncateStringWithEllipsis, -} from '../../../utils/dom' -import { Editor } from '../../Editor' -import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil' -import { TLOnBeforeCreateHandler, TLOnBeforeUpdateHandler } from '../ShapeUtil' + toDomPrecision, +} from '@tldraw/editor' +import { getRotatedBoxShadow } from '../../utils/rotated-box-shadow' +import { truncateStringWithEllipsis } from '../../utils/text' import { HyperlinkButton } from '../shared/HyperlinkButton' /** @public */ export class BookmarkShapeUtil extends BaseBoxShapeUtil { static override type = 'bookmark' as const + static override props = bookmarkShapeProps + static override migrations = bookmarkShapeMigrations override canResize = () => false @@ -166,7 +174,7 @@ const createBookmarkAssetOnUrlChange = debounce(async (editor: Editor, shape: TL // Create the asset using the external content manager's createAssetFromUrl method. // This may be overwritten by the user (for example, we overwrite it on tldraw.com) - const asset = await editor.externalContentManager.createAssetFromUrl(editor, url) + const asset = await editor.getAssetForExternalContent({ type: 'url', url }) if (!asset) { // No asset? Just leave the bookmark as a null assetId. diff --git a/packages/editor/src/lib/editor/shapes/draw/DrawShapeTool.test.ts b/packages/tldraw/src/lib/shapes/draw/DrawShapeTool.test.ts similarity index 89% rename from packages/editor/src/lib/editor/shapes/draw/DrawShapeTool.test.ts rename to packages/tldraw/src/lib/shapes/draw/DrawShapeTool.test.ts index c73f36d50..fe5372705 100644 --- a/packages/editor/src/lib/editor/shapes/draw/DrawShapeTool.test.ts +++ b/packages/tldraw/src/lib/shapes/draw/DrawShapeTool.test.ts @@ -16,14 +16,14 @@ describe(DrawShapeTool, () => { describe('When in the idle state', () => { it('Returns to select on cancel', () => { - editor.setSelectedTool('draw') + editor.setCurrentTool('draw') editor.expectPathToBe('root.draw.idle') editor.cancel() editor.expectPathToBe('root.select.idle') }) it('Enters the drawing state on pointer down', () => { - editor.setSelectedTool('draw') + editor.setCurrentTool('draw') editor.pointerDown(50, 50) editor.expectPathToBe('root.draw.drawing') }) @@ -31,14 +31,14 @@ describe('When in the idle state', () => { describe('When in the drawing state', () => { it('Returns to idle on cancel', () => { - editor.setSelectedTool('draw') + editor.setCurrentTool('draw') editor.pointerDown(50, 50) editor.cancel() editor.expectPathToBe('root.draw.idle') }) it('Returns to idle on complete', () => { - editor.setSelectedTool('draw') + editor.setCurrentTool('draw') editor.pointerDown(50, 50) editor.pointerUp(50, 50) editor.expectPathToBe('root.draw.idle') diff --git a/packages/editor/src/lib/editor/shapes/draw/DrawShapeTool.ts b/packages/tldraw/src/lib/shapes/draw/DrawShapeTool.ts similarity index 59% rename from packages/editor/src/lib/editor/shapes/draw/DrawShapeTool.ts rename to packages/tldraw/src/lib/shapes/draw/DrawShapeTool.ts index 8089eb86f..147619545 100644 --- a/packages/editor/src/lib/editor/shapes/draw/DrawShapeTool.ts +++ b/packages/tldraw/src/lib/shapes/draw/DrawShapeTool.ts @@ -1,15 +1,15 @@ -import { StateNode } from '../../tools/StateNode' +import { StateNode } from '@tldraw/editor' import { Drawing } from './toolStates/Drawing' import { Idle } from './toolStates/Idle' export class DrawShapeTool extends StateNode { static override id = 'draw' - static initial = 'idle' - static children = () => [Idle, Drawing] + static override initial = 'idle' + static override children = () => [Idle, Drawing] - shapeType = 'draw' + override shapeType = 'draw' - onExit = () => { + override onExit = () => { const drawingState = this.children!['drawing'] as Drawing drawingState.initialShape = undefined } diff --git a/packages/editor/src/lib/editor/shapes/draw/DrawShapeUtil.tsx b/packages/tldraw/src/lib/shapes/draw/DrawShapeUtil.tsx similarity index 83% rename from packages/editor/src/lib/editor/shapes/draw/DrawShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/draw/DrawShapeUtil.tsx index c365e5fa0..3ad6da288 100644 --- a/packages/editor/src/lib/editor/shapes/draw/DrawShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/draw/DrawShapeUtil.tsx @@ -1,35 +1,45 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { Box2d, - getStrokeOutlinePoints, - getStrokePoints, - linesIntersect, - pointInPolygon, - setStrokePointRadii, - toFixed, + SVGContainer, + ShapeUtil, + SvgExportContext, + TLDrawShape, + TLDrawShapeSegment, + TLOnResizeHandler, + TLShapeUtilCanvasSvgDef, Vec2d, VecLike, -} from '@tldraw/primitives' -import { getDefaultColorTheme, TLDrawShape, TLDrawShapeSegment } from '@tldraw/tlschema' -import { last, rng } from '@tldraw/utils' -import { SVGContainer } from '../../../components/SVGContainer' -import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../utils/svg' -import { ShapeUtil, TLOnResizeHandler, TLShapeUtilCanvasSvgDef } from '../ShapeUtil' + drawShapeMigrations, + drawShapeProps, + getDefaultColorTheme, + getSvgPathFromPoints, + last, + linesIntersect, + pointInPolygon, + rng, + toFixed, +} from '@tldraw/editor' +import { ShapeFill, getShapeFillSvg, useDefaultColorTheme } from '../shared/ShapeFill' import { STROKE_SIZES } from '../shared/default-shape-constants' import { getFillDefForCanvas, getFillDefForExport } from '../shared/defaultStyleDefs' -import { getShapeFillSvg, ShapeFill, useDefaultColorTheme } from '../shared/ShapeFill' -import { SvgExportContext } from '../shared/SvgExportContext' +import { getStrokeOutlinePoints } from '../shared/freehand/getStrokeOutlinePoints' +import { getStrokePoints } from '../shared/freehand/getStrokePoints' +import { setStrokePointRadii } from '../shared/freehand/setStrokePointRadii' +import { getSvgPathFromStrokePoints } from '../shared/freehand/svg' import { useForceSolid } from '../shared/useForceSolid' import { getDrawShapeStrokeDashArray, getFreehandOptions, getPointsFromSegments } from './getPath' /** @public */ export class DrawShapeUtil extends ShapeUtil { static override type = 'draw' as const + static override props = drawShapeProps + static override migrations = drawShapeMigrations - hideResizeHandles = (shape: TLDrawShape) => getIsDot(shape) - hideRotateHandle = (shape: TLDrawShape) => getIsDot(shape) - hideSelectionBoundsBg = (shape: TLDrawShape) => getIsDot(shape) - hideSelectionBoundsFg = (shape: TLDrawShape) => getIsDot(shape) + override hideResizeHandles = (shape: TLDrawShape) => getIsDot(shape) + override hideRotateHandle = (shape: TLDrawShape) => getIsDot(shape) + override hideSelectionBoundsBg = (shape: TLDrawShape) => getIsDot(shape) + override hideSelectionBoundsFg = (shape: TLDrawShape) => getIsDot(shape) override getDefaultProps(): TLDrawShape['props'] { return { @@ -44,21 +54,21 @@ export class DrawShapeUtil extends ShapeUtil { } } - isClosed = (shape: TLDrawShape) => shape.props.isClosed + override isClosed = (shape: TLDrawShape) => shape.props.isClosed getBounds(shape: TLDrawShape) { return Box2d.FromPoints(this.editor.getOutline(shape)) } - getOutline(shape: TLDrawShape) { + override getOutline(shape: TLDrawShape) { return getPointsFromSegments(shape.props.segments) } - getCenter(shape: TLDrawShape): Vec2d { + override getCenter(shape: TLDrawShape): Vec2d { return this.editor.getBounds(shape).center } - hitTestPoint(shape: TLDrawShape, point: VecLike): boolean { + override hitTestPoint(shape: TLDrawShape, point: VecLike): boolean { const outline = this.editor.getOutline(shape) const zoomLevel = this.editor.zoomLevel const offsetDist = STROKE_SIZES[shape.props.size] / zoomLevel @@ -85,7 +95,7 @@ export class DrawShapeUtil extends ShapeUtil { return false } - hitTestLineSegment(shape: TLDrawShape, A: VecLike, B: VecLike): boolean { + override hitTestLineSegment(shape: TLDrawShape, A: VecLike, B: VecLike): boolean { const outline = this.editor.getOutline(shape) if (shape.props.segments.length === 1 && shape.props.segments[0].points.length < 4) { @@ -156,7 +166,7 @@ export class DrawShapeUtil extends ShapeUtil { d={solidStrokePath} /> @@ -210,7 +220,7 @@ export class DrawShapeUtil extends ShapeUtil { return } - toSvg(shape: TLDrawShape, ctx: SvgExportContext) { + override toSvg(shape: TLDrawShape, ctx: SvgExportContext) { const theme = getDefaultColorTheme(this.editor) ctx.addExportDef(getFillDefForExport(shape.props.fill, theme)) @@ -240,7 +250,7 @@ export class DrawShapeUtil extends ShapeUtil { const strokeOutlinePoints = getStrokeOutlinePoints(strokePoints, options) const p = document.createElementNS('http://www.w3.org/2000/svg', 'path') - p.setAttribute('d', getSvgPathFromStroke(strokeOutlinePoints, true)) + p.setAttribute('d', getSvgPathFromPoints(strokeOutlinePoints, true)) p.setAttribute('fill', theme[color].solid) p.setAttribute('stroke-linecap', 'round') @@ -275,7 +285,7 @@ export class DrawShapeUtil extends ShapeUtil { return foregroundPath } - getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] { + override getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] { return [getFillDefForCanvas()] } @@ -304,7 +314,7 @@ export class DrawShapeUtil extends ShapeUtil { } } - expandSelectionOutlinePx(shape: TLDrawShape): number { + override expandSelectionOutlinePx(shape: TLDrawShape): number { const multiplier = shape.props.dash === 'draw' ? 1.6 : 1 return (STROKE_SIZES[shape.props.size] * multiplier) / 2 } diff --git a/packages/editor/src/lib/editor/shapes/draw/getPath.ts b/packages/tldraw/src/lib/shapes/draw/getPath.ts similarity index 93% rename from packages/editor/src/lib/editor/shapes/draw/getPath.ts rename to packages/tldraw/src/lib/shapes/draw/getPath.ts index 71405693a..3a84b7e3a 100644 --- a/packages/editor/src/lib/editor/shapes/draw/getPath.ts +++ b/packages/tldraw/src/lib/shapes/draw/getPath.ts @@ -1,5 +1,13 @@ -import { EASINGS, PI, SIN, StrokeOptions, Vec2d } from '@tldraw/primitives' -import { TLDefaultDashStyle, TLDrawShape, TLDrawShapeSegment } from '@tldraw/tlschema' +import { + EASINGS, + PI, + SIN, + TLDefaultDashStyle, + TLDrawShape, + TLDrawShapeSegment, + Vec2d, +} from '@tldraw/editor' +import { StrokeOptions } from '../shared/freehand/types' const PEN_EASING = (t: number) => t * 0.65 + SIN((t * PI) / 2) * 0.35 diff --git a/packages/editor/src/lib/editor/shapes/draw/toolStates/Drawing.ts b/packages/tldraw/src/lib/shapes/draw/toolStates/Drawing.ts similarity index 96% rename from packages/editor/src/lib/editor/shapes/draw/toolStates/Drawing.ts rename to packages/tldraw/src/lib/shapes/draw/toolStates/Drawing.ts index 0ecbdc851..7b0f8303a 100644 --- a/packages/editor/src/lib/editor/shapes/draw/toolStates/Drawing.ts +++ b/packages/tldraw/src/lib/shapes/draw/toolStates/Drawing.ts @@ -1,18 +1,22 @@ -import { Matrix2d, snapAngle, toFixed, Vec2d } from '@tldraw/primitives' import { - createShapeId, + DRAG_DISTANCE, + Matrix2d, + StateNode, TLDefaultSizeStyle, TLDrawShape, TLDrawShapeSegment, + TLEventHandlers, TLHighlightShape, + TLPointerEventInfo, TLShapePartial, + Vec2d, Vec2dModel, -} from '@tldraw/tlschema' -import { last, structuredClone } from '@tldraw/utils' -import { DRAG_DISTANCE } from '../../../../constants' -import { uniqueId } from '../../../../utils/data' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types' + createShapeId, + last, + snapAngle, + toFixed, + uniqueId, +} from '@tldraw/editor' import { STROKE_SIZES } from '../../shared/default-shape-constants' type DrawableShape = TLDrawShape | TLHighlightShape @@ -24,7 +28,7 @@ export class Drawing extends StateNode { initialShape?: DrawableShape - shapeType = this.parent.id === 'highlight' ? ('highlight' as const) : ('draw' as const) + override shapeType = this.parent.id === 'highlight' ? ('highlight' as const) : ('draw' as const) util = this.editor.getShapeUtil(this.shapeType) @@ -44,7 +48,7 @@ export class Drawing extends StateNode { canDraw = false - onEnter = (info: TLPointerEventInfo) => { + override onEnter = (info: TLPointerEventInfo) => { this.info = info this.canDraw = !this.editor.isMenuOpen this.lastRecordedPoint = this.editor.inputs.currentPagePoint.clone() @@ -53,7 +57,7 @@ export class Drawing extends StateNode { } } - onPointerMove: TLEventHandlers['onPointerMove'] = () => { + override onPointerMove: TLEventHandlers['onPointerMove'] = () => { const { editor: { inputs }, } = this @@ -91,7 +95,7 @@ export class Drawing extends StateNode { } } - onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { + override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { if (info.key === 'Shift') { switch (this.segmentMode) { case 'free': { @@ -108,7 +112,7 @@ export class Drawing extends StateNode { this.updateShapes() } - onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { + override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { if (info.key === 'Shift') { this.editor.snaps.clear() @@ -130,7 +134,7 @@ export class Drawing extends StateNode { this.updateShapes() } - onExit? = () => { + override onExit? = () => { this.editor.snaps.clear() this.pagePointWhereCurrentSegmentChanged = this.editor.inputs.currentPagePoint.clone() } diff --git a/packages/tldraw/src/lib/shapes/draw/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/draw/toolStates/Idle.ts new file mode 100644 index 000000000..555252982 --- /dev/null +++ b/packages/tldraw/src/lib/shapes/draw/toolStates/Idle.ts @@ -0,0 +1,17 @@ +import { StateNode, TLEventHandlers } from '@tldraw/editor' + +export class Idle extends StateNode { + static override id = 'idle' + + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + this.parent.transition('drawing', info) + } + + override onEnter = () => { + this.editor.cursor = { type: 'cross', rotation: 0 } + } + + override onCancel = () => { + this.editor.setCurrentTool('select') + } +} diff --git a/packages/editor/src/lib/editor/shapes/embed/EmbedShapeUtil.tsx b/packages/tldraw/src/lib/shapes/embed/EmbedShapeUtil.tsx similarity index 88% rename from packages/editor/src/lib/editor/shapes/embed/EmbedShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/embed/EmbedShapeUtil.tsx index 69024a24a..6700e4c24 100644 --- a/packages/editor/src/lib/editor/shapes/embed/EmbedShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/embed/EmbedShapeUtil.tsx @@ -1,20 +1,22 @@ /* eslint-disable react-hooks/rules-of-hooks */ -import { toDomPrecision } from '@tldraw/primitives' -import { useValue } from '@tldraw/state' + import { + BaseBoxShapeUtil, + HTMLContainer, TLEmbedShape, TLEmbedShapePermissions, + TLOnResizeHandler, + TLShapeUtilFlag, + embedShapeMigrations, embedShapePermissionDefaults, -} from '@tldraw/tlschema' -import * as React from 'react' + embedShapeProps, + toDomPrecision, + useIsEditing, + useValue, +} from '@tldraw/editor' import { useMemo } from 'react' -import { DefaultSpinner } from '../../../components/DefaultSpinner' -import { HTMLContainer } from '../../../components/HTMLContainer' -import { useIsEditing } from '../../../hooks/useIsEditing' -import { getRotatedBoxShadow } from '../../../utils/dom' -import { getEmbedInfo, getEmbedInfoUnsafely } from '../../../utils/embeds' -import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil' -import { TLOnResizeHandler, TLShapeUtilFlag } from '../ShapeUtil' +import { getEmbedInfo, getEmbedInfoUnsafely } from '../../utils/embeds' +import { getRotatedBoxShadow } from '../../utils/rotated-box-shadow' import { resizeBox } from '../shared/resizeBox' const getSandboxPermissions = (permissions: TLEmbedShapePermissions) => { @@ -27,6 +29,8 @@ const getSandboxPermissions = (permissions: TLEmbedShapePermissions) => { /** @public */ export class EmbedShapeUtil extends BaseBoxShapeUtil { static override type = 'embed' as const + static override props = embedShapeProps + static override migrations = embedShapeMigrations override hideSelectionBoundsBg: TLShapeUtilFlag = (shape) => !this.canResize(shape) override hideSelectionBoundsFg: TLShapeUtilFlag = (shape) => !this.canResize(shape) @@ -142,11 +146,7 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil { background: embedInfo?.definition.backgroundColor, }} /> - ) : ( - - - - )} + ) : null} ) } diff --git a/packages/editor/src/lib/editor/shapes/frame/FrameShapeTool.test.ts b/packages/tldraw/src/lib/shapes/frame/FrameShapeTool.test.ts similarity index 86% rename from packages/editor/src/lib/editor/shapes/frame/FrameShapeTool.test.ts rename to packages/tldraw/src/lib/shapes/frame/FrameShapeTool.test.ts index 6ac98e669..a3409fd19 100644 --- a/packages/editor/src/lib/editor/shapes/frame/FrameShapeTool.test.ts +++ b/packages/tldraw/src/lib/shapes/frame/FrameShapeTool.test.ts @@ -14,7 +14,7 @@ describe(FrameShapeTool, () => { it('Creates frame shapes on click-and-drag, supports undo and redo', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) @@ -35,7 +35,7 @@ describe(FrameShapeTool, () => { it('Creates frame shapes on click, supports undo and redo', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerUp(50, 50) @@ -55,26 +55,26 @@ describe(FrameShapeTool, () => { describe('When selecting the tool', () => { it('selects the tool and enters the idle state', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.expectPathToBe('root.frame.idle') }) }) describe('When in the idle state', () => { it('Enters pointing state on pointer down', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(100, 100) editor.expectPathToBe('root.frame.pointing') }) it('Switches back to select tool on cancel', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.cancel() editor.expectPathToBe('root.select.idle') }) it('Does nothing on interrupt', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.interrupt() editor.expectPathToBe('root.frame.idle') }) @@ -82,7 +82,7 @@ describe('When in the idle state', () => { describe('When in the pointing state', () => { it('Switches back to idle on cancel', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.expectPathToBe('root.frame.pointing') editor.cancel() @@ -90,7 +90,7 @@ describe('When in the pointing state', () => { }) it('Enters the select.resizing state on drag start', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerMove(51, 51) // not far enough! editor.expectPathToBe('root.frame.pointing') @@ -99,14 +99,14 @@ describe('When in the pointing state', () => { }) it('Enters the select.resizing state on pointer move', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.cancel() editor.expectPathToBe('root.frame.idle') }) it('Returns to the frame state on cancel', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.cancel() @@ -115,7 +115,7 @@ describe('When in the pointing state', () => { it('Creates a frame and returns to select tool on pointer up', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerUp(50, 50) editor.expectPathToBe('root.select.idle') @@ -123,9 +123,9 @@ describe('When in the pointing state', () => { }) it('Creates a frame and returns to frame.idle on pointer up if tool lock is enabled', () => { - editor.setToolLocked(true) + editor.isToolLocked = true expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerUp(50, 50) editor.expectPathToBe('root.frame.idle') @@ -135,7 +135,7 @@ describe('When in the pointing state', () => { describe('When in the resizing state', () => { it('Returns to frame on cancel', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerMove(55, 55) editor.expectPathToBe('root.select.resizing') @@ -144,7 +144,7 @@ describe('When in the resizing state', () => { }) it('Returns to select.idle on complete', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) @@ -152,8 +152,8 @@ describe('When in the resizing state', () => { }) it('Returns to frame.idle on complete if tool lock is enabled', () => { - editor.setToolLocked(true) - editor.setSelectedTool('frame') + editor.isToolLocked = true + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) @@ -162,7 +162,7 @@ describe('When in the resizing state', () => { }) it('Returns to the idle state on interrupt', () => { - editor.setSelectedTool('frame') + editor.setCurrentTool('frame') editor.pointerDown(50, 50) editor.interrupt() editor.expectPathToBe('root.frame.idle') diff --git a/packages/tldraw/src/lib/shapes/frame/FrameShapeTool.ts b/packages/tldraw/src/lib/shapes/frame/FrameShapeTool.ts new file mode 100644 index 000000000..27642c77c --- /dev/null +++ b/packages/tldraw/src/lib/shapes/frame/FrameShapeTool.ts @@ -0,0 +1,7 @@ +import { BaseBoxShapeTool } from '@tldraw/editor' + +export class FrameShapeTool extends BaseBoxShapeTool { + static override id = 'frame' + static override initial = 'idle' + override shapeType = 'frame' +} diff --git a/packages/editor/src/lib/editor/shapes/frame/FrameShapeUtil.tsx b/packages/tldraw/src/lib/shapes/frame/FrameShapeUtil.tsx similarity index 92% rename from packages/editor/src/lib/editor/shapes/frame/FrameShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/frame/FrameShapeUtil.tsx index 7acafd240..63acddf65 100644 --- a/packages/editor/src/lib/editor/shapes/frame/FrameShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/frame/FrameShapeUtil.tsx @@ -1,23 +1,35 @@ -import { canonicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives' import { - getDefaultColorTheme, + BaseBoxShapeUtil, + SVGContainer, + SelectionEdge, TLFrameShape, TLGroupShape, + TLOnResizeEndHandler, TLShape, TLShapeId, -} from '@tldraw/tlschema' -import { last } from '@tldraw/utils' -import { SVGContainer } from '../../../components/SVGContainer' -import { defaultEmptyAs } from '../../../utils/string' -import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil' -import { TLOnResizeEndHandler } from '../ShapeUtil' -import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans' + canonicalizeRotation, + frameShapeMigrations, + frameShapeProps, + getDefaultColorTheme, + last, + toDomPrecision, +} from '@tldraw/editor' import { useDefaultColorTheme } from '../shared/ShapeFill' +import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans' import { FrameHeading } from './components/FrameHeading' +export function defaultEmptyAs(str: string, dflt: string) { + if (str.match(/^\s*$/)) { + return dflt + } + return str +} + /** @public */ export class FrameShapeUtil extends BaseBoxShapeUtil { static override type = 'frame' as const + static override props = frameShapeProps + static override migrations = frameShapeMigrations override canBind = () => true @@ -176,7 +188,7 @@ export class FrameShapeUtil extends BaseBoxShapeUtil { return { shouldHint: false } } - onDragShapesOut = (_shape: TLFrameShape, shapes: TLShape[]): void => { + override onDragShapesOut = (_shape: TLFrameShape, shapes: TLShape[]): void => { const parent = this.editor.getShapeById(_shape.parentId) const isInGroup = parent && this.editor.isShapeOfType(parent, 'group') diff --git a/packages/editor/src/lib/editor/shapes/frame/components/FrameHeading.tsx b/packages/tldraw/src/lib/shapes/frame/components/FrameHeading.tsx similarity index 89% rename from packages/editor/src/lib/editor/shapes/frame/components/FrameHeading.tsx rename to packages/tldraw/src/lib/shapes/frame/components/FrameHeading.tsx index 12f0e80a5..84e1ca175 100644 --- a/packages/editor/src/lib/editor/shapes/frame/components/FrameHeading.tsx +++ b/packages/tldraw/src/lib/shapes/frame/components/FrameHeading.tsx @@ -1,8 +1,12 @@ -import { canonicalizeRotation, SelectionEdge, toDomPrecision } from '@tldraw/primitives' -import { TLShapeId } from '@tldraw/tlschema' +import { + SelectionEdge, + TLShapeId, + canonicalizeRotation, + toDomPrecision, + useEditor, + useIsEditing, +} from '@tldraw/editor' import { useEffect, useRef } from 'react' -import { useEditor } from '../../../../hooks/useEditor' -import { useIsEditing } from '../../../../hooks/useIsEditing' import { FrameLabelInput } from './FrameLabelInput' export const FrameHeading = function FrameHeading({ diff --git a/packages/editor/src/lib/editor/shapes/frame/components/FrameLabelInput.tsx b/packages/tldraw/src/lib/shapes/frame/components/FrameLabelInput.tsx similarity index 89% rename from packages/editor/src/lib/editor/shapes/frame/components/FrameLabelInput.tsx rename to packages/tldraw/src/lib/shapes/frame/components/FrameLabelInput.tsx index 0acca6dce..08e9f6ee8 100644 --- a/packages/editor/src/lib/editor/shapes/frame/components/FrameLabelInput.tsx +++ b/packages/tldraw/src/lib/shapes/frame/components/FrameLabelInput.tsx @@ -1,7 +1,6 @@ -import { TLFrameShape, TLShapeId } from '@tldraw/tlschema' +import { TLFrameShape, TLShapeId, useEditor } from '@tldraw/editor' import { forwardRef, useCallback } from 'react' -import { useEditor } from '../../../../hooks/useEditor' -import { defaultEmptyAs } from '../../../../utils/string' +import { defaultEmptyAs } from '../FrameShapeUtil' export const FrameLabelInput = forwardRef< HTMLInputElement, @@ -16,7 +15,7 @@ export const FrameLabelInput = forwardRef< // and sending us back into edit mode e.stopPropagation() e.currentTarget.blur() - editor.setEditingId(null) + editor.editingId = null } }, [editor] diff --git a/packages/editor/src/lib/editor/shapes/geo/GeoShapeTool.test.ts b/packages/tldraw/src/lib/shapes/geo/GeoShapeTool.test.ts similarity index 87% rename from packages/editor/src/lib/editor/shapes/geo/GeoShapeTool.test.ts rename to packages/tldraw/src/lib/shapes/geo/GeoShapeTool.test.ts index b552fda0f..b5b99885c 100644 --- a/packages/editor/src/lib/editor/shapes/geo/GeoShapeTool.test.ts +++ b/packages/tldraw/src/lib/shapes/geo/GeoShapeTool.test.ts @@ -14,7 +14,7 @@ describe(GeoShapeTool, () => { it('Creates geo shapes on click-and-drag, supports undo and redo', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) @@ -35,7 +35,7 @@ describe(GeoShapeTool, () => { it('Creates geo shapes on click, supports undo and redo', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerUp(50, 50) @@ -55,32 +55,32 @@ describe(GeoShapeTool, () => { describe('When selecting the tool', () => { it('selects the tool and enters the idle state', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.expectPathToBe('root.geo.idle') }) }) describe('When in the idle state', () => { it('Enters pointing state on pointer down', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(100, 100) editor.expectPathToBe('root.geo.pointing') }) it('Switches back to select tool on cancel', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.cancel() editor.expectPathToBe('root.select.idle') }) it('Does nothing on interrupt', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.interrupt() editor.expectPathToBe('root.geo.idle') }) it('Enters edit shape state on "Enter" key up when we have one geo shape', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) @@ -90,12 +90,12 @@ describe('When in the idle state', () => { }) it('Does not enter edit shape state on "Enter" key up when multiple geo shapes are selected', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(150, 150) editor.pointerMove(200, 200) editor.pointerUp(200, 200) @@ -112,7 +112,7 @@ describe('When in the idle state', () => { describe('When in the pointing state', () => { it('Switches back to idle on cancel', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.expectPathToBe('root.geo.pointing') editor.cancel() @@ -120,7 +120,7 @@ describe('When in the pointing state', () => { }) it('Enters the select.resizing state on drag start', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerMove(51, 51) // not far enough! editor.expectPathToBe('root.geo.pointing') @@ -129,14 +129,14 @@ describe('When in the pointing state', () => { }) it('Enters the select.resizing state on pointer move', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.cancel() editor.expectPathToBe('root.geo.idle') }) it('Returns to the idle state on interrupt', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.interrupt() editor.expectPathToBe('root.geo.idle') @@ -144,7 +144,7 @@ describe('When in the pointing state', () => { it('Creates a geo and returns to select tool on pointer up', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerUp(50, 50) editor.expectPathToBe('root.select.idle') @@ -152,9 +152,9 @@ describe('When in the pointing state', () => { }) it('Creates a geo and returns to geo.idle on pointer up if tool lock is enabled', () => { - editor.setToolLocked(true) + editor.isToolLocked = true expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerUp(50, 50) editor.expectPathToBe('root.geo.idle') @@ -164,7 +164,7 @@ describe('When in the pointing state', () => { describe('When in the resizing state while creating a geo shape', () => { it('Returns to geo on cancel', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerMove(55, 55) editor.expectPathToBe('root.select.resizing') @@ -173,7 +173,7 @@ describe('When in the resizing state while creating a geo shape', () => { }) it('Returns to select.idle on complete', () => { - editor.setSelectedTool('geo') + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) @@ -181,8 +181,8 @@ describe('When in the resizing state while creating a geo shape', () => { }) it('Returns to geo.idle on complete if tool lock is enabled', () => { - editor.setToolLocked(true) - editor.setSelectedTool('geo') + editor.isToolLocked = true + editor.setCurrentTool('geo') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) diff --git a/packages/editor/src/lib/editor/shapes/geo/GeoShapeTool.ts b/packages/tldraw/src/lib/shapes/geo/GeoShapeTool.ts similarity index 51% rename from packages/editor/src/lib/editor/shapes/geo/GeoShapeTool.ts rename to packages/tldraw/src/lib/shapes/geo/GeoShapeTool.ts index 79646ee5c..424177659 100644 --- a/packages/editor/src/lib/editor/shapes/geo/GeoShapeTool.ts +++ b/packages/tldraw/src/lib/shapes/geo/GeoShapeTool.ts @@ -1,11 +1,10 @@ -import { StateNode } from '../../tools/StateNode' +import { StateNode } from '@tldraw/editor' import { Idle } from './toolStates/Idle' import { Pointing } from './toolStates/Pointing' export class GeoShapeTool extends StateNode { static override id = 'geo' - static initial = 'idle' - static children = () => [Idle, Pointing] - - shapeType = 'geo' + static override initial = 'idle' + static override children = () => [Idle, Pointing] + override shapeType = 'geo' } diff --git a/packages/editor/src/lib/editor/shapes/geo/GeoShapeUtil.tsx b/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx similarity index 95% rename from packages/editor/src/lib/editor/shapes/geo/GeoShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx index d5ed348b9..4c6c3fcb1 100644 --- a/packages/editor/src/lib/editor/shapes/geo/GeoShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx @@ -1,27 +1,31 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { + BaseBoxShapeUtil, Box2d, - getPolygonVertices, - getRoundedInkyPolygonPath, - getRoundedPolygonPoints, - linesIntersect, + DefaultFontFamilies, + Editor, PI, PI2, - pointInPolygon, + SVGContainer, + SvgExportContext, TAU, - Vec2d, - VecLike, -} from '@tldraw/primitives' -import { - DefaultFontFamilies, - getDefaultColorTheme, TLDefaultDashStyle, TLGeoShape, -} from '@tldraw/tlschema' -import { SVGContainer } from '../../../components/SVGContainer' -import { Editor } from '../../Editor' -import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil' -import { TLOnEditEndHandler, TLOnResizeHandler, TLShapeUtilCanvasSvgDef } from '../ShapeUtil' + TLOnEditEndHandler, + TLOnResizeHandler, + TLShapeUtilCanvasSvgDef, + Vec2d, + VecLike, + geoShapeMigrations, + geoShapeProps, + getDefaultColorTheme, + getPolygonVertices, + linesIntersect, + pointInPolygon, +} from '@tldraw/editor' + +import { HyperlinkButton } from '../shared/HyperlinkButton' +import { TextLabel } from '../shared/TextLabel' import { FONT_FAMILIES, LABEL_FONT_SIZES, @@ -34,9 +38,7 @@ import { getFontDefForExport, } from '../shared/defaultStyleDefs' import { getTextLabelSvgElement } from '../shared/getTextLabelSvgElement' -import { HyperlinkButton } from '../shared/HyperlinkButton' -import { SvgExportContext } from '../shared/SvgExportContext' -import { TextLabel } from '../shared/TextLabel' +import { getRoundedInkyPolygonPath, getRoundedPolygonPoints } from '../shared/polygon-helpers' import { useForceSolid } from '../shared/useForceSolid' import { cloudOutline, cloudSvgPath } from './cloudOutline' import { DashStyleCloud, DashStyleCloudSvg } from './components/DashStyleCloud' @@ -49,9 +51,9 @@ import { DrawStylePolygon, DrawStylePolygonSvg } from './components/DrawStylePol import { SolidStyleCloud, SolidStyleCloudSvg } from './components/SolidStyleCloud' import { SolidStyleEllipse, SolidStyleEllipseSvg } from './components/SolidStyleEllipse' import { - getOvalIndicatorPath, SolidStyleOval, SolidStyleOvalSvg, + getOvalIndicatorPath, } from './components/SolidStyleOval' import { SolidStylePolygon, SolidStylePolygonSvg } from './components/SolidStylePolygon' @@ -61,8 +63,10 @@ const MIN_SIZE_WITH_LABEL = 17 * 3 /** @public */ export class GeoShapeUtil extends BaseBoxShapeUtil { static override type = 'geo' as const + static override props = geoShapeProps + static override migrations = geoShapeMigrations - canEdit = () => true + override canEdit = () => true override getDefaultProps(): TLGeoShape['props'] { return { @@ -83,7 +87,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { } } - hitTestLineSegment(shape: TLGeoShape, A: VecLike, B: VecLike): boolean { + override hitTestLineSegment(shape: TLGeoShape, A: VecLike, B: VecLike): boolean { const outline = this.editor.getOutline(shape) // Check the outline @@ -104,7 +108,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { return false } - hitTestPoint(shape: TLGeoShape, point: VecLike): boolean { + override hitTestPoint(shape: TLGeoShape, point: VecLike): boolean { const outline = this.editor.getOutline(shape) if (shape.props.fill === 'none') { @@ -131,15 +135,15 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { return pointInPolygon(point, outline) } - getBounds(shape: TLGeoShape) { + override getBounds(shape: TLGeoShape) { return new Box2d(0, 0, shape.props.w, shape.props.h + shape.props.growY) } - getCenter(shape: TLGeoShape) { + override getCenter(shape: TLGeoShape) { return new Vec2d(shape.props.w / 2, (shape.props.h + shape.props.growY) / 2) } - getOutline(shape: TLGeoShape) { + override getOutline(shape: TLGeoShape) { const w = Math.max(1, shape.props.w) const h = Math.max(1, shape.props.h + shape.props.growY) const cx = w / 2 @@ -332,7 +336,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { } } - onEditEnd: TLOnEditEndHandler = (shape) => { + override onEditEnd: TLOnEditEndHandler = (shape) => { const { id, type, @@ -565,7 +569,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { } } - toSvg(shape: TLGeoShape, ctx: SvgExportContext) { + override toSvg(shape: TLGeoShape, ctx: SvgExportContext) { const { id, props } = shape const strokeWidth = STROKE_SIZES[props.size] const theme = getDefaultColorTheme(this.editor) @@ -782,11 +786,11 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { return svgElm } - getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] { + override getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] { return [getFillDefForCanvas()] } - onResize: TLOnResizeHandler = ( + override onResize: TLOnResizeHandler = ( shape, { initialBounds, handle, newPoint, scaleX, scaleY } ) => { @@ -860,7 +864,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { } } - onBeforeCreate = (shape: TLGeoShape) => { + override onBeforeCreate = (shape: TLGeoShape) => { if (!shape.props.text) { if (shape.props.growY) { // No text / some growY, set growY to 0 @@ -901,7 +905,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { } } - onBeforeUpdate = (prev: TLGeoShape, next: TLGeoShape) => { + override onBeforeUpdate = (prev: TLGeoShape, next: TLGeoShape) => { const prevText = prev.props.text.trimEnd() const nextText = next.props.text.trimEnd() @@ -986,7 +990,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil { } } - onDoubleClick = (shape: TLGeoShape) => { + override onDoubleClick = (shape: TLGeoShape) => { // Little easter egg: double-clicking a rectangle / checkbox while // holding alt will toggle between check-box and rectangle if (this.editor.inputs.altKey) { diff --git a/packages/editor/src/lib/editor/shapes/geo/cloudOutline.ts b/packages/tldraw/src/lib/shapes/geo/cloudOutline.ts similarity index 97% rename from packages/editor/src/lib/editor/shapes/geo/cloudOutline.ts rename to packages/tldraw/src/lib/shapes/geo/cloudOutline.ts index a24618fb6..09cd4e15d 100644 --- a/packages/editor/src/lib/editor/shapes/geo/cloudOutline.ts +++ b/packages/tldraw/src/lib/shapes/geo/cloudOutline.ts @@ -1,6 +1,12 @@ -import { PI, Vec2d, getPointOnCircle, shortAngleDist } from '@tldraw/primitives' -import { TLDefaultSizeStyle, Vec2dModel } from '@tldraw/tlschema' -import { rng } from '@tldraw/utils' +import { + PI, + TLDefaultSizeStyle, + Vec2d, + Vec2dModel, + getPointOnCircle, + rng, + shortAngleDist, +} from '@tldraw/editor' function getPillCircumference(width: number, height: number) { const radius = Math.min(width, height) / 2 diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DashStyleCloud.tsx b/packages/tldraw/src/lib/shapes/geo/components/DashStyleCloud.tsx similarity index 95% rename from packages/editor/src/lib/editor/shapes/geo/components/DashStyleCloud.tsx rename to packages/tldraw/src/lib/shapes/geo/components/DashStyleCloud.tsx index f7a85ed6c..22447bc08 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/DashStyleCloud.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/DashStyleCloud.tsx @@ -1,5 +1,10 @@ -import { Vec2d, canonicalizeRotation } from '@tldraw/primitives' -import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' +import { + TLDefaultColorTheme, + TLGeoShape, + TLShapeId, + Vec2d, + canonicalizeRotation, +} from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DashStyleEllipse.tsx b/packages/tldraw/src/lib/shapes/geo/components/DashStyleEllipse.tsx similarity index 94% rename from packages/editor/src/lib/editor/shapes/geo/components/DashStyleEllipse.tsx rename to packages/tldraw/src/lib/shapes/geo/components/DashStyleEllipse.tsx index aa0bcc112..539d63188 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/DashStyleEllipse.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/DashStyleEllipse.tsx @@ -1,5 +1,10 @@ -import { perimeterOfEllipse, toDomPrecision } from '@tldraw/primitives' -import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' +import { + TLDefaultColorTheme, + TLGeoShape, + TLShapeId, + perimeterOfEllipse, + toDomPrecision, +} from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DashStyleOval.tsx b/packages/tldraw/src/lib/shapes/geo/components/DashStyleOval.tsx similarity index 94% rename from packages/editor/src/lib/editor/shapes/geo/components/DashStyleOval.tsx rename to packages/tldraw/src/lib/shapes/geo/components/DashStyleOval.tsx index fdeb79bcc..dadc62144 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/DashStyleOval.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/DashStyleOval.tsx @@ -1,5 +1,4 @@ -import { toDomPrecision } from '@tldraw/primitives' -import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' +import { TLDefaultColorTheme, TLGeoShape, TLShapeId, toDomPrecision } from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DashStylePolygon.tsx b/packages/tldraw/src/lib/shapes/geo/components/DashStylePolygon.tsx similarity index 97% rename from packages/editor/src/lib/editor/shapes/geo/components/DashStylePolygon.tsx rename to packages/tldraw/src/lib/shapes/geo/components/DashStylePolygon.tsx index a6f7ffb1d..3d71cd3e3 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/DashStylePolygon.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/DashStylePolygon.tsx @@ -1,5 +1,4 @@ -import { Vec2d, VecLike } from '@tldraw/primitives' -import { TLDefaultColorTheme, TLGeoShape } from '@tldraw/tlschema' +import { TLDefaultColorTheme, TLGeoShape, Vec2d, VecLike } from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DrawStyleCloud.tsx b/packages/tldraw/src/lib/shapes/geo/components/DrawStyleCloud.tsx similarity index 99% rename from packages/editor/src/lib/editor/shapes/geo/components/DrawStyleCloud.tsx rename to packages/tldraw/src/lib/shapes/geo/components/DrawStyleCloud.tsx index 6bcfd8d0e..4ed784c3d 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/DrawStyleCloud.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/DrawStyleCloud.tsx @@ -1,4 +1,4 @@ -import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' +import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DrawStyleEllipse.tsx b/packages/tldraw/src/lib/shapes/geo/components/DrawStyleEllipse.tsx similarity index 87% rename from packages/editor/src/lib/editor/shapes/geo/components/DrawStyleEllipse.tsx rename to packages/tldraw/src/lib/shapes/geo/components/DrawStyleEllipse.tsx index 95df5b3c3..50c967e92 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/DrawStyleEllipse.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/DrawStyleEllipse.tsx @@ -1,23 +1,27 @@ import { EASINGS, - getStrokeOutlinePoints, - getStrokePoints, - perimeterOfEllipse, PI2, - setStrokePointRadii, TAU, + TLDefaultColorTheme, + TLGeoShape, + TLShapeId, Vec2d, -} from '@tldraw/primitives' -import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' -import { rng } from '@tldraw/utils' + getSvgPathFromPoints, + perimeterOfEllipse, + rng, +} from '@tldraw/editor' + import * as React from 'react' -import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../../utils/svg' import { + ShapeFill, getShapeFillSvg, getSvgWithShapeFill, - ShapeFill, useDefaultColorTheme, } from '../../shared/ShapeFill' +import { getStrokeOutlinePoints } from '../../shared/freehand/getStrokeOutlinePoints' +import { getStrokePoints } from '../../shared/freehand/getStrokePoints' +import { setStrokePointRadii } from '../../shared/freehand/setStrokePointRadii' +import { getSvgPathFromStrokePoints } from '../../shared/freehand/svg' export const DrawStyleEllipse = React.memo(function DrawStyleEllipse({ id, @@ -123,7 +127,7 @@ export function getEllipseStrokePoints( export function getEllipsePath(id: string, width: number, height: number, strokeWidth: number) { const options = getEllipseStrokeOptions(strokeWidth) - return getSvgPathFromStroke( + return getSvgPathFromPoints( getStrokeOutlinePoints( setStrokePointRadii(getEllipseStrokePoints(id, width, height, strokeWidth), options), options diff --git a/packages/editor/src/lib/editor/shapes/geo/components/DrawStylePolygon.tsx b/packages/tldraw/src/lib/shapes/geo/components/DrawStylePolygon.tsx similarity index 97% rename from packages/editor/src/lib/editor/shapes/geo/components/DrawStylePolygon.tsx rename to packages/tldraw/src/lib/shapes/geo/components/DrawStylePolygon.tsx index 18be5c133..ec4e72165 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/DrawStylePolygon.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/DrawStylePolygon.tsx @@ -1,12 +1,12 @@ -import { getRoundedInkyPolygonPath, getRoundedPolygonPoints, VecLike } from '@tldraw/primitives' -import { TLDefaultColorTheme, TLGeoShape } from '@tldraw/tlschema' +import { TLDefaultColorTheme, TLGeoShape, VecLike } from '@tldraw/editor' import * as React from 'react' import { + ShapeFill, getShapeFillSvg, getSvgWithShapeFill, - ShapeFill, useDefaultColorTheme, } from '../../shared/ShapeFill' +import { getRoundedInkyPolygonPath, getRoundedPolygonPoints } from '../../shared/polygon-helpers' export const DrawStylePolygon = React.memo(function DrawStylePolygon({ id, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleCloud.tsx b/packages/tldraw/src/lib/shapes/geo/components/SolidStyleCloud.tsx similarity index 99% rename from packages/editor/src/lib/editor/shapes/geo/components/SolidStyleCloud.tsx rename to packages/tldraw/src/lib/shapes/geo/components/SolidStyleCloud.tsx index 5814dabc7..258826d93 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleCloud.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/SolidStyleCloud.tsx @@ -1,4 +1,4 @@ -import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/tlschema' +import { TLDefaultColorTheme, TLGeoShape, TLShapeId } from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleEllipse.tsx b/packages/tldraw/src/lib/shapes/geo/components/SolidStyleEllipse.tsx similarity index 96% rename from packages/editor/src/lib/editor/shapes/geo/components/SolidStyleEllipse.tsx rename to packages/tldraw/src/lib/shapes/geo/components/SolidStyleEllipse.tsx index 29b62cf80..af5fa98c2 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleEllipse.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/SolidStyleEllipse.tsx @@ -1,4 +1,4 @@ -import { TLDefaultColorTheme, TLGeoShape } from '@tldraw/tlschema' +import { TLDefaultColorTheme, TLGeoShape } from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleOval.tsx b/packages/tldraw/src/lib/shapes/geo/components/SolidStyleOval.tsx similarity index 96% rename from packages/editor/src/lib/editor/shapes/geo/components/SolidStyleOval.tsx rename to packages/tldraw/src/lib/shapes/geo/components/SolidStyleOval.tsx index 066ed55cf..1a75d0fb4 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/SolidStyleOval.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/SolidStyleOval.tsx @@ -1,4 +1,4 @@ -import { TLDefaultColorTheme, TLGeoShape } from '@tldraw/tlschema' +import { TLDefaultColorTheme, TLGeoShape } from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/components/SolidStylePolygon.tsx b/packages/tldraw/src/lib/shapes/geo/components/SolidStylePolygon.tsx similarity index 93% rename from packages/editor/src/lib/editor/shapes/geo/components/SolidStylePolygon.tsx rename to packages/tldraw/src/lib/shapes/geo/components/SolidStylePolygon.tsx index 65689ac12..183dad12c 100644 --- a/packages/editor/src/lib/editor/shapes/geo/components/SolidStylePolygon.tsx +++ b/packages/tldraw/src/lib/shapes/geo/components/SolidStylePolygon.tsx @@ -1,5 +1,4 @@ -import { VecLike } from '@tldraw/primitives' -import { TLDefaultColorTheme, TLGeoShape } from '@tldraw/tlschema' +import { TLDefaultColorTheme, TLGeoShape, VecLike } from '@tldraw/editor' import * as React from 'react' import { ShapeFill, diff --git a/packages/editor/src/lib/editor/shapes/geo/helpers.ts b/packages/tldraw/src/lib/shapes/geo/helpers.ts similarity index 92% rename from packages/editor/src/lib/editor/shapes/geo/helpers.ts rename to packages/tldraw/src/lib/shapes/geo/helpers.ts index d6edea626..46c2d79ff 100644 --- a/packages/editor/src/lib/editor/shapes/geo/helpers.ts +++ b/packages/tldraw/src/lib/shapes/geo/helpers.ts @@ -1,4 +1,4 @@ -import { perimeterOfEllipse } from '@tldraw/primitives' +import { perimeterOfEllipse } from '@tldraw/editor' export function getOvalSolidPath(w: number, h: number) { if (h > w) { diff --git a/packages/editor/src/lib/editor/shapes/geo/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/geo/toolStates/Idle.ts similarity index 51% rename from packages/editor/src/lib/editor/shapes/geo/toolStates/Idle.ts rename to packages/tldraw/src/lib/shapes/geo/toolStates/Idle.ts index 85d726617..86cc77760 100644 --- a/packages/editor/src/lib/editor/shapes/geo/toolStates/Idle.ts +++ b/packages/tldraw/src/lib/shapes/geo/toolStates/Idle.ts @@ -1,26 +1,24 @@ -import { TLGeoShape } from '@tldraw/tlschema' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' +import { StateNode, TLEventHandlers, TLGeoShape } from '@tldraw/editor' export class Idle extends StateNode { static override id = 'idle' - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { this.parent.transition('pointing', info) } - onEnter = () => { - this.editor.setCursor({ type: 'cross' }) + override onEnter = () => { + this.editor.cursor = { type: 'cross', rotation: 0 } } - onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { + override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { if (info.key === 'Enter') { const shape = this.editor.onlySelectedShape if (shape && this.editor.isShapeOfType(shape, 'geo')) { // 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) - this.editor.setSelectedTool('select.editing_shape', { + this.editor.editingId = shape.id + this.editor.setCurrentTool('select.editing_shape', { ...info, target: 'shape', shape, @@ -29,7 +27,7 @@ export class Idle extends StateNode { } } - onCancel = () => { - this.editor.setSelectedTool('select') + override onCancel = () => { + this.editor.setCurrentTool('select') } } diff --git a/packages/editor/src/lib/editor/shapes/geo/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/geo/toolStates/Pointing.ts similarity index 83% rename from packages/editor/src/lib/editor/shapes/geo/toolStates/Pointing.ts rename to packages/tldraw/src/lib/shapes/geo/toolStates/Pointing.ts index 0534d00bb..561ca4c96 100644 --- a/packages/editor/src/lib/editor/shapes/geo/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/geo/toolStates/Pointing.ts @@ -1,12 +1,17 @@ -import { Box2d, getStarBounds } from '@tldraw/primitives' -import { GeoShapeGeoStyle, TLGeoShape, createShapeId } from '@tldraw/tlschema' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' +import { + Box2d, + GeoShapeGeoStyle, + StateNode, + TLEventHandlers, + TLGeoShape, + createShapeId, + getStarBounds, +} from '@tldraw/editor' export class Pointing extends StateNode { static override id = 'pointing' - onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { + override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { const { originPagePoint } = this.editor.inputs @@ -29,7 +34,7 @@ export class Pointing extends StateNode { ]) this.editor.select(id) - this.editor.setSelectedTool('select.resizing', { + this.editor.setCurrentTool('select.resizing', { ...info, target: 'selection', handle: 'bottom_right', @@ -56,7 +61,7 @@ export class Pointing extends StateNode { this.cancel() } - complete() { + private complete() { const { originPagePoint } = this.editor.inputs const id = createShapeId() @@ -106,11 +111,11 @@ export class Pointing extends StateNode { if (this.editor.instanceState.isToolLocked) { this.parent.transition('idle', {}) } else { - this.editor.setSelectedTool('select', {}) + this.editor.setCurrentTool('select', {}) } } - cancel() { + private cancel() { this.parent.transition('idle', {}) } } diff --git a/packages/editor/src/lib/editor/shapes/highlight/HighlightShapeTool.test.ts b/packages/tldraw/src/lib/shapes/highlight/HighlightShapeTool.test.ts similarity index 100% rename from packages/editor/src/lib/editor/shapes/highlight/HighlightShapeTool.test.ts rename to packages/tldraw/src/lib/shapes/highlight/HighlightShapeTool.test.ts diff --git a/packages/editor/src/lib/editor/shapes/highlight/HighlightShapeTool.ts b/packages/tldraw/src/lib/shapes/highlight/HighlightShapeTool.ts similarity index 62% rename from packages/editor/src/lib/editor/shapes/highlight/HighlightShapeTool.ts rename to packages/tldraw/src/lib/shapes/highlight/HighlightShapeTool.ts index 38422d512..00a53a99f 100644 --- a/packages/editor/src/lib/editor/shapes/highlight/HighlightShapeTool.ts +++ b/packages/tldraw/src/lib/shapes/highlight/HighlightShapeTool.ts @@ -1,16 +1,15 @@ -import { StateNode } from '../../tools/StateNode' // shared custody +import { StateNode } from '@tldraw/editor' import { Drawing } from '../draw/toolStates/Drawing' import { Idle } from '../draw/toolStates/Idle' export class HighlightShapeTool extends StateNode { static override id = 'highlight' - static initial = 'idle' - static children = () => [Idle, Drawing] + static override initial = 'idle' + static override children = () => [Idle, Drawing] + override shapeType = 'highlight' - shapeType = 'highlight' - - onExit = () => { + override onExit = () => { const drawingState = this.children!['drawing'] as Drawing drawingState.initialShape = undefined } diff --git a/packages/editor/src/lib/editor/shapes/highlight/HighlightShapeUtil.tsx b/packages/tldraw/src/lib/shapes/highlight/HighlightShapeUtil.tsx similarity index 86% rename from packages/editor/src/lib/editor/shapes/highlight/HighlightShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/highlight/HighlightShapeUtil.tsx index 284fa171b..7d16d8eac 100644 --- a/packages/editor/src/lib/editor/shapes/highlight/HighlightShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/highlight/HighlightShapeUtil.tsx @@ -1,18 +1,26 @@ /* eslint-disable react-hooks/rules-of-hooks */ -import { Box2d, getStrokePoints, linesIntersect, Vec2d, VecLike } from '@tldraw/primitives' import { - getDefaultColorTheme, + Box2d, + SVGContainer, + ShapeUtil, TLDefaultColorTheme, TLDrawShapeSegment, TLHighlightShape, -} from '@tldraw/tlschema' -import { last, rng } from '@tldraw/utils' -import { SVGContainer } from '../../../components/SVGContainer' -import { getSvgPathFromStrokePoints } from '../../../utils/svg' + TLOnResizeHandler, + Vec2d, + VecLike, + getDefaultColorTheme, + highlightShapeMigrations, + highlightShapeProps, + last, + linesIntersect, + rng, +} from '@tldraw/editor' import { getHighlightFreehandSettings, getPointsFromSegments } from '../draw/getPath' -import { ShapeUtil, TLOnResizeHandler } from '../ShapeUtil' -import { FONT_SIZES } from '../shared/default-shape-constants' import { useDefaultColorTheme } from '../shared/ShapeFill' +import { FONT_SIZES } from '../shared/default-shape-constants' +import { getStrokePoints } from '../shared/freehand/getStrokePoints' +import { getSvgPathFromStrokePoints } from '../shared/freehand/svg' import { useColorSpace } from '../shared/useColorSpace' import { useForceSolid } from '../shared/useForceSolid' @@ -21,12 +29,14 @@ const UNDERLAY_OPACITY = 0.82 /** @public */ export class HighlightShapeUtil extends ShapeUtil { - static type = 'highlight' as const + static override type = 'highlight' as const + static override props = highlightShapeProps + static override migrations = highlightShapeMigrations - hideResizeHandles = (shape: TLHighlightShape) => getIsDot(shape) - hideRotateHandle = (shape: TLHighlightShape) => getIsDot(shape) - hideSelectionBoundsBg = (shape: TLHighlightShape) => getIsDot(shape) - hideSelectionBoundsFg = (shape: TLHighlightShape) => getIsDot(shape) + override hideResizeHandles = (shape: TLHighlightShape) => getIsDot(shape) + override hideRotateHandle = (shape: TLHighlightShape) => getIsDot(shape) + override hideSelectionBoundsBg = (shape: TLHighlightShape) => getIsDot(shape) + override hideSelectionBoundsFg = (shape: TLHighlightShape) => getIsDot(shape) override getDefaultProps(): TLHighlightShape['props'] { return { @@ -42,15 +52,15 @@ export class HighlightShapeUtil extends ShapeUtil { return Box2d.FromPoints(this.editor.getOutline(shape)) } - getOutline(shape: TLHighlightShape) { + override getOutline(shape: TLHighlightShape) { return getPointsFromSegments(shape.props.segments) } - getCenter(shape: TLHighlightShape): Vec2d { + override getCenter(shape: TLHighlightShape): Vec2d { return this.editor.getBounds(shape).center } - hitTestPoint(shape: TLHighlightShape, point: VecLike): boolean { + override hitTestPoint(shape: TLHighlightShape, point: VecLike): boolean { const outline = this.editor.getOutline(shape) const zoomLevel = this.editor.zoomLevel const offsetDist = getStrokeWidth(shape) / zoomLevel @@ -73,7 +83,7 @@ export class HighlightShapeUtil extends ShapeUtil { return false } - hitTestLineSegment(shape: TLHighlightShape, A: VecLike, B: VecLike): boolean { + override hitTestLineSegment(shape: TLHighlightShape, A: VecLike, B: VecLike): boolean { const outline = this.editor.getOutline(shape) if (shape.props.segments.length === 1 && shape.props.segments[0].points.length < 4) { @@ -108,7 +118,7 @@ export class HighlightShapeUtil extends ShapeUtil { ) } - backgroundComponent(shape: TLHighlightShape) { + override backgroundComponent(shape: TLHighlightShape) { return ( => { return new Promise((resolve, reject) => { @@ -50,6 +55,8 @@ async function getDataURIFromURL(url: string): Promise { /** @public */ export class ImageShapeUtil extends BaseBoxShapeUtil { static override type = 'image' as const + static override props = imageShapeProps + static override migrations = imageShapeMigrations override isAspectRatioLocked = () => true override canCrop = () => true @@ -71,7 +78,6 @@ export class ImageShapeUtil extends BaseBoxShapeUtil { const prefersReducedMotion = usePrefersReducedMotion() const [staticFrameSrc, setStaticFrameSrc] = useState('') - const { w, h } = shape.props const asset = shape.props.assetId ? this.editor.getAssetById(shape.props.assetId) : undefined if (asset?.type === 'bookmark') { @@ -140,11 +146,7 @@ export class ImageShapeUtil extends BaseBoxShapeUtil { }} draggable={false} /> - ) : ( - - - - )} + ) : null} {asset?.props.isAnimated && !shape.props.playing && (
GIF
)} @@ -165,7 +167,7 @@ export class ImageShapeUtil extends BaseBoxShapeUtil { return } - async toSvg(shape: TLImageShape) { + override async toSvg(shape: TLImageShape) { const g = document.createElementNS('http://www.w3.org/2000/svg', 'g') const asset = shape.props.assetId ? this.editor.getAssetById(shape.props.assetId) : null @@ -203,7 +205,7 @@ export class ImageShapeUtil extends BaseBoxShapeUtil { return g } - onDoubleClick = (shape: TLImageShape) => { + override onDoubleClick = (shape: TLImageShape) => { const asset = shape.props.assetId ? this.editor.getAssetById(shape.props.assetId) : undefined if (!asset) return @@ -224,7 +226,7 @@ export class ImageShapeUtil extends BaseBoxShapeUtil { ]) } - onDoubleClickEdge: TLOnDoubleClickHandler = (shape) => { + override onDoubleClickEdge: TLOnDoubleClickHandler = (shape) => { const props = shape.props if (!props) return diff --git a/packages/editor/src/lib/editor/shapes/line/LineShapeTool.test.ts b/packages/tldraw/src/lib/shapes/line/LineShapeTool.test.ts similarity index 85% rename from packages/editor/src/lib/editor/shapes/line/LineShapeTool.test.ts rename to packages/tldraw/src/lib/shapes/line/LineShapeTool.test.ts index 91b5aa3f3..768c1103d 100644 --- a/packages/editor/src/lib/editor/shapes/line/LineShapeTool.test.ts +++ b/packages/tldraw/src/lib/shapes/line/LineShapeTool.test.ts @@ -1,5 +1,4 @@ -import { TLLineShape } from '@tldraw/tlschema' -import { assert } from '@tldraw/utils' +import { TLLineShape, assert } from '@tldraw/editor' import { TestEditor } from '../../../test/TestEditor' let editor: TestEditor @@ -9,7 +8,7 @@ beforeEach(() => { }) it('enters the line state', () => { - editor.setSelectedTool('line') + editor.setCurrentTool('line') expect(editor.currentToolId).toBe('line') editor.expectPathToBe('root.line.idle') }) @@ -17,14 +16,14 @@ it('enters the line state', () => { describe('When in the idle state', () => { it('enters the pointing state and creates a shape on pointer down', () => { const shapesBefore = editor.shapesArray.length - editor.setSelectedTool('line').pointerDown(0, 0, { target: 'canvas' }) + editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }) const shapesAfter = editor.shapesArray.length expect(shapesAfter).toBe(shapesBefore + 1) editor.expectPathToBe('root.line.pointing') }) it('returns to select on cancel', () => { - editor.setSelectedTool('line') + editor.setCurrentTool('line') editor.cancel() editor.expectToBeIn('select.idle') }) @@ -33,7 +32,7 @@ describe('When in the idle state', () => { describe('When in the pointing state', () => { it('createes on pointer up', () => { const shapesBefore = editor.shapesArray.length - editor.setSelectedTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerUp(0, 0) + editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerUp(0, 0) const shapesAfter = editor.shapesArray.length expect(shapesAfter).toBe(shapesBefore + 1) expect(editor.hintingIds.length).toBe(0) @@ -42,7 +41,7 @@ describe('When in the pointing state', () => { it('bails on cancel', () => { const shapesBefore = editor.shapesArray.length - editor.setSelectedTool('line').pointerDown(0, 0, { target: 'canvas' }).cancel() + editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).cancel() const shapesAfter = editor.shapesArray.length expect(shapesAfter).toBe(shapesBefore) expect(editor.hintingIds.length).toBe(0) @@ -50,7 +49,7 @@ describe('When in the pointing state', () => { }) it('enters the dragging state on pointer move', () => { - editor.setSelectedTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) + editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) editor.expectPathToBe('root.select.dragging_handle') }) }) @@ -58,7 +57,7 @@ describe('When in the pointing state', () => { // This could be moved to dragging_handle describe('When dragging the line', () => { it('updates the line on pointer move', () => { - editor.setSelectedTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) + editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) const line = editor.shapesArray[editor.shapesArray.length - 1] editor.expectShapeToMatch(line, { id: line.id, @@ -78,7 +77,7 @@ describe('When dragging the line', () => { it('returns to select.idle, keeping shape, on pointer up', () => { const shapesBefore = editor.shapesArray.length editor - .setSelectedTool('line') + .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) @@ -89,10 +88,10 @@ describe('When dragging the line', () => { }) it('returns to line.idle, keeping shape, on pointer up if tool lock is enabled', () => { - editor.setToolLocked(true) + editor.isToolLocked = true const shapesBefore = editor.shapesArray.length editor - .setSelectedTool('line') + .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) @@ -105,7 +104,7 @@ describe('When dragging the line', () => { it('bails on cancel', () => { const shapesBefore = editor.shapesArray.length editor - .setSelectedTool('line') + .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .cancel() @@ -117,9 +116,9 @@ describe('When dragging the line', () => { describe('When extending the line with the shift-key in tool-lock mode', () => { it('extends a line by joining-the-dots', () => { - editor.setToolLocked(true) + editor.isToolLocked = true editor - .setSelectedTool('line') + .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) @@ -134,9 +133,9 @@ describe('When extending the line with the shift-key in tool-lock mode', () => { }) it('extends a line after a click by shift-click dragging', () => { - editor.setToolLocked(true) + editor.isToolLocked = true editor - .setSelectedTool('line') + .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerUp(0, 0) .keyDown('Shift') @@ -151,9 +150,9 @@ describe('When extending the line with the shift-key in tool-lock mode', () => { }) it('extends a line by shift-click dragging', () => { - editor.setToolLocked(true) + editor.isToolLocked = true editor - .setSelectedTool('line') + .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) @@ -169,9 +168,9 @@ describe('When extending the line with the shift-key in tool-lock mode', () => { }) it('extends a line by shift-clicking even after canceling a pointerdown', () => { - editor.setToolLocked(true) + editor.isToolLocked = true editor - .setSelectedTool('line') + .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) @@ -189,9 +188,9 @@ describe('When extending the line with the shift-key in tool-lock mode', () => { }) it('extends a line by shift-clicking even after canceling a pointermove', () => { - editor.setToolLocked(true) + editor.isToolLocked = true editor - .setSelectedTool('line') + .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) diff --git a/packages/editor/src/lib/editor/shapes/line/LineShapeTool.ts b/packages/tldraw/src/lib/shapes/line/LineShapeTool.ts similarity index 51% rename from packages/editor/src/lib/editor/shapes/line/LineShapeTool.ts rename to packages/tldraw/src/lib/shapes/line/LineShapeTool.ts index 98800889d..40682536d 100644 --- a/packages/editor/src/lib/editor/shapes/line/LineShapeTool.ts +++ b/packages/tldraw/src/lib/shapes/line/LineShapeTool.ts @@ -1,11 +1,11 @@ -import { StateNode } from '../../tools/StateNode' +import { StateNode } from '@tldraw/editor' import { Idle } from './toolStates/Idle' import { Pointing } from './toolStates/Pointing' export class LineShapeTool extends StateNode { static override id = 'line' - static initial = 'idle' - static children = () => [Idle, Pointing] + static override initial = 'idle' + static override children = () => [Idle, Pointing] - shapeType = 'line' + override shapeType = 'line' } diff --git a/packages/editor/src/lib/editor/shapes/line/LineShapeUtil.test.ts b/packages/tldraw/src/lib/shapes/line/LineShapeUtil.test.ts similarity index 97% rename from packages/editor/src/lib/editor/shapes/line/LineShapeUtil.test.ts rename to packages/tldraw/src/lib/shapes/line/LineShapeUtil.test.ts index f7433c759..cbaa2ed06 100644 --- a/packages/editor/src/lib/editor/shapes/line/LineShapeUtil.test.ts +++ b/packages/tldraw/src/lib/shapes/line/LineShapeUtil.test.ts @@ -1,5 +1,4 @@ -import { TLGeoShape, TLLineShape, createShapeId } from '@tldraw/tlschema' -import { deepCopy } from '@tldraw/utils' +import { TLGeoShape, TLLineShape, createShapeId, deepCopy } from '@tldraw/editor' import { TestEditor } from '../../../test/TestEditor' jest.mock('nanoid', () => { diff --git a/packages/editor/src/lib/editor/shapes/line/LineShapeUtil.tsx b/packages/tldraw/src/lib/shapes/line/LineShapeUtil.tsx similarity index 89% rename from packages/editor/src/lib/editor/shapes/line/LineShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/line/LineShapeUtil.tsx index 6c8693496..25ec2b994 100644 --- a/packages/editor/src/lib/editor/shapes/line/LineShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/line/LineShapeUtil.tsx @@ -1,22 +1,30 @@ /* eslint-disable react-hooks/rules-of-hooks */ -import { getIndexBetween, sortByIndex } from '@tldraw/indices' import { - CubicSpline2d, - Polyline2d, + SVGContainer, + ShapeUtil, + TLHandle, + TLLineShape, + TLOnHandleChangeHandler, + TLOnResizeHandler, Vec2d, VecLike, - getDrawLinePathData, + WeakMapCache, + deepCopy, + getDefaultColorTheme, + getIndexBetween, intersectLineSegmentPolyline, + lineShapeMigrations, + lineShapeProps, pointNearToPolyline, -} from '@tldraw/primitives' -import { TLHandle, TLLineShape, getDefaultColorTheme } from '@tldraw/tlschema' -import { deepCopy } from '@tldraw/utils' -import { SVGContainer } from '../../../components/SVGContainer' -import { WeakMapCache } from '../../../utils/WeakMapCache' -import { ShapeUtil, TLOnHandleChangeHandler, TLOnResizeHandler } from '../ShapeUtil' + sortByIndex, +} from '@tldraw/editor' + import { ShapeFill, useDefaultColorTheme } from '../shared/ShapeFill' import { STROKE_SIZES } from '../shared/default-shape-constants' import { getPerfectDashProps } from '../shared/getPerfectDashProps' +import { getDrawLinePathData } from '../shared/polygon-helpers' +import { CubicSpline2d } from '../shared/splines/CubicSpline2d' +import { Polyline2d } from '../shared/splines/Polyline2d' import { useForceSolid } from '../shared/useForceSolid' import { getLineDrawPath, getLineIndicatorPath, getLinePoints } from './components/getLinePath' import { getLineSvg } from './components/getLineSvg' @@ -27,6 +35,8 @@ const handlesCache = new WeakMapCache() /** @public */ export class LineShapeUtil extends ShapeUtil { static override type = 'line' as const + static override props = lineShapeProps + static override migrations = lineShapeMigrations override hideResizeHandles = () => true override hideRotateHandle = () => true @@ -67,7 +77,7 @@ export class LineShapeUtil extends ShapeUtil { return spline.bounds } - getHandles(shape: TLLineShape) { + override getHandles(shape: TLLineShape) { return handlesCache.get(shape.props, () => { const handles = shape.props.handles @@ -94,11 +104,11 @@ export class LineShapeUtil extends ShapeUtil { }) } - getOutline(shape: TLLineShape) { + override getOutline(shape: TLLineShape) { return getLinePoints(getSplineForLineShape(shape)) } - getOutlineSegments(shape: TLLineShape) { + override getOutlineSegments(shape: TLLineShape) { const spline = getSplineForLineShape(shape) return shape.props.spline === 'cubic' ? spline.segments.map((s) => s.lut) @@ -107,7 +117,7 @@ export class LineShapeUtil extends ShapeUtil { // Events - onResize: TLOnResizeHandler = (shape, info) => { + override onResize: TLOnResizeHandler = (shape, info) => { const { scaleX, scaleY } = info const handles = deepCopy(shape.props.handles) @@ -124,7 +134,7 @@ export class LineShapeUtil extends ShapeUtil { } } - onHandleChange: TLOnHandleChangeHandler = (shape, { handle }) => { + override onHandleChange: TLOnHandleChangeHandler = (shape, { handle }) => { const next = deepCopy(shape) switch (handle.id) { @@ -166,13 +176,13 @@ export class LineShapeUtil extends ShapeUtil { return next } - hitTestPoint(shape: TLLineShape, point: Vec2d): boolean { + override hitTestPoint(shape: TLLineShape, point: Vec2d): boolean { const zoomLevel = this.editor.zoomLevel const offsetDist = STROKE_SIZES[shape.props.size] / zoomLevel return pointNearToPolyline(point, this.editor.getOutline(shape), offsetDist) } - hitTestLineSegment(shape: TLLineShape, A: VecLike, B: VecLike): boolean { + override hitTestLineSegment(shape: TLLineShape, A: VecLike, B: VecLike): boolean { return intersectLineSegmentPolyline(A, B, this.editor.getOutline(shape)) !== null } @@ -337,7 +347,7 @@ export class LineShapeUtil extends ShapeUtil { return } - toSvg(shape: TLLineShape) { + override toSvg(shape: TLLineShape) { const theme = getDefaultColorTheme(this.editor) const color = theme[shape.props.color].solid const spline = getSplineForLineShape(shape) diff --git a/packages/editor/src/lib/editor/shapes/line/__snapshots__/LineShapeUtil.test.ts.snap b/packages/tldraw/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.ts.snap similarity index 96% rename from packages/editor/src/lib/editor/shapes/line/__snapshots__/LineShapeUtil.test.ts.snap rename to packages/tldraw/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.ts.snap index b2e94530f..2e8f62f9e 100644 --- a/packages/editor/src/lib/editor/shapes/line/__snapshots__/LineShapeUtil.test.ts.snap +++ b/packages/tldraw/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.ts.snap @@ -7,7 +7,7 @@ Object { "isLocked": false, "meta": Object {}, "opacity": 1, - "parentId": "page:id50", + "parentId": "page:id51", "props": Object { "color": "black", "dash": "draw", diff --git a/packages/editor/src/lib/editor/shapes/line/components/getLinePath.ts b/packages/tldraw/src/lib/shapes/line/components/getLinePath.ts similarity index 81% rename from packages/editor/src/lib/editor/shapes/line/components/getLinePath.ts rename to packages/tldraw/src/lib/shapes/line/components/getLinePath.ts index f645d2f5b..e594a856b 100644 --- a/packages/editor/src/lib/editor/shapes/line/components/getLinePath.ts +++ b/packages/tldraw/src/lib/shapes/line/components/getLinePath.ts @@ -1,13 +1,10 @@ -import { - CubicSpline2d, - getStrokeOutlinePoints, - getStrokePoints, - Polyline2d, - setStrokePointRadii, - Vec2d, -} from '@tldraw/primitives' -import { TLLineShape } from '@tldraw/tlschema' -import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../../utils/svg' +import { TLLineShape, Vec2d, getSvgPathFromPoints } from '@tldraw/editor' +import { getStrokeOutlinePoints } from '../../shared/freehand/getStrokeOutlinePoints' +import { getStrokePoints } from '../../shared/freehand/getStrokePoints' +import { setStrokePointRadii } from '../../shared/freehand/setStrokePointRadii' +import { getSvgPathFromStrokePoints } from '../../shared/freehand/svg' +import { CubicSpline2d } from '../../shared/splines/CubicSpline2d' +import { Polyline2d } from '../../shared/splines/Polyline2d' export function getLinePoints(spline: CubicSpline2d | Polyline2d) { const { segments } = spline @@ -101,7 +98,7 @@ export function getLineDrawPath( strokeWidth: number ) { const stroke = getLineDrawStrokeOutlinePoints(shape, spline, strokeWidth) - return getSvgPathFromStroke(stroke) + return getSvgPathFromPoints(stroke) } export function getLineSolidPath( @@ -110,7 +107,7 @@ export function getLineSolidPath( strokeWidth: number ) { const outlinePoints = getLineSolidStrokeOutlinePoints(shape, spline, strokeWidth) - return getSvgPathFromStroke(outlinePoints) + return getSvgPathFromPoints(outlinePoints) } export function getLineIndicatorPath( diff --git a/packages/editor/src/lib/editor/shapes/line/components/getLineSvg.ts b/packages/tldraw/src/lib/shapes/line/components/getLineSvg.ts similarity index 92% rename from packages/editor/src/lib/editor/shapes/line/components/getLineSvg.ts rename to packages/tldraw/src/lib/shapes/line/components/getLineSvg.ts index 6867e4c68..57c1f966d 100644 --- a/packages/editor/src/lib/editor/shapes/line/components/getLineSvg.ts +++ b/packages/tldraw/src/lib/shapes/line/components/getLineSvg.ts @@ -1,6 +1,7 @@ -import { CubicSpline2d, Polyline2d } from '@tldraw/primitives' -import { TLDefaultDashStyle, TLLineShape } from '@tldraw/tlschema' +import { TLDefaultDashStyle, TLLineShape } from '@tldraw/editor' import { getPerfectDashProps } from '../../shared/getPerfectDashProps' +import { CubicSpline2d } from '../../shared/splines/CubicSpline2d' +import { Polyline2d } from '../../shared/splines/Polyline2d' import { getLineDrawPath } from './getLinePath' export function getDrawLineShapeSvg({ diff --git a/packages/tldraw/src/lib/shapes/line/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/line/toolStates/Idle.ts new file mode 100644 index 000000000..f87b9aacb --- /dev/null +++ b/packages/tldraw/src/lib/shapes/line/toolStates/Idle.ts @@ -0,0 +1,20 @@ +import { StateNode, TLEventHandlers, TLShapeId } from '@tldraw/editor' + +export class Idle extends StateNode { + static override id = 'idle' + + private shapeId = '' as TLShapeId + + override onEnter = (info: { shapeId: TLShapeId }) => { + this.shapeId = info.shapeId + this.editor.cursor = { type: 'cross', rotation: 0 } + } + + override onPointerDown: TLEventHandlers['onPointerDown'] = () => { + this.parent.transition('pointing', { shapeId: this.shapeId }) + } + + override onCancel = () => { + this.editor.setCurrentTool('select') + } +} diff --git a/packages/editor/src/lib/editor/shapes/line/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/line/toolStates/Pointing.ts similarity index 85% rename from packages/editor/src/lib/editor/shapes/line/toolStates/Pointing.ts rename to packages/tldraw/src/lib/shapes/line/toolStates/Pointing.ts index 754de05a9..67686cc3e 100644 --- a/packages/editor/src/lib/editor/shapes/line/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/line/toolStates/Pointing.ts @@ -1,9 +1,18 @@ -import { getIndexAbove, sortByIndex } from '@tldraw/indices' -import { Matrix2d, Vec2d } from '@tldraw/primitives' -import { TLHandle, TLLineShape, TLShapeId, createShapeId } from '@tldraw/tlschema' -import { last, structuredClone } from '@tldraw/utils' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers, TLInterruptEvent } from '../../../types/event-types' +import { + Matrix2d, + StateNode, + TLEventHandlers, + TLHandle, + TLInterruptEvent, + TLLineShape, + TLShapeId, + Vec2d, + createShapeId, + getIndexAbove, + last, + sortByIndex, + structuredClone, +} from '@tldraw/editor' export class Pointing extends StateNode { static override id = 'pointing' @@ -12,7 +21,7 @@ export class Pointing extends StateNode { markPointId = '' - onEnter = (info: { shapeId?: TLShapeId }) => { + override onEnter = (info: { shapeId?: TLShapeId }) => { const { inputs } = this.editor const { currentPagePoint } = inputs @@ -93,7 +102,7 @@ export class Pointing extends StateNode { } } - onPointerMove: TLEventHandlers['onPointerMove'] = () => { + override onPointerMove: TLEventHandlers['onPointerMove'] = () => { if (!this.shape) return if (this.editor.inputs.isDragging) { @@ -103,7 +112,7 @@ export class Pointing extends StateNode { throw Error('No handles found') } - this.editor.setSelectedTool('select.dragging_handle', { + this.editor.setCurrentTool('select.dragging_handle', { shape: this.shape, isCreating: true, handle: last(handles)!, diff --git a/packages/editor/src/lib/editor/shapes/note/NoteShapeTool.test.ts b/packages/tldraw/src/lib/shapes/note/NoteShapeTool.test.ts similarity index 87% rename from packages/editor/src/lib/editor/shapes/note/NoteShapeTool.test.ts rename to packages/tldraw/src/lib/shapes/note/NoteShapeTool.test.ts index 04861eb14..de92def12 100644 --- a/packages/editor/src/lib/editor/shapes/note/NoteShapeTool.test.ts +++ b/packages/tldraw/src/lib/shapes/note/NoteShapeTool.test.ts @@ -14,7 +14,7 @@ describe(NoteShapeTool, () => { it('Creates note shapes on click-and-drag, supports undo and redo', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.pointerMove(100, 100) editor.pointerUp(100, 100) @@ -38,7 +38,7 @@ describe(NoteShapeTool, () => { it('Creates note shapes on click, supports undo and redo', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.pointerUp(50, 50) @@ -58,26 +58,26 @@ describe(NoteShapeTool, () => { describe('When selecting the tool', () => { it('selects the tool and enters the idle state', () => { - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.expectPathToBe('root.note.idle') }) }) describe('When in the idle state', () => { it('Enters pointing state on pointer down', () => { - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(100, 100) editor.expectPathToBe('root.note.pointing') }) it('Switches back to select tool on cancel', () => { - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.cancel() editor.expectPathToBe('root.select.idle') }) it('Does nothing on interrupt', () => { - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.interrupt() editor.expectPathToBe('root.note.idle') }) @@ -85,7 +85,7 @@ describe('When in the idle state', () => { describe('When in the pointing state', () => { it('Switches back to idle on cancel', () => { - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.expectPathToBe('root.note.pointing') editor.cancel() @@ -93,7 +93,7 @@ describe('When in the pointing state', () => { }) it('Enters the select.translating state on drag start', () => { - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.pointerMove(51, 51) // not far enough! editor.expectPathToBe('root.note.pointing') @@ -102,7 +102,7 @@ describe('When in the pointing state', () => { }) it('Returns to the note tool on cancel from translating', () => { - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.pointerMove(55, 55) editor.cancel() @@ -110,8 +110,8 @@ describe('When in the pointing state', () => { }) it('Returns to the note tool on complete from translating when tool lock is enabled', () => { - editor.setToolLocked(true) - editor.setSelectedTool('note') + editor.isToolLocked = true + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.pointerMove(55, 55) editor.pointerUp() @@ -119,7 +119,7 @@ describe('When in the pointing state', () => { }) it('Returns to the idle state on interrupt', () => { - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.interrupt() editor.expectPathToBe('root.note.idle') @@ -127,7 +127,7 @@ describe('When in the pointing state', () => { it('Creates a note and begins editing on pointer up', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.pointerUp(50, 50) editor.expectPathToBe('root.select.editing_shape') @@ -135,9 +135,9 @@ describe('When in the pointing state', () => { }) it('Creates a frame and returns to frame.idle on pointer up if tool lock is enabled', () => { - editor.setToolLocked(true) + editor.isToolLocked = true expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('note') + editor.setCurrentTool('note') editor.pointerDown(50, 50) editor.pointerUp(50, 50) editor.expectPathToBe('root.note.idle') diff --git a/packages/editor/src/lib/editor/shapes/note/NoteShapeTool.ts b/packages/tldraw/src/lib/shapes/note/NoteShapeTool.ts similarity index 51% rename from packages/editor/src/lib/editor/shapes/note/NoteShapeTool.ts rename to packages/tldraw/src/lib/shapes/note/NoteShapeTool.ts index bd6080b79..18319d51d 100644 --- a/packages/editor/src/lib/editor/shapes/note/NoteShapeTool.ts +++ b/packages/tldraw/src/lib/shapes/note/NoteShapeTool.ts @@ -1,11 +1,10 @@ -import { StateNode } from '../../tools/StateNode' +import { StateNode } from '@tldraw/editor' import { Idle } from './toolStates/Idle' import { Pointing } from './toolStates/Pointing' export class NoteShapeTool extends StateNode { static override id = 'note' - static initial = 'idle' - static children = () => [Idle, Pointing] - - shapeType = 'note' + static override initial = 'idle' + static override children = () => [Idle, Pointing] + override shapeType = 'note' } diff --git a/packages/editor/src/lib/editor/shapes/note/NoteShapeUtil.tsx b/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx similarity index 85% rename from packages/editor/src/lib/editor/shapes/note/NoteShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx index 0aac5947f..92e6a7b92 100644 --- a/packages/editor/src/lib/editor/shapes/note/NoteShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/note/NoteShapeUtil.tsx @@ -1,25 +1,36 @@ -import { Box2d, toDomPrecision, Vec2d } from '@tldraw/primitives' -import { DefaultFontFamilies, getDefaultColorTheme, TLNoteShape } from '@tldraw/tlschema' -import { Editor } from '../../Editor' -import { ShapeUtil, TLOnEditEndHandler } from '../ShapeUtil' +import { + Box2d, + DefaultFontFamilies, + Editor, + ShapeUtil, + SvgExportContext, + TLNoteShape, + TLOnEditEndHandler, + Vec2d, + getDefaultColorTheme, + noteShapeMigrations, + noteShapeProps, + toDomPrecision, +} from '@tldraw/editor' +import { HyperlinkButton } from '../shared/HyperlinkButton' +import { useDefaultColorTheme } from '../shared/ShapeFill' +import { TextLabel } from '../shared/TextLabel' import { FONT_FAMILIES, LABEL_FONT_SIZES, TEXT_PROPS } from '../shared/default-shape-constants' import { getFontDefForExport } from '../shared/defaultStyleDefs' import { getTextLabelSvgElement } from '../shared/getTextLabelSvgElement' -import { HyperlinkButton } from '../shared/HyperlinkButton' -import { useDefaultColorTheme } from '../shared/ShapeFill' -import { SvgExportContext } from '../shared/SvgExportContext' -import { TextLabel } from '../shared/TextLabel' const NOTE_SIZE = 200 /** @public */ export class NoteShapeUtil extends ShapeUtil { static override type = 'note' as const + static override props = noteShapeProps + static override migrations = noteShapeMigrations - canEdit = () => true - hideResizeHandles = () => true - hideSelectionBoundsBg = () => true - hideSelectionBoundsFg = () => true + override canEdit = () => true + override hideResizeHandles = () => true + override hideSelectionBoundsBg = () => true + override hideSelectionBoundsFg = () => true getDefaultProps(): TLNoteShape['props'] { return { @@ -43,11 +54,11 @@ export class NoteShapeUtil extends ShapeUtil { return new Box2d(0, 0, NOTE_SIZE, height) } - getOutline(shape: TLNoteShape) { + override getOutline(shape: TLNoteShape) { return this.editor.getBounds(shape).corners } - getCenter(_shape: TLNoteShape) { + override getCenter(_shape: TLNoteShape) { return new Vec2d(NOTE_SIZE / 2, this.getHeight(_shape) / 2) } @@ -109,7 +120,7 @@ export class NoteShapeUtil extends ShapeUtil { ) } - toSvg(shape: TLNoteShape, ctx: SvgExportContext) { + override toSvg(shape: TLNoteShape, ctx: SvgExportContext) { ctx.addExportDef(getFontDefForExport(shape.props.font)) const theme = getDefaultColorTheme(this.editor) const bounds = this.getBounds(shape) @@ -149,11 +160,11 @@ export class NoteShapeUtil extends ShapeUtil { return g } - onBeforeCreate = (next: TLNoteShape) => { + override onBeforeCreate = (next: TLNoteShape) => { return getGrowY(this.editor, next, next.props.growY) } - onBeforeUpdate = (prev: TLNoteShape, next: TLNoteShape) => { + override onBeforeUpdate = (prev: TLNoteShape, next: TLNoteShape) => { if ( prev.props.text === next.props.text && prev.props.font === next.props.font && @@ -165,7 +176,7 @@ export class NoteShapeUtil extends ShapeUtil { return getGrowY(this.editor, next, prev.props.growY) } - onEditEnd: TLOnEditEndHandler = (shape) => { + override onEditEnd: TLOnEditEndHandler = (shape) => { const { id, type, diff --git a/packages/tldraw/src/lib/shapes/note/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/note/toolStates/Idle.ts new file mode 100644 index 000000000..d66a59b04 --- /dev/null +++ b/packages/tldraw/src/lib/shapes/note/toolStates/Idle.ts @@ -0,0 +1,17 @@ +import { StateNode, TLEventHandlers } from '@tldraw/editor' + +export class Idle extends StateNode { + static override id = 'idle' + + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + this.parent.transition('pointing', info) + } + + override onEnter = () => { + this.editor.cursor = { type: 'cross', rotation: 0 } + } + + override onCancel = () => { + this.editor.setCurrentTool('select') + } +} diff --git a/packages/editor/src/lib/editor/shapes/note/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/note/toolStates/Pointing.ts similarity index 71% rename from packages/editor/src/lib/editor/shapes/note/toolStates/Pointing.ts rename to packages/tldraw/src/lib/shapes/note/toolStates/Pointing.ts index 8066e6096..e35bc4513 100644 --- a/packages/editor/src/lib/editor/shapes/note/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/note/toolStates/Pointing.ts @@ -1,6 +1,11 @@ -import { TLNoteShape, createShapeId } from '@tldraw/tlschema' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers, TLInterruptEvent, TLPointerEventInfo } from '../../../types/event-types' +import { + StateNode, + TLEventHandlers, + TLInterruptEvent, + TLNoteShape, + TLPointerEventInfo, + createShapeId, +} from '@tldraw/editor' export class Pointing extends StateNode { static override id = 'pointing' @@ -13,17 +18,17 @@ export class Pointing extends StateNode { markPointId = 'creating' - onEnter = () => { + override onEnter = () => { this.wasFocusedOnEnter = !this.editor.isMenuOpen } - onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { + override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { this.editor.mark(this.markPointId) const shape = this.createShape() if (!shape) return - this.editor.setSelectedTool('select.translating', { + this.editor.setCurrentTool('select.translating', { ...info, target: 'shape', shape, @@ -34,19 +39,19 @@ export class Pointing extends StateNode { } } - onPointerUp: TLEventHandlers['onPointerUp'] = () => { + override onPointerUp: TLEventHandlers['onPointerUp'] = () => { this.complete() } - onInterrupt: TLInterruptEvent = () => { + override onInterrupt: TLInterruptEvent = () => { this.cancel() } - onComplete: TLEventHandlers['onComplete'] = () => { + override onComplete: TLEventHandlers['onComplete'] = () => { this.complete() } - onCancel: TLEventHandlers['onCancel'] = () => { + override onCancel: TLEventHandlers['onCancel'] = () => { this.cancel() } @@ -63,8 +68,8 @@ export class Pointing extends StateNode { } else { if (!shape) return - this.editor.setEditingId(shape.id) - this.editor.setSelectedTool('select.editing_shape', { + this.editor.editingId = shape.id + this.editor.setCurrentTool('select.editing_shape', { ...this.info, target: 'shape', shape, diff --git a/packages/editor/src/lib/editor/shapes/shared/HyperlinkButton.tsx b/packages/tldraw/src/lib/shapes/shared/HyperlinkButton.tsx similarity index 94% rename from packages/editor/src/lib/editor/shapes/shared/HyperlinkButton.tsx rename to packages/tldraw/src/lib/shapes/shared/HyperlinkButton.tsx index ce82537ea..b2b68318a 100644 --- a/packages/editor/src/lib/editor/shapes/shared/HyperlinkButton.tsx +++ b/packages/tldraw/src/lib/shapes/shared/HyperlinkButton.tsx @@ -1,5 +1,5 @@ +import { stopEventPropagation } from '@tldraw/editor' import classNames from 'classnames' -import { stopEventPropagation } from '../../../utils/dom' const LINK_ICON = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' fill='none'%3E%3Cpath stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M13 5H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6M19 5h6m0 0v6m0-6L13 17'/%3E%3C/svg%3E" diff --git a/packages/editor/src/lib/editor/managers/ScribbleManager.ts b/packages/tldraw/src/lib/shapes/shared/ScribbleManager.ts similarity index 95% rename from packages/editor/src/lib/editor/managers/ScribbleManager.ts rename to packages/tldraw/src/lib/shapes/shared/ScribbleManager.ts index cfce32077..bb32b471a 100644 --- a/packages/editor/src/lib/editor/managers/ScribbleManager.ts +++ b/packages/tldraw/src/lib/shapes/shared/ScribbleManager.ts @@ -1,6 +1,4 @@ -import { Vec2d, VecLike } from '@tldraw/primitives' -import { TLScribble, Vec2dModel } from '@tldraw/tlschema' -import { TLTickEvent } from '../types/event-types' +import { TLScribble, TLTickEvent, Vec2d, Vec2dModel, VecLike } from '@tldraw/editor' /** @public */ export class ScribbleManager implements TLScribble { diff --git a/packages/editor/src/lib/editor/shapes/shared/ShapeFill.tsx b/packages/tldraw/src/lib/shapes/shared/ShapeFill.tsx similarity index 93% rename from packages/editor/src/lib/editor/shapes/shared/ShapeFill.tsx rename to packages/tldraw/src/lib/shapes/shared/ShapeFill.tsx index 29bf440a1..f8186889d 100644 --- a/packages/editor/src/lib/editor/shapes/shared/ShapeFill.tsx +++ b/packages/tldraw/src/lib/shapes/shared/ShapeFill.tsx @@ -1,13 +1,13 @@ -import { useValue } from '@tldraw/state' import { + HASH_PATTERN_ZOOM_NAMES, TLDefaultColorStyle, TLDefaultColorTheme, TLDefaultFillStyle, getDefaultColorTheme, -} from '@tldraw/tlschema' -import * as React from 'react' -import { HASH_PATTERN_ZOOM_NAMES } from '../../../constants' -import { useEditor } from '../../../hooks/useEditor' + useEditor, + useValue, +} from '@tldraw/editor' +import React from 'react' export interface ShapeFillProps { d: string diff --git a/packages/editor/src/lib/editor/shapes/text/TextHelpers.ts b/packages/tldraw/src/lib/shapes/shared/TextHelpers.ts similarity index 100% rename from packages/editor/src/lib/editor/shapes/text/TextHelpers.ts rename to packages/tldraw/src/lib/shapes/shared/TextHelpers.ts diff --git a/packages/editor/src/lib/editor/shapes/shared/TextLabel.tsx b/packages/tldraw/src/lib/shapes/shared/TextLabel.tsx similarity index 93% rename from packages/editor/src/lib/editor/shapes/shared/TextLabel.tsx rename to packages/tldraw/src/lib/shapes/shared/TextLabel.tsx index dfd4713ac..5f6294003 100644 --- a/packages/editor/src/lib/editor/shapes/shared/TextLabel.tsx +++ b/packages/tldraw/src/lib/shapes/shared/TextLabel.tsx @@ -6,13 +6,13 @@ import { TLDefaultSizeStyle, TLDefaultVerticalAlignStyle, TLShape, -} from '@tldraw/tlschema' + stopEventPropagation, +} from '@tldraw/editor' import React from 'react' -import { stopEventPropagation } from '../../../utils/dom' -import { isLegacyAlign } from '../../../utils/legacy' -import { TextHelpers } from '../text/TextHelpers' import { useDefaultColorTheme } from './ShapeFill' +import { TextHelpers } from './TextHelpers' import { LABEL_FONT_SIZES, TEXT_PROPS } from './default-shape-constants' +import { isLegacyAlign } from './legacyProps' import { useEditableText } from './useEditableText' export const TextLabel = React.memo(function TextLabel< diff --git a/packages/editor/src/lib/editor/shapes/shared/createTextSvgElementFromSpans.ts b/packages/tldraw/src/lib/shapes/shared/createTextSvgElementFromSpans.ts similarity index 94% rename from packages/editor/src/lib/editor/shapes/shared/createTextSvgElementFromSpans.ts rename to packages/tldraw/src/lib/shapes/shared/createTextSvgElementFromSpans.ts index 3ca712812..e29e01630 100644 --- a/packages/editor/src/lib/editor/shapes/shared/createTextSvgElementFromSpans.ts +++ b/packages/tldraw/src/lib/shapes/shared/createTextSvgElementFromSpans.ts @@ -1,11 +1,14 @@ -import { Box2d } from '@tldraw/primitives' import { + Box2d, Box2dModel, + Editor, TLDefaultHorizontalAlignStyle, TLDefaultVerticalAlignStyle, -} from '@tldraw/tlschema' -import { correctSpacesToNbsp } from '../../../utils/string' -import { Editor } from '../../Editor' +} from '@tldraw/editor' + +function correctSpacesToNbsp(input: string) { + return input.replace(/\s/g, '\xa0') +} /** Get an SVG element for a text shape. */ export function createTextSvgElementFromSpans( diff --git a/packages/editor/src/lib/editor/shapes/shared/default-shape-constants.ts b/packages/tldraw/src/lib/shapes/shared/default-shape-constants.ts similarity index 99% rename from packages/editor/src/lib/editor/shapes/shared/default-shape-constants.ts rename to packages/tldraw/src/lib/shapes/shared/default-shape-constants.ts index 2fcd6e740..78d66290d 100644 --- a/packages/editor/src/lib/editor/shapes/shared/default-shape-constants.ts +++ b/packages/tldraw/src/lib/shapes/shared/default-shape-constants.ts @@ -1,4 +1,4 @@ -import { TLDefaultFontStyle, TLDefaultSizeStyle } from '@tldraw/tlschema' +import { TLDefaultFontStyle, TLDefaultSizeStyle } from '@tldraw/editor' /** @public */ export const TEXT_PROPS = { diff --git a/packages/editor/src/lib/editor/shapes/shared/defaultStyleDefs.tsx b/packages/tldraw/src/lib/shapes/shared/defaultStyleDefs.tsx similarity index 95% rename from packages/editor/src/lib/editor/shapes/shared/defaultStyleDefs.tsx rename to packages/tldraw/src/lib/shapes/shared/defaultStyleDefs.tsx index 65cbfe14e..f9ac852a5 100644 --- a/packages/editor/src/lib/editor/shapes/shared/defaultStyleDefs.tsx +++ b/packages/tldraw/src/lib/shapes/shared/defaultStyleDefs.tsx @@ -2,16 +2,17 @@ import { DefaultColorThemePalette, DefaultFontFamilies, DefaultFontStyle, + HASH_PATTERN_ZOOM_NAMES, + MAX_ZOOM, + SvgExportDef, TLDefaultColorTheme, TLDefaultFillStyle, TLDefaultFontStyle, -} from '@tldraw/tlschema' + TLShapeUtilCanvasSvgDef, + debugFlags, + useEditor, +} from '@tldraw/editor' import { useEffect, useMemo, useRef, useState } from 'react' -import { HASH_PATTERN_ZOOM_NAMES, MAX_ZOOM } from '../../../constants' -import { useEditor } from '../../../hooks/useEditor' -import { debugFlags } from '../../../utils/debug-flags' -import { TLShapeUtilCanvasSvgDef } from '../ShapeUtil' -import { SvgExportDef } from './SvgExportContext' /** @public */ export function getFontDefForExport(fontStyle: TLDefaultFontStyle): SvgExportDef { diff --git a/packages/primitives/src/lib/freehand/getStroke.ts b/packages/tldraw/src/lib/shapes/shared/freehand/getStroke.ts similarity index 93% rename from packages/primitives/src/lib/freehand/getStroke.ts rename to packages/tldraw/src/lib/shapes/shared/freehand/getStroke.ts index 27cb28288..5e504ef6d 100644 --- a/packages/primitives/src/lib/freehand/getStroke.ts +++ b/packages/tldraw/src/lib/shapes/shared/freehand/getStroke.ts @@ -1,4 +1,4 @@ -import { Vec2d, VecLike } from '../Vec2d' +import { Vec2d, VecLike } from '@tldraw/editor' import { getStrokeOutlinePoints } from './getStrokeOutlinePoints' import { getStrokePoints } from './getStrokePoints' import { setStrokePointRadii } from './setStrokePointRadii' diff --git a/packages/primitives/src/lib/freehand/getStrokeOutlinePoints.ts b/packages/tldraw/src/lib/shapes/shared/freehand/getStrokeOutlinePoints.ts similarity index 99% rename from packages/primitives/src/lib/freehand/getStrokeOutlinePoints.ts rename to packages/tldraw/src/lib/shapes/shared/freehand/getStrokeOutlinePoints.ts index a67f5d756..5636b9476 100644 --- a/packages/primitives/src/lib/freehand/getStrokeOutlinePoints.ts +++ b/packages/tldraw/src/lib/shapes/shared/freehand/getStrokeOutlinePoints.ts @@ -1,4 +1,4 @@ -import { Vec2d } from '../Vec2d' +import { Vec2d } from '@tldraw/editor' import type { StrokeOptions, StrokePoint } from './types' const { PI } = Math diff --git a/packages/primitives/src/lib/freehand/getStrokePoints.ts b/packages/tldraw/src/lib/shapes/shared/freehand/getStrokePoints.ts similarity index 99% rename from packages/primitives/src/lib/freehand/getStrokePoints.ts rename to packages/tldraw/src/lib/shapes/shared/freehand/getStrokePoints.ts index cd2ac1c9c..fccb5ab58 100644 --- a/packages/primitives/src/lib/freehand/getStrokePoints.ts +++ b/packages/tldraw/src/lib/shapes/shared/freehand/getStrokePoints.ts @@ -1,4 +1,4 @@ -import { Vec2d, VecLike } from '../Vec2d' +import { Vec2d, VecLike } from '@tldraw/editor' import type { StrokeOptions, StrokePoint } from './types' const MIN_START_PRESSURE = 0.025 diff --git a/packages/primitives/src/lib/freehand/getStrokeRadius.ts b/packages/tldraw/src/lib/shapes/shared/freehand/getStrokeRadius.ts similarity index 100% rename from packages/primitives/src/lib/freehand/getStrokeRadius.ts rename to packages/tldraw/src/lib/shapes/shared/freehand/getStrokeRadius.ts diff --git a/packages/primitives/src/lib/freehand/setStrokePointRadii.ts b/packages/tldraw/src/lib/shapes/shared/freehand/setStrokePointRadii.ts similarity index 98% rename from packages/primitives/src/lib/freehand/setStrokePointRadii.ts rename to packages/tldraw/src/lib/shapes/shared/freehand/setStrokePointRadii.ts index 7b55459c7..57d86dfab 100644 --- a/packages/primitives/src/lib/freehand/setStrokePointRadii.ts +++ b/packages/tldraw/src/lib/shapes/shared/freehand/setStrokePointRadii.ts @@ -1,4 +1,4 @@ -import { EASINGS } from '../easings' +import { EASINGS } from '@tldraw/editor' import { StrokeOptions, StrokePoint } from './types' const { min } = Math diff --git a/packages/tldraw/src/lib/shapes/shared/freehand/svg.ts b/packages/tldraw/src/lib/shapes/shared/freehand/svg.ts new file mode 100644 index 000000000..f0284b1aa --- /dev/null +++ b/packages/tldraw/src/lib/shapes/shared/freehand/svg.ts @@ -0,0 +1,50 @@ +import { average, precise } from '@tldraw/editor' +import { StrokePoint } from './types' + +/** + * Turn an array of stroke points into a path of quadradic curves. + * + * @param points - The stroke points returned from perfect-freehand + * @param closed - Whether the shape is closed + */ +export function getSvgPathFromStrokePoints(points: StrokePoint[], closed = false): string { + const len = points.length + + if (len < 2) { + return '' + } + + let a = points[0].point + let b = points[1].point + + if (len === 2) { + return `M${precise(a)}L${precise(b)}` + } + + let result = '' + + for (let i = 2, max = len - 1; i < max; i++) { + a = points[i].point + b = points[i + 1].point + result += average(a, b) + } + + if (closed) { + // If closed, draw a curve from the last point to the first + return `M${average(points[0].point, points[1].point)}Q${precise(points[1].point)}${average( + points[1].point, + points[2].point + )}T${result}${average(points[len - 1].point, points[0].point)}${average( + points[0].point, + points[1].point + )}Z` + } else { + // If not closed, draw a curve starting at the first point and + // ending at the midpoint of the last and second-last point, then + // complete the curve with a line segment to the last point. + return `M${precise(points[0].point)}Q${precise(points[1].point)}${average( + points[1].point, + points[2].point + )}${points.length > 3 ? 'T' : ''}${result}L${precise(points[len - 1].point)}` + } +} diff --git a/packages/primitives/src/lib/freehand/types.ts b/packages/tldraw/src/lib/shapes/shared/freehand/types.ts similarity index 96% rename from packages/primitives/src/lib/freehand/types.ts rename to packages/tldraw/src/lib/shapes/shared/freehand/types.ts index e1bfde25e..0a631e70f 100644 --- a/packages/primitives/src/lib/freehand/types.ts +++ b/packages/tldraw/src/lib/shapes/shared/freehand/types.ts @@ -1,4 +1,4 @@ -import { Vec2d } from '../Vec2d' +import { Vec2d } from '@tldraw/editor' /** * The options object for `getStroke` or `getStrokePoints`. diff --git a/packages/editor/src/lib/utils/getBrowserCanvasMaxSize.tsx b/packages/tldraw/src/lib/shapes/shared/getBrowserCanvasMaxSize.tsx similarity index 100% rename from packages/editor/src/lib/utils/getBrowserCanvasMaxSize.tsx rename to packages/tldraw/src/lib/shapes/shared/getBrowserCanvasMaxSize.tsx diff --git a/packages/tldraw/src/lib/shapes/shared/getPerfectDashProps.ts b/packages/tldraw/src/lib/shapes/shared/getPerfectDashProps.ts new file mode 100644 index 000000000..ecac502fa --- /dev/null +++ b/packages/tldraw/src/lib/shapes/shared/getPerfectDashProps.ts @@ -0,0 +1,96 @@ +import { TLDefaultDashStyle } from '@tldraw/editor' + +export function getPerfectDashProps( + totalLength: number, + strokeWidth: number, + opts = {} as Partial<{ + style: TLDefaultDashStyle + snap: number + end: 'skip' | 'outset' | 'none' + start: 'skip' | 'outset' | 'none' + lengthRatio: number + closed: boolean + }> +): { + strokeDasharray: string + strokeDashoffset: string +} { + const { + closed = false, + snap = 1, + start = 'outset', + end = 'outset', + lengthRatio = 2, + style = 'dashed', + } = opts + + let dashLength = 0 + let dashCount = 0 + let ratio = 1 + let gapLength = 0 + let strokeDashoffset = 0 + + switch (style) { + case 'dashed': { + ratio = 1 + dashLength = Math.min(strokeWidth * lengthRatio, totalLength / 4) + break + } + case 'dotted': { + ratio = 100 + dashLength = strokeWidth / ratio + break + } + default: { + return { + strokeDasharray: 'none', + strokeDashoffset: 'none', + } + } + } + + if (!closed) { + if (start === 'outset') { + totalLength += dashLength / 2 + strokeDashoffset += dashLength / 2 + } else if (start === 'skip') { + totalLength -= dashLength + strokeDashoffset -= dashLength + } + + if (end === 'outset') { + totalLength += dashLength / 2 + } else if (end === 'skip') { + totalLength -= dashLength + } + } + + dashCount = Math.floor(totalLength / dashLength / (2 * ratio)) + dashCount -= dashCount % snap + + if (dashCount < 3 && style === 'dashed') { + if (totalLength / strokeWidth < 5) { + dashLength = totalLength + dashCount = 1 + gapLength = 0 + } else { + dashLength = totalLength * 0.333 + gapLength = totalLength * 0.333 + } + } else { + dashCount = Math.max(dashCount, 3) + dashLength = totalLength / dashCount / (2 * ratio) + + if (closed) { + strokeDashoffset = dashLength / 2 + gapLength = (totalLength - dashCount * dashLength) / dashCount + } else { + gapLength = (totalLength - dashCount * dashLength) / Math.max(1, dashCount - 1) + } + } + + return { + strokeDasharray: [dashLength, gapLength].join(' '), + strokeDashoffset: strokeDashoffset.toString(), + } +} diff --git a/packages/editor/src/lib/editor/shapes/shared/getTextLabelSvgElement.ts b/packages/tldraw/src/lib/shapes/shared/getTextLabelSvgElement.ts similarity index 83% rename from packages/editor/src/lib/editor/shapes/shared/getTextLabelSvgElement.ts rename to packages/tldraw/src/lib/shapes/shared/getTextLabelSvgElement.ts index e87e03a57..4c2325d78 100644 --- a/packages/editor/src/lib/editor/shapes/shared/getTextLabelSvgElement.ts +++ b/packages/tldraw/src/lib/shapes/shared/getTextLabelSvgElement.ts @@ -1,9 +1,7 @@ -import { Box2d } from '@tldraw/primitives' -import { TLGeoShape, TLNoteShape } from '@tldraw/tlschema' -import { getLegacyOffsetX } from '../../../utils/legacy' -import { Editor } from '../../Editor' +import { Box2d, Editor, TLGeoShape, TLNoteShape } from '@tldraw/editor' import { createTextSvgElementFromSpans } from './createTextSvgElementFromSpans' import { LABEL_FONT_SIZES, TEXT_PROPS } from './default-shape-constants' +import { getLegacyOffsetX } from './legacyProps' export function getTextLabelSvgElement({ bounds, diff --git a/packages/editor/src/lib/utils/legacy.ts b/packages/tldraw/src/lib/shapes/shared/legacyProps.ts similarity index 87% rename from packages/editor/src/lib/utils/legacy.ts rename to packages/tldraw/src/lib/shapes/shared/legacyProps.ts index b3844683e..6a0c008df 100644 --- a/packages/editor/src/lib/utils/legacy.ts +++ b/packages/tldraw/src/lib/shapes/shared/legacyProps.ts @@ -1,5 +1,4 @@ -import { Box2d } from '@tldraw/primitives' -import { Box2dModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema' +import { Box2d, Box2dModel, TLDefaultHorizontalAlignStyle } from '@tldraw/editor' export function getLegacyOffsetX( align: TLDefaultHorizontalAlignStyle | string, diff --git a/packages/primitives/src/lib/polygon-helpers.ts b/packages/tldraw/src/lib/shapes/shared/polygon-helpers.ts similarity index 97% rename from packages/primitives/src/lib/polygon-helpers.ts rename to packages/tldraw/src/lib/shapes/shared/polygon-helpers.ts index e26773c0a..c8a3a7ff7 100644 --- a/packages/primitives/src/lib/polygon-helpers.ts +++ b/packages/tldraw/src/lib/shapes/shared/polygon-helpers.ts @@ -1,5 +1,4 @@ -import { Vec2d, VecLike } from './Vec2d' -import { toDomPrecision } from './utils' +import { Vec2d, VecLike, toDomPrecision } from '@tldraw/editor' function precise(A: VecLike) { return `${toDomPrecision(A.x)},${toDomPrecision(A.y)} ` diff --git a/packages/tldraw/src/lib/shapes/shared/resizeBox.ts b/packages/tldraw/src/lib/shapes/shared/resizeBox.ts new file mode 100644 index 000000000..74eae15ea --- /dev/null +++ b/packages/tldraw/src/lib/shapes/shared/resizeBox.ts @@ -0,0 +1,130 @@ +import { + Box2d, + TLBaseBoxShape, + TLResizeHandle, + TLResizeMode, + Vec2d, + Vec2dModel, +} from '@tldraw/editor' + +/** @public */ +export type ResizeBoxOptions = Partial<{ + minWidth: number + maxWidth: number + minHeight: number + maxHeight: number +}> + +/** @public */ +export function resizeBox( + shape: TLBaseBoxShape, + info: { + newPoint: Vec2dModel + handle: TLResizeHandle + mode: TLResizeMode + scaleX: number + scaleY: number + initialBounds: Box2d + initialShape: TLBaseBoxShape + }, + opts = {} as ResizeBoxOptions +) { + const { newPoint, handle, scaleX, scaleY } = info + const { minWidth = 1, maxWidth = Infinity, minHeight = 1, maxHeight = Infinity } = opts + + let w = shape.props.w * scaleX + let h = shape.props.h * scaleY + + const offset = new Vec2d(0, 0) + + if (w > 0) { + if (w < minWidth) { + switch (handle) { + case 'top_left': + case 'left': + case 'bottom_left': { + offset.x = w - minWidth + break + } + case 'top': + case 'bottom': { + offset.x = (w - minWidth) / 2 + break + } + default: { + offset.x = 0 + } + } + w = minWidth + } + } else { + offset.x = w + w = -w + if (w < minWidth) { + switch (handle) { + case 'top_left': + case 'left': + case 'bottom_left': { + offset.x = -w + break + } + default: { + offset.x = -minWidth + } + } + + w = minWidth + } + } + + if (h > 0) { + if (h < minHeight) { + switch (handle) { + case 'top_left': + case 'top': + case 'top_right': { + offset.y = h - minHeight + break + } + case 'right': + case 'left': { + offset.y = (h - minHeight) / 2 + break + } + default: { + offset.y = 0 + } + } + + h = minHeight + } + } else { + offset.y = h + h = -h + if (h < minHeight) { + switch (handle) { + case 'top_left': + case 'top': + case 'top_right': { + offset.y = -h + break + } + default: { + offset.y = -minHeight + } + } + h = minHeight + } + } + + const { x, y } = offset.rot(shape.rotation).add(newPoint) + + return { + x, + y, + props: { + w: Math.min(maxWidth, w), + h: Math.min(maxHeight, h), + }, + } +} diff --git a/packages/tldraw/src/lib/shapes/shared/resizeScaled.ts b/packages/tldraw/src/lib/shapes/shared/resizeScaled.ts new file mode 100644 index 000000000..97672905c --- /dev/null +++ b/packages/tldraw/src/lib/shapes/shared/resizeScaled.ts @@ -0,0 +1,40 @@ +import { Box2d, TLShape, Vec2d, Vec2dModel } from '@tldraw/editor' + +export function resizeScaled( + shape: Extract, + { + initialBounds, + scaleX, + scaleY, + newPoint, + }: { + newPoint: Vec2dModel + initialBounds: Box2d + scaleX: number + scaleY: number + } +) { + // Compute the new scale (to apply to the scale prop) + const scaleDelta = Math.max(0.01, Math.min(Math.abs(scaleX), Math.abs(scaleY))) + + // Compute the offset (if flipped X or flipped Y) + const offset = new Vec2d(0, 0) + + if (scaleX < 0) { + offset.x = -(initialBounds.width * scaleDelta) + } + if (scaleY < 0) { + offset.y = -(initialBounds.height * scaleDelta) + } + + // Apply the offset to the new point + const { x, y } = Vec2d.Add(newPoint, offset.rot(shape.rotation)) + + return { + x, + y, + props: { + scale: scaleDelta * shape.props.scale, + }, + } +} diff --git a/packages/primitives/src/lib/BaseSegment2d.ts b/packages/tldraw/src/lib/shapes/shared/splines/BaseSegment2d.ts similarity index 98% rename from packages/primitives/src/lib/BaseSegment2d.ts rename to packages/tldraw/src/lib/shapes/shared/splines/BaseSegment2d.ts index 24c7bd8db..4473df779 100644 --- a/packages/primitives/src/lib/BaseSegment2d.ts +++ b/packages/tldraw/src/lib/shapes/shared/splines/BaseSegment2d.ts @@ -1,5 +1,4 @@ -import { Box2d } from './Box2d' -import { Vec2d, VecLike } from './Vec2d' +import { Box2d, Vec2d, VecLike } from '@tldraw/editor' /** * A base segment used for cubic and quadradic curves. diff --git a/packages/primitives/src/lib/BaseSpline2d.ts b/packages/tldraw/src/lib/shapes/shared/splines/BaseSpline2d.ts similarity index 97% rename from packages/primitives/src/lib/BaseSpline2d.ts rename to packages/tldraw/src/lib/shapes/shared/splines/BaseSpline2d.ts index c7aa28d22..506f8ed23 100644 --- a/packages/primitives/src/lib/BaseSpline2d.ts +++ b/packages/tldraw/src/lib/shapes/shared/splines/BaseSpline2d.ts @@ -1,6 +1,5 @@ +import { Box2d, Vec2d, VecLike } from '@tldraw/editor' import { BaseSegment2d } from './BaseSegment2d' -import { Box2d } from './Box2d' -import { Vec2d, VecLike } from './Vec2d' export abstract class BaseSpline2d { constructor(public points: VecLike[], public p = 50, public k = 1) {} diff --git a/packages/primitives/src/lib/CubicSegment2d.test.ts b/packages/tldraw/src/lib/shapes/shared/splines/CubicSegment2d.test.ts similarity index 100% rename from packages/primitives/src/lib/CubicSegment2d.test.ts rename to packages/tldraw/src/lib/shapes/shared/splines/CubicSegment2d.test.ts diff --git a/packages/primitives/src/lib/CubicSegment2d.ts b/packages/tldraw/src/lib/shapes/shared/splines/CubicSegment2d.ts similarity index 97% rename from packages/primitives/src/lib/CubicSegment2d.ts rename to packages/tldraw/src/lib/shapes/shared/splines/CubicSegment2d.ts index 4d8436165..78f379d99 100644 --- a/packages/primitives/src/lib/CubicSegment2d.ts +++ b/packages/tldraw/src/lib/shapes/shared/splines/CubicSegment2d.ts @@ -1,5 +1,5 @@ +import { Vec2d, VecLike } from '@tldraw/editor' import { BaseSegment2d } from './BaseSegment2d' -import { Vec2d, VecLike } from './Vec2d' /** @public */ export interface CubicSegment2dModel { diff --git a/packages/primitives/src/lib/CubicSpline2d.ts b/packages/tldraw/src/lib/shapes/shared/splines/CubicSpline2d.ts similarity index 95% rename from packages/primitives/src/lib/CubicSpline2d.ts rename to packages/tldraw/src/lib/shapes/shared/splines/CubicSpline2d.ts index 5758ee0d4..f1c98ac8b 100644 --- a/packages/primitives/src/lib/CubicSpline2d.ts +++ b/packages/tldraw/src/lib/shapes/shared/splines/CubicSpline2d.ts @@ -1,6 +1,6 @@ +import { Vec2d, VecLike } from '@tldraw/editor' import { BaseSpline2d } from './BaseSpline2d' import { CubicSegment2d, CubicSegment2dModel } from './CubicSegment2d' -import { Vec2d, VecLike } from './Vec2d' /** @public */ export class CubicSpline2d extends BaseSpline2d { diff --git a/packages/primitives/src/lib/LineSegment2d.ts b/packages/tldraw/src/lib/shapes/shared/splines/LineSegment2d.ts similarity index 96% rename from packages/primitives/src/lib/LineSegment2d.ts rename to packages/tldraw/src/lib/shapes/shared/splines/LineSegment2d.ts index a40947b42..0e8904cde 100644 --- a/packages/primitives/src/lib/LineSegment2d.ts +++ b/packages/tldraw/src/lib/shapes/shared/splines/LineSegment2d.ts @@ -1,6 +1,5 @@ +import { Box2d, Vec2d, VecLike } from '@tldraw/editor' import { BaseSegment2d } from './BaseSegment2d' -import { Box2d } from './Box2d' -import { Vec2d, VecLike } from './Vec2d' /** @public */ export interface LineSegment2dModel { diff --git a/packages/primitives/src/lib/Polyline2d.ts b/packages/tldraw/src/lib/shapes/shared/splines/Polyline2d.ts similarity index 94% rename from packages/primitives/src/lib/Polyline2d.ts rename to packages/tldraw/src/lib/shapes/shared/splines/Polyline2d.ts index 7446cd3b2..09fb947c8 100644 --- a/packages/primitives/src/lib/Polyline2d.ts +++ b/packages/tldraw/src/lib/shapes/shared/splines/Polyline2d.ts @@ -1,6 +1,6 @@ +import { VecLike } from '@tldraw/editor' import { BaseSpline2d } from './BaseSpline2d' import { LineSegment2d, LineSegment2dModel } from './LineSegment2d' -import { VecLike } from './Vec2d' /** @public */ export class Polyline2d extends BaseSpline2d { diff --git a/packages/editor/src/lib/editor/shapes/shared/useColorSpace.tsx b/packages/tldraw/src/lib/shapes/shared/useColorSpace.tsx similarity index 86% rename from packages/editor/src/lib/editor/shapes/shared/useColorSpace.tsx rename to packages/tldraw/src/lib/shapes/shared/useColorSpace.tsx index cdab00bf5..bcb7d9dc6 100644 --- a/packages/editor/src/lib/editor/shapes/shared/useColorSpace.tsx +++ b/packages/tldraw/src/lib/shapes/shared/useColorSpace.tsx @@ -1,6 +1,5 @@ -import { useValue } from '@tldraw/state' +import { debugFlags, useValue } from '@tldraw/editor' import { useEffect, useState } from 'react' -import { debugFlags } from '../../../utils/debug-flags' export function useColorSpace(): 'srgb' | 'p3' { const [supportsP3, setSupportsP3] = useState(false) diff --git a/packages/editor/src/lib/editor/shapes/shared/useEditableText.ts b/packages/tldraw/src/lib/shapes/shared/useEditableText.ts similarity index 92% rename from packages/editor/src/lib/editor/shapes/shared/useEditableText.ts rename to packages/tldraw/src/lib/shapes/shared/useEditableText.ts index a0cf4453f..ad48f3494 100644 --- a/packages/editor/src/lib/editor/shapes/shared/useEditableText.ts +++ b/packages/tldraw/src/lib/shapes/shared/useEditableText.ts @@ -1,10 +1,15 @@ /* eslint-disable no-inner-declarations */ -import { useValue } from '@tldraw/state' -import { TLShape, TLUnknownShape } from '@tldraw/tlschema' -import React, { useCallback, useEffect, useRef } from 'react' -import { useEditor } from '../../../hooks/useEditor' -import { preventDefault, stopEventPropagation } from '../../../utils/dom' -import { INDENT, TextHelpers } from '../text/TextHelpers' + +import { + TLShape, + TLUnknownShape, + preventDefault, + stopEventPropagation, + useEditor, + useValue, +} from '@tldraw/editor' +import { useCallback, useEffect, useRef } from 'react' +import { INDENT, TextHelpers } from './TextHelpers' export function useEditableText>( id: T['id'], @@ -28,7 +33,8 @@ export function useEditableText { + const mql = window.matchMedia('(prefers-reduced-motion: reduce)') + const handler = () => { + setPrefersReducedMotion(mql.matches) + } + handler() + mql.addEventListener('change', handler) + return () => mql.removeEventListener('change', handler) + }, []) + + return prefersReducedMotion +} diff --git a/packages/editor/src/lib/editor/shapes/text/TextShapeTool.test.ts b/packages/tldraw/src/lib/shapes/text/TextShapeTool.test.ts similarity index 90% rename from packages/editor/src/lib/editor/shapes/text/TextShapeTool.test.ts rename to packages/tldraw/src/lib/shapes/text/TextShapeTool.test.ts index 500dc1f27..45de6fd39 100644 --- a/packages/editor/src/lib/editor/shapes/text/TextShapeTool.test.ts +++ b/packages/tldraw/src/lib/shapes/text/TextShapeTool.test.ts @@ -6,6 +6,7 @@ let editor: TestEditor beforeEach(() => { editor = new TestEditor() }) + afterEach(() => { editor?.dispose() }) @@ -13,7 +14,7 @@ afterEach(() => { describe(TextShapeTool, () => { it('Creates text, edits it, undoes and redoes', () => { expect(editor.shapesArray.length).toBe(0) - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.expectToBeIn('text.idle') editor.pointerDown(0, 0) editor.expectToBeIn('text.pointing') @@ -49,14 +50,14 @@ describe(TextShapeTool, () => { describe('When selecting the tool', () => { it('starts in idle, transitions to pointing and dragging', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.expectToBeIn('text.idle') }) }) describe('When in idle state', () => { it('Transitions to pointing on pointer down', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.pointerDown(0, 0) editor.expectToBeIn('text.pointing') editor.pointerUp() @@ -64,7 +65,7 @@ describe('When in idle state', () => { }) it('creates a shape on pointer up', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.pointerDown(0, 0) editor.pointerUp() editor.expectToBeIn('select.editing_shape') @@ -72,7 +73,7 @@ describe('When in idle state', () => { }) it('returns to select on cancel', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.cancel() editor.expectToBeIn('select.idle') }) @@ -80,7 +81,7 @@ describe('When in idle state', () => { describe('When in the pointing state', () => { it('returns to idle on escape', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.pointerDown(0, 0) editor.cancel() editor.expectToBeIn('text.idle') @@ -88,7 +89,7 @@ describe('When in the pointing state', () => { }) it('returns to idle on interrupt', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.pointerDown(0, 0) editor.expectToBeIn('text.pointing') editor.interrupt() @@ -97,7 +98,7 @@ describe('When in the pointing state', () => { }) it('transitions to select.resizing when dragging and edits on pointer up', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.pointerDown(0, 0) editor.pointerMove(10, 10) editor.expectToBeIn('select.resizing') @@ -107,7 +108,7 @@ describe('When in the pointing state', () => { }) it('on pointer up, preserves the center when the text has a auto width', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') const x = 0 const y = 0 editor.pointerDown(x, y) @@ -122,7 +123,7 @@ describe('When in the pointing state', () => { describe('When resizing', () => { it('bails on escape while resizing and returns to text.idle', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.pointerDown(0, 0) editor.pointerMove(100, 100) editor.expectToBeIn('select.resizing') @@ -132,7 +133,7 @@ describe('When resizing', () => { }) it('does not bails on interrupt while resizing', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') editor.pointerDown(0, 0) editor.pointerMove(100, 100) editor.expectToBeIn('select.resizing') @@ -141,7 +142,7 @@ describe('When resizing', () => { }) it('preserves the top left when the text has a fixed width', () => { - editor.setSelectedTool('text') + editor.setCurrentTool('text') const x = 0 const y = 0 editor.pointerDown(x, y) diff --git a/packages/editor/src/lib/editor/shapes/text/TextShapeTool.ts b/packages/tldraw/src/lib/shapes/text/TextShapeTool.ts similarity index 51% rename from packages/editor/src/lib/editor/shapes/text/TextShapeTool.ts rename to packages/tldraw/src/lib/shapes/text/TextShapeTool.ts index 5c3bee46f..f8f72dd39 100644 --- a/packages/editor/src/lib/editor/shapes/text/TextShapeTool.ts +++ b/packages/tldraw/src/lib/shapes/text/TextShapeTool.ts @@ -1,12 +1,10 @@ -import { StateNode } from '../../tools/StateNode' +import { StateNode } from '@tldraw/editor' import { Idle } from './toolStates/Idle' import { Pointing } from './toolStates/Pointing' export class TextShapeTool extends StateNode { static override id = 'text' - static initial = 'idle' - - static children = () => [Idle, Pointing] - - shapeType = 'text' + static override initial = 'idle' + static override children = () => [Idle, Pointing] + override shapeType = 'text' } diff --git a/packages/editor/src/lib/editor/shapes/text/TextShapeUtil.tsx b/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx similarity index 89% rename from packages/editor/src/lib/editor/shapes/text/TextShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx index f6b28d8d6..5fc8ae7c0 100644 --- a/packages/editor/src/lib/editor/shapes/text/TextShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/text/TextShapeUtil.tsx @@ -1,29 +1,36 @@ /* eslint-disable react-hooks/rules-of-hooks */ -import { Box2d, toDomPrecision, Vec2d } from '@tldraw/primitives' -import { DefaultFontFamilies, getDefaultColorTheme, TLTextShape } from '@tldraw/tlschema' -import { HTMLContainer } from '../../../components/HTMLContainer' -import { stopEventPropagation } from '../../../utils/dom' -import { WeakMapCache } from '../../../utils/WeakMapCache' -import { Editor } from '../../Editor' -import { ShapeUtil, TLOnEditEndHandler, TLOnResizeHandler, TLShapeUtilFlag } from '../ShapeUtil' +import { + Box2d, + DefaultFontFamilies, + Editor, + HTMLContainer, + ShapeUtil, + SvgExportContext, + TLOnEditEndHandler, + TLOnResizeHandler, + TLShapeUtilFlag, + TLTextShape, + Vec2d, + WeakMapCache, + getDefaultColorTheme, + stopEventPropagation, + textShapeMigrations, + textShapeProps, + toDomPrecision, +} from '@tldraw/editor' import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans' import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../shared/default-shape-constants' import { getFontDefForExport } from '../shared/defaultStyleDefs' import { resizeScaled } from '../shared/resizeScaled' -import { SvgExportContext } from '../shared/SvgExportContext' import { useEditableText } from '../shared/useEditableText' -export { INDENT } from './TextHelpers' - const sizeCache = new WeakMapCache() /** @public */ export class TextShapeUtil extends ShapeUtil { static override type = 'text' as const - - canEdit = () => true - - isAspectRatioLocked: TLShapeUtilFlag = () => true + static override props = textShapeProps + static override migrations = textShapeMigrations getDefaultProps(): TLTextShape['props'] { return { @@ -48,7 +55,11 @@ export class TextShapeUtil extends ShapeUtil { return new Box2d(0, 0, width * scale, height * scale) } - getOutline(shape: TLTextShape) { + override canEdit = () => true + + override isAspectRatioLocked: TLShapeUtilFlag = () => true + + override getOutline(shape: TLTextShape) { const bounds = this.editor.getBounds(shape) return [ @@ -137,7 +148,7 @@ export class TextShapeUtil extends ShapeUtil { return } - toSvg(shape: TLTextShape, ctx: SvgExportContext) { + override toSvg(shape: TLTextShape, ctx: SvgExportContext) { ctx.addExportDef(getFontDefForExport(shape.props.font)) const theme = getDefaultColorTheme(this.editor) @@ -186,7 +197,7 @@ export class TextShapeUtil extends ShapeUtil { return groupEl } - onResize: TLOnResizeHandler = (shape, info) => { + override onResize: TLOnResizeHandler = (shape, info) => { const { initialBounds, initialShape, scaleX, handle } = info if (info.mode === 'scale_shape' || (handle !== 'right' && handle !== 'left')) { @@ -229,7 +240,7 @@ export class TextShapeUtil extends ShapeUtil { } } - onBeforeCreate = (shape: TLTextShape) => { + override onBeforeCreate = (shape: TLTextShape) => { // When a shape is created, center the text at the created point. // Only center if the shape is set to autosize. @@ -247,7 +258,7 @@ export class TextShapeUtil extends ShapeUtil { } } - onEditEnd: TLOnEditEndHandler = (shape) => { + override onEditEnd: TLOnEditEndHandler = (shape) => { const { id, type, @@ -273,7 +284,7 @@ export class TextShapeUtil extends ShapeUtil { } } - onBeforeUpdate = (prev: TLTextShape, next: TLTextShape) => { + override onBeforeUpdate = (prev: TLTextShape, next: TLTextShape) => { if (!next.props.autoSize) return const styleDidChange = @@ -334,7 +345,7 @@ export class TextShapeUtil extends ShapeUtil { } } - onDoubleClickEdge = (shape: TLTextShape) => { + override onDoubleClickEdge = (shape: TLTextShape) => { // If the shape has a fixed width, set it to autoSize. if (!shape.props.autoSize) { return { diff --git a/packages/editor/src/lib/editor/shapes/text/toolStates/Idle.ts b/packages/tldraw/src/lib/shapes/text/toolStates/Idle.ts similarity index 60% rename from packages/editor/src/lib/editor/shapes/text/toolStates/Idle.ts rename to packages/tldraw/src/lib/shapes/text/toolStates/Idle.ts index bbfc3fcaf..bd6c11e78 100644 --- a/packages/editor/src/lib/editor/shapes/text/toolStates/Idle.ts +++ b/packages/tldraw/src/lib/shapes/text/toolStates/Idle.ts @@ -1,11 +1,9 @@ -import { TLGeoShape, TLTextShape } from '@tldraw/tlschema' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' +import { StateNode, TLEventHandlers, TLGeoShape, TLTextShape } from '@tldraw/editor' export class Idle extends StateNode { static override id = 'idle' - onPointerEnter: TLEventHandlers['onPointerEnter'] = (info) => { + override onPointerEnter: TLEventHandlers['onPointerEnter'] = (info) => { switch (info.target) { case 'canvas': { // noop @@ -19,7 +17,7 @@ export class Idle extends StateNode { ) if (hoveringShape.id !== focusLayerId) { if (this.editor.isShapeOfType(hoveringShape, 'text')) { - this.editor.setHoveredId(hoveringShape.id) + this.editor.hoveredId = hoveringShape.id } } break @@ -27,24 +25,24 @@ export class Idle extends StateNode { } } - onPointerLeave: TLEventHandlers['onPointerLeave'] = (info) => { + override onPointerLeave: TLEventHandlers['onPointerLeave'] = (info) => { switch (info.target) { case 'shape': { - this.editor.setHoveredId(null) + this.editor.hoveredId = null break } } } - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { const { hoveredId } = this.editor if (hoveredId) { const shape = this.editor.getShapeById(hoveredId)! if (this.editor.isShapeOfType(shape, 'text')) { requestAnimationFrame(() => { this.editor.setSelectedIds([shape.id]) - this.editor.setEditingId(shape.id) - this.editor.setSelectedTool('select.editing_shape', { + this.editor.editingId = shape.id + this.editor.setCurrentTool('select.editing_shape', { ...info, target: 'shape', shape, @@ -57,16 +55,16 @@ export class Idle extends StateNode { this.parent.transition('pointing', info) } - onEnter = () => { - this.editor.setCursor({ type: 'cross' }) + override onEnter = () => { + this.editor.cursor = { type: 'cross', rotation: 0 } } - onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { + override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { if (info.key === 'Enter') { const shape = this.editor.selectedShapes[0] if (shape && this.editor.isShapeOfType(shape, 'geo')) { - this.editor.setSelectedTool('select') - this.editor.setEditingId(shape.id) + this.editor.setCurrentTool('select') + this.editor.editingId = shape.id this.editor.root.current.value!.transition('editing_shape', { ...info, target: 'shape', @@ -76,7 +74,7 @@ export class Idle extends StateNode { } } - onCancel = () => { - this.editor.setSelectedTool('select') + override onCancel = () => { + this.editor.setCurrentTool('select') } } diff --git a/packages/editor/src/lib/editor/shapes/text/toolStates/Pointing.ts b/packages/tldraw/src/lib/shapes/text/toolStates/Pointing.ts similarity index 71% rename from packages/editor/src/lib/editor/shapes/text/toolStates/Pointing.ts rename to packages/tldraw/src/lib/shapes/text/toolStates/Pointing.ts index 683b36e6e..54906c8b4 100644 --- a/packages/editor/src/lib/editor/shapes/text/toolStates/Pointing.ts +++ b/packages/tldraw/src/lib/shapes/text/toolStates/Pointing.ts @@ -1,17 +1,15 @@ -import { createShapeId, TLTextShape } from '@tldraw/tlschema' -import { StateNode } from '../../../tools/StateNode' -import { TLEventHandlers } from '../../../types/event-types' +import { StateNode, TLEventHandlers, TLTextShape, createShapeId } from '@tldraw/editor' export class Pointing extends StateNode { static override id = 'pointing' shape?: TLTextShape - onExit = () => { - this.editor.setHintingIds([]) + override onExit = () => { + this.editor.hintingIds = [] } - onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { + override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { const { inputs: { originPagePoint }, @@ -40,7 +38,7 @@ export class Pointing extends StateNode { this.shape = this.editor.getShapeById(id) if (!this.shape) return - this.editor.setSelectedTool('select.resizing', { + this.editor.setCurrentTool('select.resizing', { ...info, target: 'selection', handle: 'right', @@ -52,22 +50,23 @@ export class Pointing extends StateNode { } } - onPointerUp = () => { + override onPointerUp = () => { this.complete() } - onComplete = () => { - this.cancel() - } - onCancel = () => { + override onComplete = () => { this.cancel() } - onInterrupt = () => { + override onCancel = () => { this.cancel() } - complete() { + override onInterrupt = () => { + this.cancel() + } + + private complete() { this.editor.mark('creating text shape') const id = createShapeId() const { x, y } = this.editor.inputs.currentPagePoint @@ -87,12 +86,12 @@ export class Pointing extends StateNode { true ) - this.editor.setEditingId(id) - this.editor.setSelectedTool('select') + this.editor.editingId = id + this.editor.setCurrentTool('select') this.editor.root.current.value?.transition('editing_shape', {}) } - cancel() { + private cancel() { this.parent.transition('idle', {}) this.editor.bailToMark('creating') } diff --git a/packages/editor/src/lib/editor/shapes/video/VideoShapeUtil.tsx b/packages/tldraw/src/lib/shapes/video/VideoShapeUtil.tsx similarity index 87% rename from packages/editor/src/lib/editor/shapes/video/VideoShapeUtil.tsx rename to packages/tldraw/src/lib/shapes/video/VideoShapeUtil.tsx index f308fba17..66b24bb29 100644 --- a/packages/editor/src/lib/editor/shapes/video/VideoShapeUtil.tsx +++ b/packages/tldraw/src/lib/shapes/video/VideoShapeUtil.tsx @@ -1,17 +1,22 @@ -import { toDomPrecision } from '@tldraw/primitives' -import { track } from '@tldraw/state' -import { TLVideoShape } from '@tldraw/tlschema' -import * as React from 'react' -import { DefaultSpinner } from '../../../components/DefaultSpinner' -import { HTMLContainer } from '../../../components/HTMLContainer' -import { useIsEditing } from '../../../hooks/useIsEditing' -import { usePrefersReducedMotion } from '../../../utils/dom' -import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil' +import { + BaseBoxShapeUtil, + HTMLContainer, + TLVideoShape, + toDomPrecision, + track, + useIsEditing, + videoShapeMigrations, + videoShapeProps, +} from '@tldraw/editor' +import React from 'react' import { HyperlinkButton } from '../shared/HyperlinkButton' +import { usePrefersReducedMotion } from '../shared/usePrefersReducedMotion' /** @public */ export class VideoShapeUtil extends BaseBoxShapeUtil { static override type = 'video' as const + static override props = videoShapeProps + static override migrations = videoShapeMigrations override canEdit = () => true override isAspectRatioLocked = () => true @@ -35,7 +40,7 @@ export class VideoShapeUtil extends BaseBoxShapeUtil { return } - toSvg(shape: TLVideoShape) { + override toSvg(shape: TLVideoShape) { const g = document.createElementNS('http://www.w3.org/2000/svg', 'g') const image = document.createElementNS('http://www.w3.org/2000/svg', 'image') image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', serializeVideo(shape.id)) @@ -67,7 +72,7 @@ const TLVideoUtilComponent = track(function TLVideoUtilComponent(props: { const { shape, videoUtil } = props const showControls = videoUtil.editor.getBounds(shape).w * videoUtil.editor.zoomLevel >= 110 const asset = shape.props.assetId ? videoUtil.editor.getAssetById(shape.props.assetId) : null - const { w, h, time, playing } = shape.props + const { time, playing } = shape.props const isEditing = useIsEditing(shape.id) const prefersReducedMotion = usePrefersReducedMotion() @@ -191,11 +196,7 @@ const TLVideoUtilComponent = track(function TLVideoUtilComponent(props: { > - ) : ( - - - - )} + ) : null} {'url' in shape.props && shape.props.url && ( diff --git a/packages/tldraw/src/lib/tools/EraserTool/EraserTool.ts b/packages/tldraw/src/lib/tools/EraserTool/EraserTool.ts new file mode 100644 index 000000000..020c9fde3 --- /dev/null +++ b/packages/tldraw/src/lib/tools/EraserTool/EraserTool.ts @@ -0,0 +1,14 @@ +import { StateNode } from '@tldraw/editor' +import { Erasing } from './children/Erasing' +import { Idle } from './children/Idle' +import { Pointing } from './children/Pointing' + +export class EraserTool extends StateNode { + static override id = 'eraser' + static override initial = 'idle' + static override children = () => [Idle, Pointing, Erasing] + + override onEnter = () => { + this.editor.cursor = { type: 'cross', rotation: 0 } + } +} diff --git a/packages/editor/src/lib/editor/tools/EraserTool/children/Erasing.ts b/packages/tldraw/src/lib/tools/EraserTool/children/Erasing.ts similarity index 85% rename from packages/editor/src/lib/editor/tools/EraserTool/children/Erasing.ts rename to packages/tldraw/src/lib/tools/EraserTool/children/Erasing.ts index 6becfd53f..b615f025f 100644 --- a/packages/editor/src/lib/editor/tools/EraserTool/children/Erasing.ts +++ b/packages/tldraw/src/lib/tools/EraserTool/children/Erasing.ts @@ -1,8 +1,14 @@ -import { pointInPolygon } from '@tldraw/primitives' -import { TLFrameShape, TLGroupShape, TLScribble, TLShapeId } from '@tldraw/tlschema' -import { ScribbleManager } from '../../../managers/ScribbleManager' -import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types' -import { StateNode } from '../../StateNode' +import { + StateNode, + TLEventHandlers, + TLFrameShape, + TLGroupShape, + TLPointerEventInfo, + TLScribble, + TLShapeId, + pointInPolygon, +} from '@tldraw/editor' +import { ScribbleManager } from '../../../shapes/shared/ScribbleManager' export class Erasing extends StateNode { static override id = 'erasing' @@ -54,12 +60,12 @@ export class Erasing extends StateNode { } private onScribbleUpdate = (scribble: TLScribble) => { - this.editor.setScribble(scribble) + this.editor.scribble = scribble } private onScribbleComplete = () => { this.editor.off('tick', this.scribble.tick) - this.editor.setScribble(null) + this.editor.scribble = null } override onExit = () => { @@ -118,17 +124,17 @@ export class Erasing extends StateNode { // Remove the hit shapes, except if they're in the list of excluded shapes // (these excluded shapes will be any frames or groups the pointer was inside of // when the user started erasing) - this.editor.setErasingIds([...erasing].filter((id) => !excludedShapeIds.has(id))) + this.editor.erasingIds = [...erasing].filter((id) => !excludedShapeIds.has(id)) } complete() { this.editor.deleteShapes(this.editor.pageState.erasingIds) - this.editor.setErasingIds([]) + this.editor.erasingIds = [] this.parent.transition('idle', {}) } cancel() { - this.editor.setErasingIds([]) + this.editor.erasingIds = [] this.editor.bailToMark(this.markId) this.parent.transition('idle', this.info) } diff --git a/packages/tldraw/src/lib/tools/EraserTool/children/Idle.ts b/packages/tldraw/src/lib/tools/EraserTool/children/Idle.ts new file mode 100644 index 000000000..83bb9fc33 --- /dev/null +++ b/packages/tldraw/src/lib/tools/EraserTool/children/Idle.ts @@ -0,0 +1,9 @@ +import { StateNode, TLEventHandlers } from '@tldraw/editor' + +export class Idle extends StateNode { + static override id = 'idle' + + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + this.parent.transition('pointing', info) + } +} diff --git a/packages/editor/src/lib/editor/tools/EraserTool/children/Pointing.ts b/packages/tldraw/src/lib/tools/EraserTool/children/Pointing.ts similarity index 79% rename from packages/editor/src/lib/editor/tools/EraserTool/children/Pointing.ts rename to packages/tldraw/src/lib/tools/EraserTool/children/Pointing.ts index f0bdb4c8f..526a47432 100644 --- a/packages/editor/src/lib/editor/tools/EraserTool/children/Pointing.ts +++ b/packages/tldraw/src/lib/tools/EraserTool/children/Pointing.ts @@ -1,11 +1,9 @@ -import { TLFrameShape, TLGroupShape, TLShapeId } from '@tldraw/tlschema' -import { TLEventHandlers } from '../../../types/event-types' -import { StateNode } from '../../StateNode' +import { StateNode, TLEventHandlers, TLFrameShape, TLGroupShape, TLShapeId } from '@tldraw/editor' export class Pointing extends StateNode { static override id = 'pointing' - onEnter = () => { + override onEnter = () => { const { inputs } = this.editor const erasing = new Set() @@ -30,10 +28,10 @@ export class Pointing extends StateNode { } } - this.editor.setErasingIds([...erasing]) + this.editor.erasingIds = [...erasing] } - onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { + override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { this.parent.transition('erasing', info) } @@ -63,12 +61,12 @@ export class Pointing extends StateNode { this.editor.deleteShapes(erasingIds) } - this.editor.setErasingIds([]) + this.editor.erasingIds = [] this.parent.transition('idle', {}) } cancel() { - this.editor.setErasingIds([]) + this.editor.erasingIds = [] this.parent.transition('idle', {}) } } diff --git a/packages/editor/src/lib/editor/tools/HandTool/HandTool.ts b/packages/tldraw/src/lib/tools/HandTool/HandTool.ts similarity index 72% rename from packages/editor/src/lib/editor/tools/HandTool/HandTool.ts rename to packages/tldraw/src/lib/tools/HandTool/HandTool.ts index 25db5d678..1ada63b61 100644 --- a/packages/editor/src/lib/editor/tools/HandTool/HandTool.ts +++ b/packages/tldraw/src/lib/tools/HandTool/HandTool.ts @@ -1,31 +1,28 @@ -import { EASINGS } from '@tldraw/primitives' -import { TLClickEvent } from '../../types/event-types' -import { StateNode } from '../StateNode' - +import { EASINGS, StateNode, TLClickEvent } from '@tldraw/editor' import { Dragging } from './children/Dragging' import { Idle } from './children/Idle' import { Pointing } from './children/Pointing' export class HandTool extends StateNode { static override id = 'hand' - static initial = 'idle' - static children = () => [Idle, Pointing, Dragging] + static override initial = 'idle' + static override children = () => [Idle, Pointing, Dragging] - onDoubleClick: TLClickEvent = (info) => { + override onDoubleClick: TLClickEvent = (info) => { if (info.phase === 'settle') { const { currentScreenPoint } = this.editor.inputs this.editor.zoomIn(currentScreenPoint, { duration: 220, easing: EASINGS.easeOutQuint }) } } - onTripleClick: TLClickEvent = (info) => { + override onTripleClick: TLClickEvent = (info) => { if (info.phase === 'settle') { const { currentScreenPoint } = this.editor.inputs this.editor.zoomOut(currentScreenPoint, { duration: 320, easing: EASINGS.easeOutQuint }) } } - onQuadrupleClick: TLClickEvent = (info) => { + override onQuadrupleClick: TLClickEvent = (info) => { if (info.phase === 'settle') { const { zoomLevel, diff --git a/packages/editor/src/lib/editor/tools/HandTool/children/Dragging.ts b/packages/tldraw/src/lib/tools/HandTool/children/Dragging.ts similarity index 59% rename from packages/editor/src/lib/editor/tools/HandTool/children/Dragging.ts rename to packages/tldraw/src/lib/tools/HandTool/children/Dragging.ts index e446e55e4..e2dfae2a6 100644 --- a/packages/editor/src/lib/editor/tools/HandTool/children/Dragging.ts +++ b/packages/tldraw/src/lib/tools/HandTool/children/Dragging.ts @@ -1,28 +1,25 @@ -import { Vec2d } from '@tldraw/primitives' -import { HAND_TOOL_FRICTION } from '../../../../constants' -import { TLEventHandlers } from '../../../types/event-types' -import { StateNode } from '../../StateNode' +import { CAMERA_SLIDE_FRICTION, StateNode, TLEventHandlers, Vec2d } from '@tldraw/editor' export class Dragging extends StateNode { static override id = 'dragging' - onEnter = () => { + override onEnter = () => { this.update() } - onPointerMove: TLEventHandlers['onPointerMove'] = () => { + override onPointerMove: TLEventHandlers['onPointerMove'] = () => { this.update() } - onPointerUp: TLEventHandlers['onPointerUp'] = () => { + override onPointerUp: TLEventHandlers['onPointerUp'] = () => { this.complete() } - onCancel: TLEventHandlers['onCancel'] = () => { + override onCancel: TLEventHandlers['onCancel'] = () => { this.complete() } - onComplete = () => { + override onComplete = () => { this.complete() } @@ -40,7 +37,7 @@ export class Dragging extends StateNode { this.editor.slideCamera({ speed: Math.min(2, this.editor.inputs.pointerVelocity.len()), direction: this.editor.inputs.pointerVelocity, - friction: HAND_TOOL_FRICTION, + friction: CAMERA_SLIDE_FRICTION, }) this.parent.transition('idle', {}) diff --git a/packages/tldraw/src/lib/tools/HandTool/children/Idle.ts b/packages/tldraw/src/lib/tools/HandTool/children/Idle.ts new file mode 100644 index 000000000..0621735f7 --- /dev/null +++ b/packages/tldraw/src/lib/tools/HandTool/children/Idle.ts @@ -0,0 +1,17 @@ +import { StateNode, TLEventHandlers } from '@tldraw/editor' + +export class Idle extends StateNode { + static override id = 'idle' + + override onEnter = () => { + this.editor.cursor = { type: 'grab', rotation: 0 } + } + + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + this.parent.transition('pointing', info) + } + + override onCancel = () => { + this.editor.setCurrentTool('select') + } +} diff --git a/packages/tldraw/src/lib/tools/HandTool/children/Pointing.ts b/packages/tldraw/src/lib/tools/HandTool/children/Pointing.ts new file mode 100644 index 000000000..7a24e2c54 --- /dev/null +++ b/packages/tldraw/src/lib/tools/HandTool/children/Pointing.ts @@ -0,0 +1,36 @@ +import { StateNode, TLEventHandlers } from '@tldraw/editor' + +export class Pointing extends StateNode { + static override id = 'pointing' + + override onEnter = () => { + this.editor.stopCameraAnimation() + this.editor.cursor = { type: 'grabbing', rotation: 0 } + } + + override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { + if (this.editor.inputs.isDragging) { + this.parent.transition('dragging', info) + } + } + + override onPointerUp: TLEventHandlers['onPointerUp'] = () => { + this.complete() + } + + override onCancel: TLEventHandlers['onCancel'] = () => { + this.complete() + } + + override onComplete: TLEventHandlers['onComplete'] = () => { + this.complete() + } + + override onInterrupt: TLEventHandlers['onInterrupt'] = () => { + this.complete() + } + + private complete() { + this.parent.transition('idle', {}) + } +} diff --git a/packages/tldraw/src/lib/tools/LaserTool/LaserTool.ts b/packages/tldraw/src/lib/tools/LaserTool/LaserTool.ts new file mode 100644 index 000000000..17a8dbee1 --- /dev/null +++ b/packages/tldraw/src/lib/tools/LaserTool/LaserTool.ts @@ -0,0 +1,13 @@ +import { StateNode } from '@tldraw/editor' +import { Idle } from './children/Idle' +import { Lasering } from './children/Lasering' + +export class LaserTool extends StateNode { + static override id = 'laser' + static override initial = 'idle' + static override children = () => [Idle, Lasering] + + override onEnter = () => { + this.editor.cursor = { type: 'cross', rotation: 0 } + } +} diff --git a/packages/tldraw/src/lib/tools/LaserTool/children/Idle.ts b/packages/tldraw/src/lib/tools/LaserTool/children/Idle.ts new file mode 100644 index 000000000..4f6735db1 --- /dev/null +++ b/packages/tldraw/src/lib/tools/LaserTool/children/Idle.ts @@ -0,0 +1,9 @@ +import { StateNode, TLEventHandlers } from '@tldraw/editor' + +export class Idle extends StateNode { + static override id = 'idle' + + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + this.parent.transition('lasering', info) + } +} diff --git a/packages/editor/src/lib/editor/tools/LaserTool/children/Lasering.ts b/packages/tldraw/src/lib/tools/LaserTool/children/Lasering.ts similarity index 80% rename from packages/editor/src/lib/editor/tools/LaserTool/children/Lasering.ts rename to packages/tldraw/src/lib/tools/LaserTool/children/Lasering.ts index 02c69a46d..3d9142c8c 100644 --- a/packages/editor/src/lib/editor/tools/LaserTool/children/Lasering.ts +++ b/packages/tldraw/src/lib/tools/LaserTool/children/Lasering.ts @@ -1,7 +1,5 @@ -import { TLScribble } from '@tldraw/tlschema' -import { ScribbleManager } from '../../../managers/ScribbleManager' -import { TLEventHandlers } from '../../../types/event-types' -import { StateNode } from '../../StateNode' +import { StateNode, TLEventHandlers, TLScribble } from '@tldraw/editor' +import { ScribbleManager } from '../../../shapes/shared/ScribbleManager' export class Lasering extends StateNode { static override id = 'lasering' @@ -14,7 +12,7 @@ export class Lasering extends StateNode { } override onExit = () => { - this.editor.setErasingIds([]) + this.editor.erasingIds = [] this.scribble.stop() } @@ -49,12 +47,12 @@ export class Lasering extends StateNode { } private onScribbleUpdate = (scribble: TLScribble) => { - this.editor.setScribble(scribble) + this.editor.scribble = scribble } private onScribbleComplete = () => { this.editor.off('tick', this.scribble.tick) - this.editor.setScribble(null) + this.editor.scribble = null } override onCancel: TLEventHandlers['onCancel'] = () => { diff --git a/packages/editor/src/lib/editor/managers/DragAndDropManager.ts b/packages/tldraw/src/lib/tools/SelectTool/DragAndDropManager.ts similarity index 92% rename from packages/editor/src/lib/editor/managers/DragAndDropManager.ts rename to packages/tldraw/src/lib/tools/SelectTool/DragAndDropManager.ts index 890e479e1..ac5b8f052 100644 --- a/packages/editor/src/lib/editor/managers/DragAndDropManager.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/DragAndDropManager.ts @@ -1,9 +1,8 @@ -import { TLShape, TLShapeId } from '@tldraw/tlschema' -import { compact } from '@tldraw/utils' -import type { Editor } from '../Editor' +import { Editor, TLShape, TLShapeId, compact } from '@tldraw/editor' const LAG_DURATION = 100 +/** @public */ export class DragAndDropManager { constructor(public editor: Editor) { editor.disposables.add(this.dispose) @@ -73,11 +72,11 @@ export class DragAndDropManager { .onDragShapesOver?.(nextDroppingShape, movingShapes) if (res && res.shouldHint) { - this.editor.setHintingIds([nextDroppingShape.id]) + this.editor.hintingIds = [nextDroppingShape.id] } } else { // If we're dropping onto the page, then clear hinting ids - this.editor.setHintingIds([]) + this.editor.hintingIds = [] } cb?.() @@ -104,7 +103,7 @@ export class DragAndDropManager { } this.droppingNodeTimer = null - this.editor.setHintingIds([]) + this.editor.hintingIds = [] } dispose = () => { diff --git a/packages/editor/src/lib/editor/tools/SelectTool/SelectTool.ts b/packages/tldraw/src/lib/tools/SelectTool/SelectTool.ts similarity index 88% rename from packages/editor/src/lib/editor/tools/SelectTool/SelectTool.ts rename to packages/tldraw/src/lib/tools/SelectTool/SelectTool.ts index b901a449c..e16e58704 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/SelectTool.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/SelectTool.ts @@ -1,4 +1,4 @@ -import { StateNode } from '../StateNode' +import { StateNode } from '@tldraw/editor' import { Brushing } from './children/Brushing' import { Crop } from './children/Crop/Crop' import { Cropping } from './children/Cropping' @@ -19,8 +19,8 @@ import { Translating } from './children/Translating' export class SelectTool extends StateNode { static override id = 'select' - static initial = 'idle' - static children = () => [ + static override initial = 'idle' + static override children = () => [ Crop, Cropping, Idle, @@ -40,9 +40,9 @@ export class SelectTool extends StateNode { DraggingHandle, ] - onExit = () => { + override onExit = () => { if (this.editor.pageState.editingId) { - this.editor.setEditingId(null) + this.editor.editingId = null } } } diff --git a/packages/editor/src/lib/editor/tools/SelectTool/children/Brushing.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Brushing.ts similarity index 85% rename from packages/editor/src/lib/editor/tools/SelectTool/children/Brushing.ts rename to packages/tldraw/src/lib/tools/SelectTool/children/Brushing.ts index 63f8a354f..f015064a3 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/children/Brushing.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Brushing.ts @@ -1,21 +1,23 @@ import { Box2d, Matrix2d, - pointInPolygon, - polygonsIntersect, - Vec2d, - VecLike, -} from '@tldraw/primitives' -import { TLFrameShape, TLGroupShape, TLPageId, TLShape, TLShapeId } from '@tldraw/tlschema' -import { ShapeUtil } from '../../../shapes/ShapeUtil' -import { + ShapeUtil, + StateNode, TLCancelEvent, TLEventHandlers, + TLFrameShape, + TLGroupShape, TLInterruptEvent, TLKeyboardEvent, + TLPageId, TLPointerEventInfo, -} from '../../../types/event-types' -import { StateNode } from '../../StateNode' + TLShape, + TLShapeId, + Vec2d, + VecLike, + pointInPolygon, + polygonsIntersect, +} from '@tldraw/editor' export class Brushing extends StateNode { static override id = 'brushing' @@ -29,7 +31,7 @@ export class Brushing extends StateNode { // The shape that the brush started on initialStartShape: TLShape | null = null - onEnter = (info: TLPointerEventInfo & { target: 'canvas' }) => { + override onEnter = (info: TLPointerEventInfo & { target: 'canvas' }) => { const { altKey, currentPagePoint } = this.editor.inputs if (altKey) { @@ -53,29 +55,29 @@ export class Brushing extends StateNode { this.onPointerMove() } - onExit = () => { + override onExit = () => { this.initialSelectedIds = [] - this.editor.setBrush(null) + this.editor.brush = null } - onPointerMove = () => { + override onPointerMove = () => { this.hitTestShapes() } - onPointerUp: TLEventHandlers['onPointerUp'] = () => { + override onPointerUp: TLEventHandlers['onPointerUp'] = () => { this.complete() } - onComplete: TLEventHandlers['onComplete'] = () => { + override onComplete: TLEventHandlers['onComplete'] = () => { this.complete() } - onCancel?: TLCancelEvent | undefined = (info) => { + override onCancel?: TLCancelEvent | undefined = (info) => { this.editor.setSelectedIds(this.initialSelectedIds, true) this.parent.transition('idle', info) } - onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { + override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { if (this.editor.inputs.altKey) { this.parent.transition('scribble_brushing', info) } else { @@ -83,7 +85,7 @@ export class Brushing extends StateNode { } } - onKeyUp?: TLKeyboardEvent | undefined = () => { + override onKeyUp?: TLKeyboardEvent | undefined = () => { this.hitTestShapes() } @@ -166,12 +168,12 @@ export class Brushing extends StateNode { } } - this.editor.setBrush({ ...this.brush.toJson() }) + this.editor.brush = { ...this.brush.toJson() } this.editor.setSelectedIds(Array.from(results), true) } - onInterrupt: TLInterruptEvent = () => { - this.editor.setBrush(null) + override onInterrupt: TLInterruptEvent = () => { + this.editor.brush = null } private handleHit( diff --git a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/Crop.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/Crop.ts similarity index 60% rename from packages/editor/src/lib/editor/tools/SelectTool/children/Crop/Crop.ts rename to packages/tldraw/src/lib/tools/SelectTool/children/Crop/Crop.ts index f72af94f2..8bc292fd4 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/Crop.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/Crop.ts @@ -1,11 +1,10 @@ -import { StateNode } from '../../../StateNode' +import { StateNode } from '@tldraw/editor' import { Idle } from './children/Idle' import { PointingCrop } from './children/PointingCrop' import { TranslatingCrop } from './children/TranslatingCrop' export class Crop extends StateNode { static override id = 'crop' - - static initial = 'idle' - static children = () => [Idle, TranslatingCrop, PointingCrop] + static override initial = 'idle' + static override children = () => [Idle, TranslatingCrop, PointingCrop] } diff --git a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/Idle.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/Idle.ts similarity index 69% rename from packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/Idle.ts rename to packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/Idle.ts index 978b9bd23..50b34f83f 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/Idle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/Idle.ts @@ -1,13 +1,11 @@ -import { Vec2d } from '@tldraw/primitives' -import { TLEventHandlers, TLExitEventHandler } from '../../../../../types/event-types' -import { StateNode } from '../../../../StateNode' +import { StateNode, TLEventHandlers, TLExitEventHandler, Vec2d } from '@tldraw/editor' import { ShapeWithCrop, getTranslateCroppedImageChange } from './crop_helpers' export class Idle extends StateNode { static override id = 'idle' - onEnter = () => { - this.editor.setCursor({ type: 'default' }) + override onEnter = () => { + this.editor.cursor = { type: 'default', rotation: 0 } const { onlySelectedShape } = this.editor @@ -19,27 +17,27 @@ export class Idle extends StateNode { this.editor.mark('crop') if (onlySelectedShape) { - this.editor.setCroppingId(onlySelectedShape.id) + this.editor.croppingId = onlySelectedShape.id } } - onExit: TLExitEventHandler = () => { - this.editor.setCursor({ type: 'default' }) + override onExit: TLExitEventHandler = () => { + this.editor.cursor = { type: 'default', rotation: 0 } this.editor.off('change-history', this.cleanupCroppingState) } - onCancel: TLEventHandlers['onCancel'] = () => { - this.editor.setCroppingId(null) - this.editor.setSelectedTool('select.idle', {}) + override onCancel: TLEventHandlers['onCancel'] = () => { + this.editor.croppingId = null + this.editor.setCurrentTool('select.idle', {}) } - onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { + override onPointerDown: TLEventHandlers['onPointerDown'] = (info) => { if (this.editor.isMenuOpen) return if (info.ctrlKey) { - this.editor.setCroppingId(null) - this.editor.setSelectedTool('select.brushing', info) + this.editor.croppingId = null + this.editor.setCurrentTool('select.brushing', info) return } @@ -50,13 +48,13 @@ export class Idle extends StateNode { } case 'shape': { if (info.shape.id === this.editor.croppingId) { - this.editor.setSelectedTool('select.crop.pointing_crop', info) + this.editor.setCurrentTool('select.crop.pointing_crop', info) return } else { if (this.editor.getShapeUtil(info.shape)?.canCrop(info.shape)) { - this.editor.setCroppingId(info.shape.id) + this.editor.croppingId = info.shape.id this.editor.setSelectedIds([info.shape.id]) - this.editor.setSelectedTool('select.crop.pointing_crop', info) + this.editor.setCurrentTool('select.crop.pointing_crop', info) } else { this.cancel() } @@ -70,7 +68,7 @@ export class Idle extends StateNode { case 'top_right_rotate': case 'bottom_left_rotate': case 'bottom_right_rotate': { - this.editor.setSelectedTool('select.pointing_rotate_handle', { + this.editor.setCurrentTool('select.pointing_rotate_handle', { ...info, onInteractionEnd: 'select.crop', }) @@ -80,7 +78,7 @@ export class Idle extends StateNode { case 'right': case 'bottom': case 'left': { - this.editor.setSelectedTool('select.pointing_crop_handle', { + this.editor.setCurrentTool('select.pointing_crop_handle', { ...info, onInteractionEnd: 'select.crop', }) @@ -90,7 +88,7 @@ export class Idle extends StateNode { case 'top_right': case 'bottom_left': case 'bottom_right': { - this.editor.setSelectedTool('select.pointing_crop_handle', { + this.editor.setCurrentTool('select.pointing_crop_handle', { ...info, onInteractionEnd: 'select.crop', }) @@ -105,7 +103,7 @@ export class Idle extends StateNode { } } - onDoubleClick: TLEventHandlers['onDoubleClick'] = (info) => { + override onDoubleClick: TLEventHandlers['onDoubleClick'] = (info) => { // Without this, the double click's "settle" would trigger the reset // after the user double clicked the edge to begin cropping if (info.phase !== 'up') return @@ -122,32 +120,32 @@ export class Idle extends StateNode { } } - onKeyDown: TLEventHandlers['onKeyDown'] = () => { + override onKeyDown: TLEventHandlers['onKeyDown'] = () => { this.nudgeCroppingImage(false) } - onKeyRepeat: TLEventHandlers['onKeyRepeat'] = () => { + override onKeyRepeat: TLEventHandlers['onKeyRepeat'] = () => { this.nudgeCroppingImage(true) } - onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { + override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { switch (info.code) { case 'Enter': { - this.editor.setCroppingId(null) - this.editor.setSelectedTool('select.idle', {}) + this.editor.croppingId = null + this.editor.setCurrentTool('select.idle', {}) break } } } private cancel() { - this.editor.setCroppingId(null) - this.editor.setSelectedTool('select.idle', {}) + this.editor.croppingId = null + this.editor.setCurrentTool('select.idle', {}) } private cleanupCroppingState = () => { if (!this.editor.croppingId) { - this.editor.setSelectedTool('select.idle', {}) + this.editor.setCurrentTool('select.idle', {}) } } diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/PointingCrop.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/PointingCrop.ts new file mode 100644 index 000000000..4e6dda856 --- /dev/null +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/PointingCrop.ts @@ -0,0 +1,19 @@ +import { StateNode, TLEventHandlers, TLPointerEvent } from '@tldraw/editor' + +export class PointingCrop extends StateNode { + static override id = 'pointing_crop' + + override onCancel: TLEventHandlers['onCancel'] = () => { + this.editor.setCurrentTool('select.crop.idle', {}) + } + + override onPointerMove: TLPointerEvent = (info) => { + if (this.editor.inputs.isDragging) { + this.editor.setCurrentTool('select.crop.translating_crop', info) + } + } + + override onPointerUp: TLPointerEvent = (info) => { + this.editor.setCurrentTool('select.crop.idle', info) + } +} diff --git a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/TranslatingCrop.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/TranslatingCrop.ts similarity index 69% rename from packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/TranslatingCrop.ts rename to packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/TranslatingCrop.ts index 6f85e2763..dd3291d47 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/TranslatingCrop.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/TranslatingCrop.ts @@ -1,5 +1,4 @@ -import { TLEventHandlers, TLPointerEventInfo } from '../../../../../types/event-types' -import { StateNode } from '../../../../StateNode' +import { StateNode, TLEventHandlers, TLPointerEventInfo } from '@tldraw/editor' import { ShapeWithCrop, getTranslateCroppedImageChange } from './crop_helpers' type Snapshot = ReturnType @@ -17,7 +16,7 @@ export class TranslatingCrop extends StateNode { private snapshot = {} as any as Snapshot - onEnter = ( + override onEnter = ( info: TLPointerEventInfo & { target: 'shape' isCreating?: boolean @@ -28,31 +27,31 @@ export class TranslatingCrop extends StateNode { this.snapshot = this.createSnapshot() this.editor.mark(this.markId) - this.editor.setCursor({ type: 'move' }) + this.editor.cursor = { type: 'move', rotation: 0 } this.updateShapes() } - onExit = () => { - this.editor.setCursor({ type: 'default' }) + override onExit = () => { + this.editor.cursor = { type: 'default', rotation: 0 } } - onPointerMove = () => { + override onPointerMove = () => { this.updateShapes() } - onPointerUp: TLEventHandlers['onPointerUp'] = () => { + override onPointerUp: TLEventHandlers['onPointerUp'] = () => { this.complete() } - onComplete: TLEventHandlers['onComplete'] = () => { + override onComplete: TLEventHandlers['onComplete'] = () => { this.complete() } - onCancel: TLEventHandlers['onCancel'] = () => { + override onCancel: TLEventHandlers['onCancel'] = () => { this.cancel() } - onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { + override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => { switch (info.key) { case 'Alt': case 'Shift': { @@ -62,7 +61,7 @@ export class TranslatingCrop extends StateNode { } } - onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { + override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => { switch (info.key) { case 'Enter': { this.complete() @@ -77,12 +76,12 @@ export class TranslatingCrop extends StateNode { protected complete() { this.updateShapes() - this.editor.setSelectedTool('select.crop.idle', this.info) + this.editor.setCurrentTool('select.crop.idle', this.info) } private cancel() { this.editor.bailToMark(this.markId) - this.editor.setSelectedTool('select.crop.idle', this.info) + this.editor.setCurrentTool('select.crop.idle', this.info) } private createSnapshot() { diff --git a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/crop_helpers.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/crop_helpers.ts similarity index 87% rename from packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/crop_helpers.ts rename to packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/crop_helpers.ts index ff8b8836f..74d30d016 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/children/Crop/children/crop_helpers.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/children/crop_helpers.ts @@ -1,7 +1,11 @@ -import { Vec2d } from '@tldraw/primitives' -import { TLBaseShape, TLImageShapeCrop, TLShapePartial } from '@tldraw/tlschema' -import { deepCopy } from '@tldraw/utils' -import { Editor } from '../../../../../Editor' +import { + Editor, + TLBaseShape, + TLImageShapeCrop, + TLShapePartial, + Vec2d, + deepCopy, +} from '@tldraw/editor' export type ShapeWithCrop = TLBaseShape diff --git a/packages/tldraw/src/lib/tools/SelectTool/children/Crop/crop-constants.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/crop-constants.ts new file mode 100644 index 000000000..16675752a --- /dev/null +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Crop/crop-constants.ts @@ -0,0 +1,2 @@ +/** @internal */ +export const MIN_CROP_SIZE = 8 diff --git a/packages/editor/src/lib/editor/tools/SelectTool/children/Cropping.ts b/packages/tldraw/src/lib/tools/SelectTool/children/Cropping.ts similarity index 87% rename from packages/editor/src/lib/editor/tools/SelectTool/children/Cropping.ts rename to packages/tldraw/src/lib/tools/SelectTool/children/Cropping.ts index d58162f74..4f47a8caa 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/children/Cropping.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/Cropping.ts @@ -1,13 +1,17 @@ -import { SelectionHandle, Vec2d } from '@tldraw/primitives' -import { TLBaseShape, TLImageShape, TLImageShapeCrop, TLShapePartial } from '@tldraw/tlschema' -import { deepCopy } from '@tldraw/utils' -import { MIN_CROP_SIZE } from '../../../../constants' import { + SelectionHandle, + StateNode, + TLBaseShape, TLEnterEventHandler, TLEventHandlers, + TLImageShape, + TLImageShapeCrop, TLPointerEventInfo, -} from '../../../types/event-types' -import { StateNode } from '../../StateNode' + TLShapePartial, + Vec2d, + deepCopy, +} from '@tldraw/editor' +import { MIN_CROP_SIZE } from './Crop/crop-constants' import { CursorTypeMap } from './PointingResizeHandle' type Snapshot = ReturnType @@ -25,7 +29,7 @@ export class Cropping extends StateNode { private snapshot = {} as any as Snapshot - onEnter: TLEnterEventHandler = ( + override onEnter: TLEnterEventHandler = ( info: TLPointerEventInfo & { target: 'selection' handle: SelectionHandle @@ -38,19 +42,19 @@ export class Cropping extends StateNode { this.updateShapes() } - onPointerMove: TLEventHandlers['onPointerMove'] = () => { + override onPointerMove: TLEventHandlers['onPointerMove'] = () => { this.updateShapes() } - onPointerUp: TLEventHandlers['onPointerUp'] = () => { + override onPointerUp: TLEventHandlers['onPointerUp'] = () => { this.complete() } - onComplete: TLEventHandlers['onComplete'] = () => { + override onComplete: TLEventHandlers['onComplete'] = () => { this.complete() } - onCancel: TLEventHandlers['onCancel'] = () => { + override onCancel: TLEventHandlers['onCancel'] = () => { this.cancel() } @@ -59,10 +63,10 @@ export class Cropping extends StateNode { if (!selectedShape) return const cursorType = CursorTypeMap[this.info.handle!] - this.editor.setCursor({ + this.editor.cursor = { type: cursorType, rotation: selectedShape.rotation, - }) + } } private getDefaultCrop = (): TLImageShapeCrop => ({ @@ -199,9 +203,9 @@ export class Cropping extends StateNode { private complete() { if (this.info.onInteractionEnd) { - this.editor.setSelectedTool(this.info.onInteractionEnd, this.info) + this.editor.setCurrentTool(this.info.onInteractionEnd, this.info) } else { - this.editor.setCroppingId(null) + this.editor.croppingId = null this.parent.transition('idle', {}) } } @@ -209,9 +213,9 @@ export class Cropping extends StateNode { private cancel() { this.editor.bailToMark(this.markId) if (this.info.onInteractionEnd) { - this.editor.setSelectedTool(this.info.onInteractionEnd, this.info) + this.editor.setCurrentTool(this.info.onInteractionEnd, this.info) } else { - this.editor.setCroppingId(null) + this.editor.croppingId = null this.parent.transition('idle', {}) } } diff --git a/packages/editor/src/lib/editor/tools/SelectTool/children/DraggingHandle.ts b/packages/tldraw/src/lib/tools/SelectTool/children/DraggingHandle.ts similarity index 85% rename from packages/editor/src/lib/editor/tools/SelectTool/children/DraggingHandle.ts rename to packages/tldraw/src/lib/tools/SelectTool/children/DraggingHandle.ts index b9429e718..35a7da022 100644 --- a/packages/editor/src/lib/editor/tools/SelectTool/children/DraggingHandle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/children/DraggingHandle.ts @@ -1,24 +1,24 @@ -import { sortByIndex } from '@tldraw/indices' -import { Matrix2d, snapAngle, Vec2d } from '@tldraw/primitives' import { + Matrix2d, + StateNode, TLArrowShape, TLArrowShapeTerminal, - TLHandle, - TLShapeId, - TLShapePartial, -} from '@tldraw/tlschema' -import { deepCopy } from '@tldraw/utils' -import { TLCancelEvent, TLEnterEventHandler, TLEventHandlers, + TLHandle, TLKeyboardEvent, TLPointerEventInfo, -} from '../../../types/event-types' -import { StateNode } from '../../StateNode' + TLShapeId, + TLShapePartial, + Vec2d, + deepCopy, + snapAngle, + sortByIndex, +} from '@tldraw/editor' export class DraggingHandle extends StateNode { - static id = 'dragging_handle' + static override id = 'dragging_handle' shapeId = '' as TLShapeId initialHandle = {} as TLHandle @@ -39,7 +39,7 @@ export class DraggingHandle extends StateNode { isPreciseId = null as TLShapeId | null pointingId = null as TLShapeId | null - onEnter: TLEnterEventHandler = ( + override onEnter: TLEnterEventHandler = ( info: TLPointerEventInfo & { shape: TLArrowShape target: 'handle' @@ -49,13 +49,14 @@ export class DraggingHandle extends StateNode { ) => { const { shape, isCreating, handle } = info this.info = info + this.parent.currentToolIdMask = info.onInteractionEnd this.shapeId = shape.id this.markId = isCreating ? 'creating' : this.editor.mark('dragging handle') this.initialHandle = deepCopy(handle) this.initialPageTransform = this.editor.getPageTransform(shape)! this.initialPageRotation = this.editor.getPageRotation(shape)! - this.editor.setCursor({ type: isCreating ? 'cross' : 'grabbing', rotation: 0 }) + this.editor.cursor = { type: isCreating ? 'cross' : 'grabbing', rotation: 0 } //