├── .changeset
├── README.md
└── config.json
├── .eslintignore
├── .eslintrc
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── config.yml
│ ├── documentation.md
│ ├── feature.md
│ └── testing.md
└── workflows
│ └── main.yml
├── .gitignore
├── .husky
├── pre-commit
└── pre-push
├── .ignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .vscode
├── extensions.json
├── settings.json
└── snippets.code-snippets
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── TRADEMARKS.md
├── apps
└── www
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── components
│ ├── BetaNotification.tsx
│ ├── Editor.tsx
│ ├── IFrameWarning.tsx
│ ├── MaintenanceMode.tsx
│ ├── MultiplayerEditor.tsx
│ └── ReadOnlyMultiplayerEditor.tsx
│ ├── hooks
│ ├── useMultiplayerAssets.ts
│ ├── useMultiplayerState.ts
│ ├── useReadOnlyMultiplayerState.ts
│ └── useUploadAssets.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── api
│ │ ├── create.ts
│ │ └── upload.ts
│ ├── index.tsx
│ ├── r
│ │ ├── [id].tsx
│ │ └── index.tsx
│ └── v
│ │ └── [id].tsx
│ ├── public
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── android-chrome-maskable-192x192.png
│ ├── android-chrome-maskable-512x512.png
│ ├── android-chrome-maskable-beta-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── flat.png
│ ├── images
│ │ └── hello.mp4
│ ├── manifest.json
│ ├── social-image.png
│ └── tldraw-assets.json
│ ├── styles
│ ├── globals.css
│ ├── index.ts
│ └── stitches.config.ts
│ ├── tsconfig.json
│ ├── types.ts
│ ├── utils
│ ├── export.ts
│ ├── github.ts
│ ├── gtag.ts
│ ├── liveblocks.ts
│ └── useGtag.ts
│ └── worker
│ └── index.js
├── assets
├── card-repo.png
├── icon_flat_black.png
├── icon_flat_black_on_white.png
├── icon_flat_white.png
├── icon_flat_white_on_black.png
├── recording.gif
├── screenshot.png
└── tldraw.png
├── examples
├── core-example-advanced
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── card-repo.png
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── App.tsx
│ │ ├── components
│ │ │ ├── TitleLinks.tsx
│ │ │ └── Toolbar.tsx
│ │ ├── main.tsx
│ │ ├── shapes
│ │ │ ├── CustomShapeUtil.ts
│ │ │ ├── arrow
│ │ │ │ ├── ArrowComponent.tsx
│ │ │ │ ├── ArrowIndicator.tsx
│ │ │ │ ├── ArrowShape.ts
│ │ │ │ ├── ArrowUtil.ts
│ │ │ │ └── index.ts
│ │ │ ├── box
│ │ │ │ ├── BoxComponent.tsx
│ │ │ │ ├── BoxIndicator.tsx
│ │ │ │ ├── BoxShape.ts
│ │ │ │ ├── BoxUtil.ts
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ └── pencil
│ │ │ │ ├── PencilComponent.tsx
│ │ │ │ ├── PencilShape.ts
│ │ │ │ ├── PencilUtil.ts
│ │ │ │ ├── PenclIndicator.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── pencil-helpers.ts
│ │ ├── state
│ │ │ ├── actions
│ │ │ │ ├── bindings
│ │ │ │ │ ├── createBindings.ts
│ │ │ │ │ ├── deleteBindings.ts
│ │ │ │ │ ├── getBoundHandlePoint.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── removePartialBindings.ts
│ │ │ │ │ ├── updateBindings.ts
│ │ │ │ │ └── updateBoundShapes.ts
│ │ │ │ ├── camera
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── panCamera.ts
│ │ │ │ │ ├── pinchCamera.ts
│ │ │ │ │ ├── zoomIn.ts
│ │ │ │ │ ├── zoomOut.ts
│ │ │ │ │ ├── zoomToFit.ts
│ │ │ │ │ └── zoomToSelection.ts
│ │ │ │ ├── data
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── loadDocument.ts
│ │ │ │ │ ├── loadNewDocument.ts
│ │ │ │ │ └── restoreSavedDocument.ts
│ │ │ │ ├── erase
│ │ │ │ │ ├── eraseGhostShapes.ts
│ │ │ │ │ ├── eraseShapes.ts
│ │ │ │ │ ├── eraseShapesAtPoint.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── handles
│ │ │ │ │ ├── clearPointedHandle.ts
│ │ │ │ │ ├── getBoundTarget.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── setPointedHandle.ts
│ │ │ │ │ └── translateHandle.ts
│ │ │ │ ├── history
│ │ │ │ │ ├── addToHistory.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── redo.ts
│ │ │ │ │ └── undo.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mutables
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── restoreSnapshot.ts
│ │ │ │ │ ├── setInitialPoint.ts
│ │ │ │ │ ├── setSnapshot.ts
│ │ │ │ │ ├── setViewport.ts
│ │ │ │ │ └── updatePointer.ts
│ │ │ │ ├── performance
│ │ │ │ │ ├── clearPerformanceMode.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── setTransformPerformanceMode.ts
│ │ │ │ │ └── setTranslatePerformanceMode.ts
│ │ │ │ ├── selection
│ │ │ │ │ ├── clearBrush.ts
│ │ │ │ │ ├── clearHoveredShape.ts
│ │ │ │ │ ├── clearPointedShape.ts
│ │ │ │ │ ├── deselectAllShapes.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── selectAllShapes.ts
│ │ │ │ │ ├── selectShape.ts
│ │ │ │ │ ├── setHoveredShape.ts
│ │ │ │ │ └── updateBrush.ts
│ │ │ │ ├── shapes
│ │ │ │ │ ├── createArrowShape.ts
│ │ │ │ │ ├── createBoxShape.ts
│ │ │ │ │ ├── createPencilShape.ts
│ │ │ │ │ ├── createShapes.ts
│ │ │ │ │ ├── deleteSelectedShapes.ts
│ │ │ │ │ ├── deleteShapes.ts
│ │ │ │ │ ├── extendPencilShape.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── updateShapes.ts
│ │ │ │ ├── snapping
│ │ │ │ │ └── clearSnapLines.ts
│ │ │ │ ├── snaps
│ │ │ │ │ ├── clearSnapInfo.ts
│ │ │ │ │ ├── clearSnapLines.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── setSnapInfo.ts
│ │ │ │ ├── transform
│ │ │ │ │ ├── clearPointedBoundsHandle.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── resizeSelectedShapes.ts
│ │ │ │ │ ├── rotateSelectedShapes.ts
│ │ │ │ │ ├── setInitialCommonBounds.ts
│ │ │ │ │ ├── setPointedBoundsHandle.ts
│ │ │ │ │ └── transformSelectedShapes.ts
│ │ │ │ └── translate
│ │ │ │ │ ├── clearIsCloning.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── translateSelectedShapes.ts
│ │ │ ├── api.ts
│ │ │ ├── constants.ts
│ │ │ ├── helpers.ts
│ │ │ ├── history.ts
│ │ │ ├── machine.ts
│ │ │ └── mutables.ts
│ │ ├── stitches.config.ts
│ │ ├── styles.css
│ │ ├── types.ts
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── core-example
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── card-repo.png
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── App.tsx
│ │ ├── main.tsx
│ │ ├── shapes
│ │ │ ├── index.ts
│ │ │ └── rect
│ │ │ │ ├── RectComponent.tsx
│ │ │ │ ├── RectIndicator.tsx
│ │ │ │ ├── RectShape.ts
│ │ │ │ ├── RectUtil.ts
│ │ │ │ └── index.ts
│ │ ├── styles.css
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
└── tldraw-example
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── card-repo.png
│ ├── index.html
│ ├── package.json
│ ├── public
│ ├── Example.tldr
│ ├── card-repo.png
│ ├── favicon.ico
│ └── tldraw-assets.json
│ ├── src
│ ├── App.tsx
│ ├── api-control.tsx
│ ├── api.tsx
│ ├── basic.tsx
│ ├── changing-id.tsx
│ ├── core-stuff
│ │ └── index.tsx
│ ├── custom-cursors-core.tsx
│ ├── custom-cursors.tsx
│ ├── dark-mode.tsx
│ ├── develop.tsx
│ ├── embedded.tsx
│ ├── export-to-server.tsx
│ ├── export.tsx
│ ├── file-system.tsx
│ ├── iframe.tsx
│ ├── loading-files.tsx
│ ├── main.tsx
│ ├── multiplayer
│ │ ├── index.ts
│ │ ├── liveblocks.config.ts
│ │ ├── multiplayer.tsx
│ │ └── useMultiplayerState.ts
│ ├── no-size-embedded.tsx
│ ├── persisted.tsx
│ ├── props-control.tsx
│ ├── readonly.tsx
│ ├── scroll.tsx
│ ├── styles.css
│ ├── ui-options.tsx
│ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── guides
├── development.md
├── documentation.md
├── publishing.md
└── translation.md
├── package.json
├── packages
├── core
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── TLShapeUtil
│ │ │ ├── TLShapeUtil.spec.tsx
│ │ │ ├── TLShapeUtil.tsx
│ │ │ └── index.ts
│ │ ├── components
│ │ │ ├── Binding
│ │ │ │ ├── Binding.test.tsx
│ │ │ │ ├── Binding.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Bounds
│ │ │ │ ├── Bounds.tsx
│ │ │ │ ├── BoundsBg.tsx
│ │ │ │ ├── CenterHandle.tsx
│ │ │ │ ├── CloneButton.tsx
│ │ │ │ ├── CloneButtons.tsx
│ │ │ │ ├── CornerHandle.tsx
│ │ │ │ ├── EdgeHandle.tsx
│ │ │ │ ├── LinkHandle.tsx
│ │ │ │ ├── RotateHandle.tsx
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── Bounds.test.tsx
│ │ │ │ │ ├── BoundsBg.test.tsx
│ │ │ │ │ ├── CenterHandle.test.tsx
│ │ │ │ │ ├── CloneButton.test.tsx
│ │ │ │ │ ├── CornerHandle.test.tsx
│ │ │ │ │ ├── EdgeHandle.test.tsx
│ │ │ │ │ ├── LinkHandle.test.tsx
│ │ │ │ │ └── RotateHandle.test.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Brush
│ │ │ │ ├── Brush.test.tsx
│ │ │ │ ├── Brush.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Canvas
│ │ │ │ ├── Canvas.test.tsx
│ │ │ │ ├── Canvas.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Container
│ │ │ │ ├── Container.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Cursor
│ │ │ │ ├── Cursor.tsx
│ │ │ │ └── index.ts
│ │ │ ├── EraseLine
│ │ │ │ ├── EraseLine.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Grid
│ │ │ │ ├── Grid.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HTMLContainer
│ │ │ │ ├── HTMLContainer.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Handles
│ │ │ │ ├── Handle.test.tsx
│ │ │ │ ├── Handle.tsx
│ │ │ │ ├── Handles.test.tsx
│ │ │ │ ├── Handles.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Overlay
│ │ │ │ ├── Overlay.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Page
│ │ │ │ ├── Page.test.tsx
│ │ │ │ ├── Page.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Renderer
│ │ │ │ ├── Renderer.test.tsx
│ │ │ │ ├── Renderer.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── SVGContainer
│ │ │ │ ├── SVGContainer.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Shape
│ │ │ │ ├── RenderedShape.tsx
│ │ │ │ ├── Shape.test.tsx
│ │ │ │ ├── Shape.tsx
│ │ │ │ ├── ShapeNode.tsx
│ │ │ │ └── index.ts
│ │ │ ├── ShapeIndicator
│ │ │ │ ├── ShapeIndicator.test.tsx
│ │ │ │ ├── ShapeIndicator.tsx
│ │ │ │ └── index.ts
│ │ │ ├── SnapLines
│ │ │ │ ├── SnapLines.tsx
│ │ │ │ └── index.ts
│ │ │ ├── User
│ │ │ │ ├── User.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Users
│ │ │ │ ├── Users.tsx
│ │ │ │ └── index.ts
│ │ │ ├── UsersIndicators
│ │ │ │ ├── UsersIndicators.tsx
│ │ │ │ └── index.ts
│ │ │ └── index.tsx
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useBoundsEvents.tsx
│ │ │ ├── useBoundsHandleEvents.tsx
│ │ │ ├── useCameraCss.tsx
│ │ │ ├── useCanvasEvents.tsx
│ │ │ ├── useHandleEvents.tsx
│ │ │ ├── useHandles.ts
│ │ │ ├── useKeyEvents.ts
│ │ │ ├── usePerformanceCss.ts
│ │ │ ├── usePosition.ts
│ │ │ ├── usePreventNavigationCss.tsx
│ │ │ ├── useResizeObserver.ts
│ │ │ ├── useSafariFocusOutFix.tsx
│ │ │ ├── useSelection.tsx
│ │ │ ├── useShapeEvents.tsx
│ │ │ ├── useShapeTree.tsx
│ │ │ ├── useStyle.tsx
│ │ │ ├── useTLContext.tsx
│ │ │ └── useZoomEvents.ts
│ │ ├── index.ts
│ │ ├── inputs.ts
│ │ ├── test
│ │ │ ├── ContextWrapper.tsx
│ │ │ ├── index.ts
│ │ │ ├── mockDocument.ts
│ │ │ ├── mockUtils.tsx
│ │ │ ├── renderWithContext.tsx
│ │ │ └── renderWithSvg.tsx
│ │ ├── types.ts
│ │ └── utils
│ │ │ ├── index.ts
│ │ │ ├── polyfills.ts
│ │ │ └── utils.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.dev.json
│ └── tsconfig.json
├── curve
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.dev.json
│ └── tsconfig.json
├── intersect
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.dev.json
│ └── tsconfig.json
├── tldraw
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── Tldraw.spec.tsx
│ │ ├── Tldraw.tsx
│ │ ├── components
│ │ │ ├── ContextMenu
│ │ │ │ ├── ContextMenu.test.tsx
│ │ │ │ ├── ContextMenu.tsx
│ │ │ │ └── index.ts
│ │ │ ├── ErrorFallback
│ │ │ │ ├── ErrorFallback.tsx
│ │ │ │ └── index.ts
│ │ │ ├── FocusButton
│ │ │ │ ├── FocusButton.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Loading
│ │ │ │ ├── Loading.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Primitives
│ │ │ │ ├── AlertDialog
│ │ │ │ │ ├── AlertDialog.tsx
│ │ │ │ │ ├── FilenameDialog.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Divider
│ │ │ │ │ ├── Divider.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── DropdownMenu
│ │ │ │ │ ├── DMCheckboxItem.tsx
│ │ │ │ │ ├── DMContent.tsx
│ │ │ │ │ ├── DMDivider.tsx
│ │ │ │ │ ├── DMItem.tsx
│ │ │ │ │ ├── DMRadioItem.tsx
│ │ │ │ │ ├── DMSubMenu.tsx
│ │ │ │ │ ├── DMTriggerIcon.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── IconButton
│ │ │ │ │ ├── IconButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Kbd
│ │ │ │ │ ├── Kbd.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── MenuContent
│ │ │ │ │ ├── MenuContent.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Panel
│ │ │ │ │ ├── Panel.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── RowButton
│ │ │ │ │ ├── RowButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── SmallIcon
│ │ │ │ │ ├── SmallIcon.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── TextField
│ │ │ │ │ ├── TextField.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── ToolButton
│ │ │ │ │ ├── ToolButton.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── Tooltip
│ │ │ │ │ ├── Tooltip.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── icons
│ │ │ │ │ ├── BoxIcon.tsx
│ │ │ │ │ ├── CircleIcon.tsx
│ │ │ │ │ ├── DashDashedIcon.tsx
│ │ │ │ │ ├── DashDottedIcon.tsx
│ │ │ │ │ ├── DashDrawIcon.tsx
│ │ │ │ │ ├── DashSolidIcon.tsx
│ │ │ │ │ ├── DiscordIcon.tsx
│ │ │ │ │ ├── EraserIcon.tsx
│ │ │ │ │ ├── LineIcon.tsx
│ │ │ │ │ ├── MultiplayerIcon2.tsx
│ │ │ │ │ ├── SizeLargeIcon.tsx
│ │ │ │ │ ├── SizeMediumIcon.tsx
│ │ │ │ │ ├── SizeSmallIcon.tsx
│ │ │ │ │ ├── TrashIcon.tsx
│ │ │ │ │ ├── UndoIcon.tsx
│ │ │ │ │ └── index.ts
│ │ │ ├── ToolsPanel
│ │ │ │ ├── ActionButton.tsx
│ │ │ │ ├── BackToContent.tsx
│ │ │ │ ├── DeleteButton.tsx
│ │ │ │ ├── HelpPanel.tsx
│ │ │ │ ├── KeyboardShortcutDialog.tsx
│ │ │ │ ├── LockButton.tsx
│ │ │ │ ├── PrimaryTools.tsx
│ │ │ │ ├── ShapesMenu.tsx
│ │ │ │ ├── StatusBar.tsx
│ │ │ │ ├── ToolsPanel.test.tsx
│ │ │ │ ├── ToolsPanel.tsx
│ │ │ │ └── index.ts
│ │ │ ├── TopPanel
│ │ │ │ ├── LanguageMenu
│ │ │ │ │ └── LanguageMenu.tsx
│ │ │ │ ├── Menu
│ │ │ │ │ ├── Menu.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── MultiplayerMenu
│ │ │ │ │ ├── MultiplayerMenu.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── PageMenu
│ │ │ │ │ ├── PageMenu.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── PageOptionsDialog
│ │ │ │ │ ├── PageOptionsDialog.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── PreferencesMenu
│ │ │ │ │ ├── PreferencesMenu.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── StyleMenu
│ │ │ │ │ ├── StyleMenu.test.tsx
│ │ │ │ │ ├── StyleMenu.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── TopPanel.tsx
│ │ │ │ ├── ZoomMenu
│ │ │ │ │ ├── ZoomMenu.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ │ ├── breakpoints.tsx
│ │ │ ├── preventEvent.ts
│ │ │ └── stopPropagation.ts
│ │ ├── constants.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useCursor.ts
│ │ │ ├── useDialog.ts
│ │ │ ├── useFileSystem.ts
│ │ │ ├── useFileSystemHandlers.ts
│ │ │ ├── useKeyboardShortcuts.tsx
│ │ │ ├── useTheme.ts
│ │ │ ├── useTldrawApp.tsx
│ │ │ └── useTranslation.ts
│ │ ├── index.ts
│ │ ├── state
│ │ │ ├── IdbClipboard.ts
│ │ │ ├── StateManager
│ │ │ │ ├── StateManager.ts
│ │ │ │ ├── copy.ts
│ │ │ │ └── index.ts
│ │ │ ├── TLDR.ts
│ │ │ ├── TldrawApp.spec.ts
│ │ │ ├── TldrawApp.ts
│ │ │ ├── __snapshots__
│ │ │ │ └── TldrawApp.spec.ts.snap
│ │ │ ├── commands
│ │ │ │ ├── alignShapes
│ │ │ │ │ ├── alignShapes.spec.ts
│ │ │ │ │ ├── alignShapes.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── changePage
│ │ │ │ │ ├── changePage.spec.ts
│ │ │ │ │ ├── changePage.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── createPage
│ │ │ │ │ ├── createPage.spec.ts
│ │ │ │ │ ├── createPage.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── createShapes
│ │ │ │ │ ├── createShapes.spec.ts
│ │ │ │ │ ├── createShapes.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── deletePage
│ │ │ │ │ ├── deletePage.spec.ts
│ │ │ │ │ ├── deletePage.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── deleteShapes
│ │ │ │ │ ├── deleteShapes.spec.ts
│ │ │ │ │ ├── deleteShapes.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── distributeShapes
│ │ │ │ │ ├── distributeShapes.spec.ts
│ │ │ │ │ ├── distributeShapes.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── duplicatePage
│ │ │ │ │ ├── duplicatePage.spec.ts
│ │ │ │ │ ├── duplicatePage.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── duplicateShapes
│ │ │ │ │ ├── duplicateShapes.spec.ts
│ │ │ │ │ ├── duplicateShapes.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── flipShapes
│ │ │ │ │ ├── flipShapes.spec.ts
│ │ │ │ │ ├── flipShapes.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── groupShapes
│ │ │ │ │ ├── groupShapes.spec.ts
│ │ │ │ │ ├── groupShapes.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── insertContent
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── insertContent.spec.ts
│ │ │ │ │ └── insertContent.ts
│ │ │ │ ├── movePage
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── movePage.ts
│ │ │ │ ├── moveShapesToPage
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── moveShapesToPage.spec.ts
│ │ │ │ │ └── moveShapesToPage.ts
│ │ │ │ ├── renamePage
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── renamePage.spec.ts
│ │ │ │ │ └── renamePage.ts
│ │ │ │ ├── reorderShapes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── reorderShapes.spec.ts
│ │ │ │ │ └── reorderShapes.ts
│ │ │ │ ├── resetBounds
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── resetBounds.spec.ts
│ │ │ │ │ └── resetBounds.ts
│ │ │ │ ├── rotateShapes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── rotateShapes.spec.ts
│ │ │ │ │ └── rotateShapes.ts
│ │ │ │ ├── setShapesProps
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── setShapesProps.spec.ts
│ │ │ │ │ └── setShapesProps.ts
│ │ │ │ ├── shared
│ │ │ │ │ ├── getIncrementedName.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── removeShapesFromPage.ts
│ │ │ │ ├── stretchShapes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── stretchShapes.spec.ts
│ │ │ │ │ └── stretchShapes.ts
│ │ │ │ ├── styleShapes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── styleShapes.spec.ts
│ │ │ │ │ └── styleShapes.ts
│ │ │ │ ├── toggleShapesDecoration
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── toggleShapesDecoration.spec.ts
│ │ │ │ │ └── toggleShapesDecoration.ts
│ │ │ │ ├── toggleShapesProp
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── toggleShapesProp.spec.ts
│ │ │ │ │ └── toggleShapesProp.ts
│ │ │ │ ├── translateShapes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── translateShapes.spec.ts
│ │ │ │ │ └── translateShapes.ts
│ │ │ │ ├── ungroupShapes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── ungroupShapes.spec.ts
│ │ │ │ │ └── ungroupShapes.ts
│ │ │ │ └── updateShapes
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── updateShapes.spec.ts
│ │ │ │ │ └── updateShapes.ts
│ │ │ ├── data
│ │ │ │ ├── filesystem.spec.ts
│ │ │ │ ├── filesystem.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── migrate.spec.ts
│ │ │ │ └── migrate.ts
│ │ │ ├── index.ts
│ │ │ ├── internal.ts
│ │ │ ├── sessions
│ │ │ │ ├── ArrowSession
│ │ │ │ │ ├── ArrowSession.spec.ts
│ │ │ │ │ ├── ArrowSession.ts
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── ArrowSession.spec.ts.snap
│ │ │ │ │ ├── arrows.tldr
│ │ │ │ │ └── index.ts
│ │ │ │ ├── BaseSession.ts
│ │ │ │ ├── BrushSession
│ │ │ │ │ ├── BrushSession.spec.ts
│ │ │ │ │ ├── BrushSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── DrawSession
│ │ │ │ │ ├── DrawSession.spec.ts
│ │ │ │ │ ├── DrawSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── EditSession
│ │ │ │ │ ├── EditSession.spec.ts
│ │ │ │ │ ├── EditSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── EraseSession
│ │ │ │ │ ├── EraseSession.spec.ts
│ │ │ │ │ ├── EraseSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── GridSession
│ │ │ │ │ ├── GridSession.spec.ts
│ │ │ │ │ ├── GridSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── HandleSession
│ │ │ │ │ ├── HandleSession.spec.ts
│ │ │ │ │ ├── HandleSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── RotateSession
│ │ │ │ │ ├── RotateSession.spec.ts
│ │ │ │ │ ├── RotateSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── TransformSession
│ │ │ │ │ ├── TransformSession.spec.ts
│ │ │ │ │ ├── TransformSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── TransformSingleSession
│ │ │ │ │ ├── TransformSingleSession.spec.ts
│ │ │ │ │ ├── TransformSingleSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── TranslateLabelSession
│ │ │ │ │ ├── TranslateLabelSession.spec.ts
│ │ │ │ │ ├── TranslateLabelSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── TranslateSession
│ │ │ │ │ ├── TranslateSession.spec.ts
│ │ │ │ │ ├── TranslateSession.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── about-sessions.md
│ │ │ │ └── index.ts
│ │ │ ├── shapes
│ │ │ │ ├── ArrowUtil
│ │ │ │ │ ├── ArrowUtil.spec.tsx
│ │ │ │ │ ├── ArrowUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── ArrowUtil.spec.tsx.snap
│ │ │ │ │ ├── arrowHelpers.ts
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── ArrowHead.tsx
│ │ │ │ │ │ ├── CurvedArrow.tsx.tsx
│ │ │ │ │ │ └── StraightArrow.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── DrawUtil
│ │ │ │ │ ├── DrawUtil.spec.tsx
│ │ │ │ │ ├── DrawUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── DrawUtil.spec.tsx.snap
│ │ │ │ │ ├── drawHelpers.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── EllipseUtil
│ │ │ │ │ ├── EllipseUtil.spec.tsx
│ │ │ │ │ ├── EllipseUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── EllipseUtil.spec.tsx.snap
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── DashedEllipse.tsx
│ │ │ │ │ │ └── DrawEllipse.tsx
│ │ │ │ │ ├── ellipseHelpers.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── GroupUtil
│ │ │ │ │ ├── GroupUtil.spec.tsx
│ │ │ │ │ ├── GroupUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── GroupUtil.spec.tsx.snap
│ │ │ │ │ └── index.ts
│ │ │ │ ├── ImageUtil
│ │ │ │ │ ├── ImageUtil.spec.tsx
│ │ │ │ │ ├── ImageUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── ImageUtil.spec.tsx.snap
│ │ │ │ │ └── index.ts
│ │ │ │ ├── RectangleUtil
│ │ │ │ │ ├── RectangleUtil.spec.tsx
│ │ │ │ │ ├── RectangleUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── RectangleUtil.spec.tsx.snap
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── BindingIndicator.tsx
│ │ │ │ │ │ ├── DashedRectangle.tsx
│ │ │ │ │ │ └── DrawRectangle.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── rectangleHelpers.ts
│ │ │ │ ├── StickyUtil
│ │ │ │ │ ├── StickyUtil.spec.tsx
│ │ │ │ │ ├── StickyUtil.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── TDShapeUtil.tsx
│ │ │ │ ├── TextUtil
│ │ │ │ │ ├── TextUtil.spec.tsx
│ │ │ │ │ ├── TextUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── TextUtil.spec.tsx.snap
│ │ │ │ │ └── index.ts
│ │ │ │ ├── TriangleUtil
│ │ │ │ │ ├── TriangleUtil.spec.tsx
│ │ │ │ │ ├── TriangleUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── TriangleUtil.spec.tsx.snap
│ │ │ │ │ ├── components
│ │ │ │ │ │ ├── DashedTriangle.tsx
│ │ │ │ │ │ ├── DrawTriangle.tsx
│ │ │ │ │ │ └── TriangleBindingIndicator.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── triangleHelpers.ts
│ │ │ │ ├── VideoUtil
│ │ │ │ │ ├── VideoUtil.spec.tsx
│ │ │ │ │ ├── VideoUtil.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── VideoUtil.spec.tsx.snap
│ │ │ │ │ └── index.ts
│ │ │ │ ├── about-shape-utils.md
│ │ │ │ ├── index.ts
│ │ │ │ └── shared
│ │ │ │ │ ├── LabelMask.tsx
│ │ │ │ │ ├── PolygonUtils.ts
│ │ │ │ │ ├── TextAreaUtils.ts
│ │ │ │ │ ├── TextLabel.tsx
│ │ │ │ │ ├── getBoundsRectangle.ts
│ │ │ │ │ ├── getTextAlign.ts
│ │ │ │ │ ├── getTextSize.ts
│ │ │ │ │ ├── getTextSvgElement.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── shape-styles.ts
│ │ │ │ │ ├── transformRectangle.ts
│ │ │ │ │ └── transformSingleRectangle.ts
│ │ │ └── tools
│ │ │ │ ├── ArrowTool
│ │ │ │ ├── ArrowTool.spec.ts
│ │ │ │ ├── ArrowTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── BaseTool.ts
│ │ │ │ ├── DrawTool
│ │ │ │ ├── DrawTool.spec.ts
│ │ │ │ ├── DrawTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── EllipseTool
│ │ │ │ ├── EllipseTool.spec.ts
│ │ │ │ ├── EllipseTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── EraseTool
│ │ │ │ ├── EraseTool.spec.ts
│ │ │ │ ├── EraseTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── LineTool
│ │ │ │ ├── LineTool.spec.ts
│ │ │ │ ├── LineTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── RectangleTool
│ │ │ │ ├── RectangleTool.spec.ts
│ │ │ │ ├── RectangleTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── SelectTool
│ │ │ │ ├── SelectTool.spec.ts
│ │ │ │ ├── SelectTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── StickyTool
│ │ │ │ ├── StickyTool.spec.ts
│ │ │ │ ├── StickyTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── TextTool
│ │ │ │ ├── TextTool.spec.ts
│ │ │ │ ├── TextTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── TriangleTool
│ │ │ │ ├── TriangleTool.spec.ts
│ │ │ │ ├── TriangleTool.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── about-tools.md
│ │ │ │ └── index.ts
│ │ ├── styles
│ │ │ ├── index.ts
│ │ │ └── stitches.config.ts
│ │ ├── test
│ │ │ ├── TldrawTestApp.tsx
│ │ │ ├── accessibility.spec.tsx
│ │ │ ├── badDocument.spec.ts
│ │ │ ├── documents
│ │ │ │ ├── badDocument.ts
│ │ │ │ ├── old-doc-2.ts
│ │ │ │ └── old-doc.ts
│ │ │ ├── index.ts
│ │ │ ├── mockDocument.tsx
│ │ │ ├── renderWithContext.tsx
│ │ │ ├── renderWithIntlProvider.tsx
│ │ │ └── stylemock.ts
│ │ ├── translations
│ │ │ ├── ar.json
│ │ │ ├── da.json
│ │ │ ├── de.json
│ │ │ ├── en.json
│ │ │ ├── es.json
│ │ │ ├── fa.json
│ │ │ ├── fr.json
│ │ │ ├── gl.json
│ │ │ ├── he.json
│ │ │ ├── index.ts
│ │ │ ├── it.json
│ │ │ ├── ja.json
│ │ │ ├── ko-kr.json
│ │ │ ├── ku.json
│ │ │ ├── main.json
│ │ │ ├── my.json
│ │ │ ├── nb-no.json
│ │ │ ├── ne.json
│ │ │ ├── nn-no.json
│ │ │ ├── pl.json
│ │ │ ├── pt-br.json
│ │ │ ├── pt-pt.json
│ │ │ ├── ru.json
│ │ │ ├── sv.json
│ │ │ ├── te.json
│ │ │ ├── th.json
│ │ │ ├── tr.json
│ │ │ ├── translations.ts
│ │ │ ├── uk.json
│ │ │ ├── zh-cn.json
│ │ │ └── zh-tw.json
│ │ └── types.ts
│ ├── tldraw-assets.json
│ ├── tsconfig.build.json
│ ├── tsconfig.dev.json
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
└── vec
│ ├── CHANGELOG.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ └── index.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.dev.json
│ └── tsconfig.json
├── repo-map.tldr
├── setupTests.ts
├── tsconfig.base.json
├── tsconfig.json
├── turbo.json
└── yarn.lock
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "linked": [],
6 | "access": "public",
7 | "baseBranch": "main",
8 | "updateInternalDependencies": "patch",
9 | "ignore": []
10 | }
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/*
2 | **/out/*
3 | **/.next/*
4 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [steveruizok]
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Writing and other documentation.
4 | title: '[bug] Bug description'
5 | labels: bug
6 | assignees: ''
7 | ---
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: New tldraw
4 | url: https://github.com/tldraw/tldraw/issues/new
5 | about: Is your issue for the new tldraw? Report it on the new repo instead.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Documentation
3 | about: Writing and other documentation.
4 | title: '[documentation] Content'
5 | labels: documentation
6 | assignees: ''
7 | ---
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature
3 | about: Begin discussion of a new feature.
4 | title: '[feature] Feature or improvement'
5 | labels: enhancement
6 | assignees: ''
7 | ---
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/testing.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Testing
3 | about: Tests that need to be written.
4 | title: '[tests] Test'
5 | labels: testing
6 | assignees: ''
7 | ---
8 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: push
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v2
8 |
9 | # turbo cache
10 | - name: Turbo Cache
11 | id: turbo-cache
12 | uses: actions/cache@v2
13 | with:
14 | path: node_modules/.cache/turbo
15 | key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
16 | restore-keys: |
17 | turbo-${{ github.job }}-${{ github.ref_name }}-
18 |
19 | # install modules
20 | - name: Install modules
21 | run: yarn
22 |
23 | # build
24 | - name: Build Packages
25 | run: yarn build:packages --cache-dir=".turbo"
26 |
27 | # lint
28 | - name: Lint
29 | run: yarn lint
30 |
31 | # run unit tests
32 | - name: Jest Annotations & Coverage
33 | run: yarn test:ci
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | lib/
4 | dist/
5 | docs/
6 | .idea/*
7 |
8 | .DS_Store
9 | coverage
10 | *.log
11 |
12 | .vercel
13 | .next
14 | apps/www/public/workbox-*
15 | apps/www/public/worker-*
16 | apps/www/public/sw.js
17 | apps/www/public/sw.js.map
18 | .env
19 | firebase.config.*.turbo
20 | .turbo
21 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn run pre-commit
5 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn run pre-push
5 |
--------------------------------------------------------------------------------
/.ignore:
--------------------------------------------------------------------------------
1 | # Ignored Files for Search
2 |
3 | dist
4 | node_modules
5 | *.d.ts
6 | *.js
7 | *.md
8 | *.lock
9 | *.tsbuildinfo
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.github/
2 | /node_modules/
3 | /build/
4 | /tmp/
5 | .idea/*
6 |
7 | coverage
8 | *.log
9 | .gitlab-ci.yml
10 |
11 | package-lock.json
12 | /*.tgz
13 | /tmp*
14 | /mnt/
15 | /package/
16 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | assets
2 | **/assets
3 | **/coverage
4 | **/dist
5 | **/extension/editor
6 | **/out
7 | **/public
8 |
9 | .DS_Store
10 | .next
11 | .tldr
12 | .turbo
13 | .vercel
14 | .vsix
15 |
16 | *.lock
17 | *.log
18 | *.yaml
19 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "singleQuote": true,
4 | "semi": false,
5 | "printWidth": 100,
6 | "importOrder": ["^[~]", "^[./]"],
7 | "importOrderSortSpecifiers": true,
8 | "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"]
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["esbenp.prettier-vscode", "tldraw-org.tldraw-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/snippets.code-snippets:
--------------------------------------------------------------------------------
1 | {
2 | "createComment": {
3 | "scope": "typescript,typescriptreact",
4 | "prefix": "ccc",
5 | "body": [
6 | "/**",
7 | " * ${1:description}",
8 | " *",
9 | " * ### Example",
10 | " *",
11 | " *```ts",
12 | " * ${2:example}",
13 | " *```"
14 | ],
15 | "description": "comment"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/apps/www/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "next/core-web-vitals",
4 | "rules": {
5 | "@typescript-eslint/no-explicit-any": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/www/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/apps/www/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | # @tldraw/www
6 |
7 | The [tldraw](https://tldraw.com) website.
8 |
--------------------------------------------------------------------------------
/apps/www/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/apps/www/next.config.js:
--------------------------------------------------------------------------------
1 | const withPWA = require('next-pwa')
2 | const withTM = require('next-transpile-modules')
3 |
4 | const { GITHUB_ID, GITHUB_API_SECRET, NODE_ENV, VERCEL_GIT_COMMIT_SHA, GA_MEASUREMENT_ID } =
5 | process.env
6 |
7 | const isProduction = NODE_ENV === 'production'
8 |
9 | module.exports = withTM(['@tldraw/tldraw', '@tldraw/core'])(
10 | withPWA({
11 | reactStrictMode: true,
12 | pwa: {
13 | disable: !isProduction,
14 | dest: 'public',
15 | },
16 | productionBrowserSourceMaps: true,
17 | env: {
18 | NEXT_PUBLIC_COMMIT_SHA: VERCEL_GIT_COMMIT_SHA,
19 | GA_MEASUREMENT_ID,
20 | GITHUB_ID,
21 | GITHUB_API_SECRET,
22 | },
23 | })
24 | )
25 |
--------------------------------------------------------------------------------
/apps/www/pages/api/upload.ts:
--------------------------------------------------------------------------------
1 | import aws from 'aws-sdk'
2 |
3 | export default async function handler(req, res) {
4 | aws.config.update({
5 | accessKeyId: process.env.TL_AWS_ACCESS_KEY,
6 | secretAccessKey: process.env.TL_AWS_SECRET_KEY,
7 | region: process.env.TL_AWS_REGION,
8 | signatureVersion: 'v4',
9 | })
10 |
11 | const s3 = new aws.S3()
12 |
13 | const post = s3.createPresignedPost({
14 | Bucket: process.env.TL_AWS_BUCKET_NAME,
15 | Fields: {
16 | key: req.query.file,
17 | 'Content-Type': req.query.fileType,
18 | },
19 | Expires: 60, // seconds
20 | Conditions: [
21 | ['content-length-range', 0, 5242880], // up to 5 MB
22 | ],
23 | })
24 |
25 | res.status(200).json(post)
26 | }
27 |
--------------------------------------------------------------------------------
/apps/www/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import dynamic from 'next/dynamic'
2 | import Head from 'next/head'
3 | import { useRouter } from 'next/router'
4 | import { useMemo } from 'react'
5 |
6 | const Editor = dynamic(() => import('~components/Editor'), { ssr: false }) as any
7 |
8 | const Home = () => {
9 | const { query } = useRouter()
10 | const isExportMode = useMemo(() => 'exportMode' in query, [query])
11 |
12 | return (
13 | <>
14 |
15 | tldraw
16 |
17 |
18 | >
19 | )
20 | }
21 |
22 | export default Home
23 |
--------------------------------------------------------------------------------
/apps/www/pages/r/[id].tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSideProps } from 'next'
2 | import dynamic from 'next/dynamic'
3 | import Head from 'next/head'
4 | import * as React from 'react'
5 |
6 | const IFrameWarning = dynamic(() => import('~components/IFrameWarning'), {
7 | ssr: false,
8 | }) as any
9 |
10 | const MultiplayerEditor = dynamic(() => import('~components/MultiplayerEditor'), {
11 | ssr: false,
12 | }) as any
13 |
14 | interface RoomProps {
15 | id: string
16 | }
17 |
18 | export default function Room({ id }: RoomProps) {
19 | if (typeof window !== 'undefined' && window.self !== window.top) {
20 | return
21 | }
22 |
23 | return (
24 | <>
25 |
26 | tldraw - {id}
27 |
28 |
29 | >
30 | )
31 | }
32 |
33 | export const getServerSideProps: GetServerSideProps = async (context) => {
34 | const id = context.query.id?.toString()
35 |
36 | return {
37 | props: {
38 | id,
39 | },
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/apps/www/pages/r/index.tsx:
--------------------------------------------------------------------------------
1 | import type { GetServerSideProps } from 'next'
2 | import Head from 'next/head'
3 | import * as React from 'react'
4 |
5 | export default function RandomRoomPage() {
6 | return (
7 | <>
8 |
9 | tldraw
10 |
11 | >
12 | )
13 | }
14 |
15 | export const getServerSideProps: GetServerSideProps = async (context) => {
16 | // Generate random id
17 | const id = Date.now().toString()
18 |
19 | // Route to a room with that id
20 | context.res.setHeader('Location', `/r/${id}`)
21 | context.res.statusCode = 307
22 |
23 | // Return id (though it shouldn't matter)
24 | return {
25 | props: {},
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apps/www/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/apps/www/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/apps/www/public/android-chrome-maskable-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/android-chrome-maskable-192x192.png
--------------------------------------------------------------------------------
/apps/www/public/android-chrome-maskable-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/android-chrome-maskable-512x512.png
--------------------------------------------------------------------------------
/apps/www/public/android-chrome-maskable-beta-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/android-chrome-maskable-beta-512x512.png
--------------------------------------------------------------------------------
/apps/www/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/apps/www/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/favicon-16x16.png
--------------------------------------------------------------------------------
/apps/www/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/favicon-32x32.png
--------------------------------------------------------------------------------
/apps/www/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/favicon.ico
--------------------------------------------------------------------------------
/apps/www/public/flat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/flat.png
--------------------------------------------------------------------------------
/apps/www/public/images/hello.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/images/hello.mp4
--------------------------------------------------------------------------------
/apps/www/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tldraw",
3 | "short_name": "tldraw",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-512x512.png",
7 | "sizes": "512x512",
8 | "type": "image/png",
9 | "purpose": "any"
10 | },
11 | {
12 | "src": "/android-chrome-maskable-512x512.png",
13 | "sizes": "512x512",
14 | "type": "image/png",
15 | "purpose": "any maskable"
16 | },
17 | {
18 | "src": "/android-chrome-192x192.png",
19 | "sizes": "192x192",
20 | "type": "image/png",
21 | "purpose": "any"
22 | },
23 | {
24 | "src": "/android-chrome-maskable-192x192.png",
25 | "sizes": "192x192",
26 | "type": "image/png",
27 | "purpose": "any maskable"
28 | }
29 | ],
30 | "theme_color": "#ffffff",
31 | "background_color": "#ffffff",
32 | "start_url": "/",
33 | "display": "standalone",
34 | "orientation": "portrait"
35 | }
36 |
--------------------------------------------------------------------------------
/apps/www/public/social-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/apps/www/public/social-image.png
--------------------------------------------------------------------------------
/apps/www/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
6 | Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | .tldraw {
19 | position: fixed;
20 | top: 0px;
21 | left: 0px;
22 | right: 0px;
23 | bottom: 0px;
24 | width: 100%;
25 | height: 100%;
26 | }
27 |
--------------------------------------------------------------------------------
/apps/www/styles/index.ts:
--------------------------------------------------------------------------------
1 | export * from './stitches.config'
2 |
--------------------------------------------------------------------------------
/apps/www/types.ts:
--------------------------------------------------------------------------------
1 | export {}
2 |
--------------------------------------------------------------------------------
/apps/www/utils/export.ts:
--------------------------------------------------------------------------------
1 | import { TDExport } from '@tldraw/tldraw'
2 |
3 | export const EXPORT_ENDPOINT =
4 | process.env.NODE_ENV === 'development'
5 | ? 'http://localhost:3000/api/export'
6 | : 'https://www.tldraw.com/api/export'
7 |
8 | // export async function exportToImage(info: TDExport) {
9 | // if (info.serialized) {
10 | // const link = document.createElement('a')
11 | // link.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(info.serialized)
12 | // link.download = info.name + '.' + info.type
13 | // link.click()
14 |
15 | // return
16 | // }
17 |
18 | // const response = await fetch(EXPORT_ENDPOINT, {
19 | // method: 'POST',
20 | // headers: { 'Content-Type': 'application/json' },
21 | // body: JSON.stringify(info),
22 | // })
23 | // const blob = await response.blob()
24 | // const blobUrl = URL.createObjectURL(blob)
25 | // const link = document.createElement('a')
26 | // link.href = blobUrl
27 | // link.download = info.name + '.' + info.type
28 | // link.click()
29 | // }
30 |
--------------------------------------------------------------------------------
/apps/www/utils/gtag.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | export const GA_TRACKING_ID = process.env.GA_MEASUREMENT_ID
3 |
4 | type GTagEvent = {
5 | action: string
6 | category: string
7 | label: string
8 | value: number
9 | }
10 |
11 | export const pageview = (url: URL): void => {
12 | if ('gtag' in window) {
13 | const win = window as any
14 | win?.gtag('config', GA_TRACKING_ID, {
15 | page_path: url,
16 | })
17 | }
18 | }
19 |
20 | export const event = ({ action, category, label, value }: GTagEvent): void => {
21 | if ('gtag' in window) {
22 | const win = window as any
23 | win?.gtag('event', action, {
24 | event_category: category,
25 | event_label: label,
26 | value: value,
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/apps/www/utils/useGtag.ts:
--------------------------------------------------------------------------------
1 | import router from 'next/router'
2 | import { useEffect } from 'react'
3 | import * as gtag from './gtag'
4 |
5 | function handleRouteChange(url: URL) {
6 | gtag.pageview(url)
7 | }
8 |
9 | export default function useGtag() {
10 | useEffect(() => {
11 | if (process.env.NODE_ENV !== 'production') return
12 |
13 | router.events.on('routeChangeComplete', handleRouteChange)
14 |
15 | return () => {
16 | router.events.off('routeChangeComplete', handleRouteChange)
17 | }
18 | }, [])
19 | }
20 |
--------------------------------------------------------------------------------
/apps/www/worker/index.js:
--------------------------------------------------------------------------------
1 | self.__WB_DISABLE_DEV_LOGS = true
2 |
--------------------------------------------------------------------------------
/assets/card-repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/assets/card-repo.png
--------------------------------------------------------------------------------
/assets/icon_flat_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/assets/icon_flat_black.png
--------------------------------------------------------------------------------
/assets/icon_flat_black_on_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/assets/icon_flat_black_on_white.png
--------------------------------------------------------------------------------
/assets/icon_flat_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/assets/icon_flat_white.png
--------------------------------------------------------------------------------
/assets/icon_flat_white_on_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/assets/icon_flat_white_on_black.png
--------------------------------------------------------------------------------
/assets/recording.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/assets/recording.gif
--------------------------------------------------------------------------------
/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/assets/screenshot.png
--------------------------------------------------------------------------------
/assets/tldraw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/assets/tldraw.png
--------------------------------------------------------------------------------
/examples/core-example-advanced/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .idea
17 | .DS_Store
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | # @tldraw/core-example-advanced
6 |
7 | An advanced example project for `@tldraw/core`.
8 |
9 | To start this project:
10 |
11 | 1. Run `yarn` from the repository's root directory
12 | 2. Run `yarn start:core` from the repository's root directory
13 | 3. Open http://localhost:5421/ in your browser
14 |
15 | As an alternative to running `yarn start:core` you can also:
16 |
17 | 1. Run `yarn build` from the repository's root directory
18 | 2. Run `yarn dev` from the `examples/core-example-advanced` directory
19 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/card-repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/examples/core-example-advanced/card-repo.png
--------------------------------------------------------------------------------
/examples/core-example-advanced/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | tldraw - core example advanced
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/examples/core-example-advanced/public/favicon.ico
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/components/TitleLinks.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import styled from 'stitches.config'
3 |
4 | export function TitleLinks() {
5 | return (
6 |
7 | @tldraw/core
8 |
9 | )
10 | }
11 |
12 | const TitleLinksContainer = styled('div', {
13 | position: 'fixed',
14 | top: 0,
15 | left: 0,
16 | width: '100%',
17 | display: 'flex',
18 | alignItems: 'center',
19 | justifyContent: 'center',
20 | zIndex: 100,
21 | fontSize: '$3',
22 |
23 | '& > a': {
24 | color: '$text',
25 | textDecoration: 'none',
26 | padding: '$2 $4',
27 | fontWeight: '$2',
28 | pointerEvents: 'all',
29 | '&:hover': {
30 | textDecoration: 'underline',
31 | },
32 | },
33 | })
34 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './styles.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/CustomShapeUtil.ts:
--------------------------------------------------------------------------------
1 | import { TLBounds, TLShape, TLShapeUtil } from '@tldraw/core'
2 |
3 | export abstract class CustomShapeUtil<
4 | T extends TLShape,
5 | E extends Element = Element
6 | > extends TLShapeUtil {
7 | /* ----------------- Custom Methods ----------------- */
8 |
9 | canBind = false
10 |
11 | hideBounds = false
12 |
13 | abstract getCenter: (shape: T) => number[]
14 |
15 | abstract getShape: (shape: Partial) => T
16 |
17 | abstract transform: (shape: T, bounds: TLBounds, initialShape: T, scale: number[]) => void
18 |
19 | abstract hitTestPoint: (shape: T, point: number[]) => boolean
20 |
21 | abstract hitTestLineSegment: (shape: T, A: number[], B: number[]) => boolean
22 | }
23 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/arrow/ArrowIndicator.tsx:
--------------------------------------------------------------------------------
1 | import { TLShapeUtil } from '@tldraw/core'
2 | import Vec from '@tldraw/vec'
3 | import * as React from 'react'
4 | import type { ArrowShape } from './ArrowShape'
5 |
6 | export const ArrowIndicator = TLShapeUtil.Indicator(({ shape }) => {
7 | const { start, end } = shape.handles
8 |
9 | const u = Vec.uni(Vec.sub(end.point, start.point))
10 | const dist = Vec.dist(end.point, start.point)
11 | const length = Math.min(18, dist / 2)
12 | const ahLeft = Vec.rotWith(Vec.sub(end.point, Vec.mul(u, length)), end.point, -Math.PI / 6)
13 | const ahRight = Vec.rotWith(Vec.sub(end.point, Vec.mul(u, length)), end.point, Math.PI / 6)
14 |
15 | return (
16 |
24 | )
25 | })
26 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/arrow/ArrowShape.ts:
--------------------------------------------------------------------------------
1 | import type { TLShape } from '@tldraw/core'
2 |
3 | export interface ArrowShape extends TLShape {
4 | type: 'arrow'
5 | handles: {
6 | start: {
7 | id: 'start'
8 | index: number
9 | point: number[]
10 | }
11 | end: {
12 | id: 'end'
13 | index: number
14 | point: number[]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/arrow/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ArrowShape'
2 | export * from './ArrowUtil'
3 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/box/BoxComponent.tsx:
--------------------------------------------------------------------------------
1 | import { SVGContainer, TLShapeUtil } from '@tldraw/core'
2 | import * as React from 'react'
3 | import type { BoxShape } from './BoxShape'
4 |
5 | export const BoxComponent = TLShapeUtil.Component(
6 | ({ shape, events, isGhost, meta }, ref) => {
7 | const color = meta.isDarkMode ? 'white' : 'black'
8 |
9 | return (
10 |
11 |
22 |
23 | )
24 | }
25 | )
26 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/box/BoxIndicator.tsx:
--------------------------------------------------------------------------------
1 | import { TLShapeUtil } from '@tldraw/core'
2 | import * as React from 'react'
3 | import type { BoxShape } from './BoxShape'
4 |
5 | export const BoxIndicator = TLShapeUtil.Indicator(({ shape }) => {
6 | return (
7 |
16 | )
17 | })
18 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/box/BoxShape.ts:
--------------------------------------------------------------------------------
1 | import type { TLShape } from '@tldraw/core'
2 |
3 | export interface BoxShape extends TLShape {
4 | type: 'box'
5 | size: number[]
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/box/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BoxShape'
2 | export * from './BoxUtil'
3 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/index.ts:
--------------------------------------------------------------------------------
1 | import { TLShapeUtilsMap } from '@tldraw/core'
2 | import type { CustomShapeUtil } from './CustomShapeUtil'
3 | import { ArrowShape, ArrowUtil } from './arrow'
4 | import { BoxShape, BoxUtil } from './box'
5 | import { PencilShape, PencilUtil } from './pencil'
6 |
7 | export * from './arrow'
8 | export * from './pencil'
9 | export * from './box'
10 |
11 | export type Shape = BoxShape | ArrowShape | PencilShape
12 |
13 | export const shapeUtils = {
14 | box: new BoxUtil(),
15 | arrow: new ArrowUtil(),
16 | pencil: new PencilUtil(),
17 | }
18 |
19 | export const getShapeUtils = (shape: T | T['type']) => {
20 | if (typeof shape === 'string') return shapeUtils[shape] as unknown as CustomShapeUtil
21 | return shapeUtils[shape.type] as unknown as CustomShapeUtil
22 | }
23 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/pencil/PencilComponent.tsx:
--------------------------------------------------------------------------------
1 | import { SVGContainer, TLShapeUtil } from '@tldraw/core'
2 | import * as React from 'react'
3 | import type { PencilShape } from './PencilShape'
4 | import { getComponentSvgPath } from './pencil-helpers'
5 |
6 | export const PencilComponent = TLShapeUtil.Component(
7 | ({ shape, events, isGhost, meta }, ref) => {
8 | const color = meta.isDarkMode ? 'white' : 'black'
9 | const pathData = getComponentSvgPath(shape.points)
10 | return (
11 |
12 |
13 |
14 |
15 | )
16 | }
17 | )
18 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/pencil/PencilShape.ts:
--------------------------------------------------------------------------------
1 | import type { TLShape } from '@tldraw/core'
2 |
3 | export interface PencilShape extends TLShape {
4 | type: 'pencil'
5 | points: number[][]
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/pencil/PenclIndicator.tsx:
--------------------------------------------------------------------------------
1 | import { TLShapeUtil } from '@tldraw/core'
2 | import * as React from 'react'
3 | import type { PencilShape } from './PencilShape'
4 | import { getIndicatorSvgPath } from './pencil-helpers'
5 |
6 | export const PencilIndicator = TLShapeUtil.Indicator(({ shape }) => {
7 | return (
8 |
16 | )
17 | })
18 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/shapes/pencil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PencilShape'
2 | export * from './PencilUtil'
3 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/bindings/createBindings.ts:
--------------------------------------------------------------------------------
1 | import type { TLBinding } from '@tldraw/core'
2 | import { nanoid } from 'nanoid'
3 | import type { Action, CustomBinding } from 'state/constants'
4 |
5 | export const createBindings: Action = (
6 | data,
7 | payload: {
8 | bindings: (Partial & Pick)[]
9 | }
10 | ) => {
11 | payload.bindings.forEach((partial) => {
12 | const binding = {
13 | id: nanoid(),
14 | ...partial,
15 | }
16 |
17 | data.page.bindings[binding.id] = binding
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/bindings/deleteBindings.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const deleteBindings: Action = (data, payload: { ids: string[] }) => {
4 | try {
5 | payload.ids.forEach((id) => {
6 | delete data.page.bindings[id]
7 | })
8 | } catch (e: any) {
9 | e.message = 'Could not delete bindings: ' + e.message
10 | console.error(e)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/bindings/index.ts:
--------------------------------------------------------------------------------
1 | export * from './updateBoundShapes'
2 | export * from './createBindings'
3 | export * from './removePartialBindings'
4 | export * from './updateBindings'
5 | export * from './deleteBindings'
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/bindings/removePartialBindings.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | // Remove bindings from selected shapes to shapes that aren't also selected
4 | export const removePartialBindings: Action = (data) => {
5 | const { selectedIds } = data.pageState
6 |
7 | const bindings = Object.values(data.page.bindings)
8 |
9 | bindings
10 | .filter((binding) => selectedIds.includes(binding.fromId))
11 | .forEach((binding) => {
12 | if (!selectedIds.includes(binding.toId)) {
13 | delete data.page.bindings[binding.id]
14 | }
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/bindings/updateBindings.ts:
--------------------------------------------------------------------------------
1 | import type { TLBinding } from '@tldraw/core'
2 | import type { Action } from 'state/constants'
3 |
4 | export const updateBindings: Action = (
5 | data,
6 | payload: { bindings: (Partial & Pick)[] }
7 | ) => {
8 | try {
9 | payload.bindings.forEach((partial) => {
10 | Object.assign(data.page.bindings[partial.id], partial)
11 | })
12 | } catch (e: any) {
13 | e.message = 'Could not update shapes: ' + e.message
14 | console.error(e)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/camera/index.ts:
--------------------------------------------------------------------------------
1 | export * from './panCamera'
2 | export * from './pinchCamera'
3 | export * from './zoomIn'
4 | export * from './zoomOut'
5 | export * from './zoomToSelection'
6 | export * from './zoomToFit'
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/camera/panCamera.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import Vec from '@tldraw/vec'
3 | import type { Action } from 'state/constants'
4 |
5 | export const panCamera: Action = (data, payload: TLPointerInfo) => {
6 | const { point, zoom } = data.pageState.camera
7 | data.pageState.camera.point = Vec.sub(point, Vec.div(payload.delta, zoom))
8 | }
9 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/camera/pinchCamera.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import Vec from '@tldraw/vec'
3 | import type { Action } from 'state/constants'
4 |
5 | export const pinchCamera: Action = (data, payload: TLPointerInfo) => {
6 | const { camera } = data.pageState
7 | const nextZoom = payload.delta[2]
8 | const nextPoint = Vec.sub(camera.point, Vec.div(payload.delta, camera.zoom))
9 | const p0 = Vec.sub(Vec.div(payload.point, camera.zoom), nextPoint)
10 | const p1 = Vec.sub(Vec.div(payload.point, nextZoom), nextPoint)
11 | data.pageState.camera.point = Vec.toFixed(Vec.add(nextPoint, Vec.sub(p1, p0)))
12 | data.pageState.camera.zoom = nextZoom
13 | }
14 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/camera/zoomIn.ts:
--------------------------------------------------------------------------------
1 | import Vec from '@tldraw/vec'
2 | import type { Action } from 'state/constants'
3 | import { mutables } from 'state/mutables'
4 |
5 | export const zoomIn: Action = (data) => {
6 | const { camera } = data.pageState
7 | const i = Math.round((data.pageState.camera.zoom * 100) / 25)
8 | const zoom = Math.min(5, (i + 1) * 0.25)
9 | const center = [mutables.rendererBounds.width / 2, mutables.rendererBounds.height / 2]
10 | const p0 = Vec.sub(Vec.div(center, camera.zoom), center)
11 | const p1 = Vec.sub(Vec.div(center, zoom), center)
12 | const point = Vec.toFixed(Vec.add(camera.point, Vec.sub(p1, p0)))
13 |
14 | data.pageState.camera.zoom = zoom
15 | data.pageState.camera.point = point
16 | }
17 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/camera/zoomOut.ts:
--------------------------------------------------------------------------------
1 | import Vec from '@tldraw/vec'
2 | import type { Action } from 'state/constants'
3 | import { mutables } from 'state/mutables'
4 |
5 | export const zoomOut: Action = (data) => {
6 | const { camera } = data.pageState
7 | const i = Math.round((data.pageState.camera.zoom * 100) / 25)
8 | const zoom = Math.max(0.25, (i - 1) * 0.25)
9 | const center = [mutables.rendererBounds.width / 2, mutables.rendererBounds.height / 2]
10 | const p0 = Vec.sub(Vec.div(center, camera.zoom), center)
11 | const p1 = Vec.sub(Vec.div(center, zoom), center)
12 | const point = Vec.toFixed(Vec.add(camera.point, Vec.sub(p1, p0)))
13 |
14 | data.pageState.camera.zoom = zoom
15 | data.pageState.camera.point = point
16 | }
17 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/data/index.ts:
--------------------------------------------------------------------------------
1 | export * from './loadDocument'
2 | export * from './restoreSavedDocument'
3 | export * from './loadNewDocument'
4 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/data/loadDocument.ts:
--------------------------------------------------------------------------------
1 | import { current } from 'immer'
2 | import type { Action, AppDocument } from 'state/constants'
3 | import { mutables } from 'state/mutables'
4 |
5 | export const loadDocument: Action = (data, payload: { doc: AppDocument }) => {
6 | Object.assign(data, payload.doc)
7 |
8 | const snapshot = current(data)
9 |
10 | mutables.history.reset(snapshot)
11 |
12 | Object.assign(mutables, {
13 | snapshot,
14 | initialPoint: [0, 0],
15 | isCloning: false,
16 | pointedShapeId: undefined,
17 | pointedHandleId: undefined,
18 | pointedBoundsHandleId: undefined,
19 | initialCommonBounds: undefined,
20 | snapInfo: undefined,
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/data/restoreSavedDocument.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const restoreSavedDocument: Action = (data) => {
5 | const snapshot = mutables.history.restore()
6 | Object.assign(data, snapshot)
7 | }
8 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/erase/eraseGhostShapes.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const eraseGhostShapes: Action = (data) => {
4 | const idsToDelete = Object.values(data.page.shapes)
5 | .filter((shape) => shape.isGhost)
6 | .map((shape) => shape.id)
7 |
8 | idsToDelete.forEach((id) => delete data.page.shapes[id])
9 |
10 | data.pageState.selectedIds = data.pageState.selectedIds.filter((id) => !idsToDelete.includes(id))
11 |
12 | if (data.pageState.hoveredId && idsToDelete.includes(data.pageState.hoveredId)) {
13 | data.pageState.hoveredId = undefined
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/erase/eraseShapes.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import { getShapeUtils } from 'shapes'
3 | import type { Action } from 'state/constants'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const eraseShapes: Action = (data, payload: TLPointerInfo) => {
7 | const { previousPoint } = mutables
8 |
9 | Object.values(data.page.shapes)
10 | .filter((shape) => !shape.isGhost)
11 | .forEach((shape) => {
12 | if (getShapeUtils(shape).hitTestLineSegment(shape, previousPoint, mutables.currentPoint)) {
13 | shape.isGhost = true
14 | }
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/erase/eraseShapesAtPoint.ts:
--------------------------------------------------------------------------------
1 | import { getShapeUtils } from 'shapes'
2 | import type { Action } from 'state/constants'
3 | import { mutables } from 'state/mutables'
4 |
5 | export const eraseShapesAtPoint: Action = (data) => {
6 | const { currentPoint } = mutables
7 |
8 | Object.values(data.page.shapes).forEach((shape) => {
9 | if (getShapeUtils(shape).hitTestPoint(shape, currentPoint)) {
10 | delete data.page.shapes[shape.id]
11 | }
12 | })
13 |
14 | const { shapes } = data.page
15 | const { selectedIds, hoveredId } = data.pageState
16 |
17 | // Filter out any deleted shapes
18 | data.pageState.selectedIds = selectedIds.filter((id) => {
19 | return shapes[id] !== undefined
20 | })
21 |
22 | // Remove hovered id if it's been deleted
23 | if (hoveredId && !shapes[hoveredId]) {
24 | data.pageState.hoveredId = undefined
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/erase/index.ts:
--------------------------------------------------------------------------------
1 | export * from './eraseShapes'
2 | export * from './eraseShapesAtPoint'
3 | export * from './eraseGhostShapes'
4 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/handles/clearPointedHandle.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const clearPointedHandle: Action = () => {
5 | mutables.pointedHandleId = undefined
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/handles/index.ts:
--------------------------------------------------------------------------------
1 | export * from './setPointedHandle'
2 | export * from './clearPointedHandle'
3 | export * from './translateHandle'
4 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/handles/setPointedHandle.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const setPointedHandle: Action = (data, payload) => {
5 | mutables.pointedHandleId = payload.target
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/history/addToHistory.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const addToHistory: Action = (data) => {
5 | mutables.history.push(data)
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/history/index.ts:
--------------------------------------------------------------------------------
1 | export * from './addToHistory'
2 | export * from './redo'
3 | export * from './undo'
4 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/history/redo.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const redo: Action = (data) => {
5 | const snapshot = mutables.history.redo()
6 | Object.assign(data, snapshot)
7 | }
8 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/history/undo.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const undo: Action = (data) => {
5 | const snapshot = mutables.history.undo()
6 | Object.assign(data, snapshot)
7 | }
8 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './bindings'
2 | export * from './camera'
3 | export * from './data'
4 | export * from './erase'
5 | export * from './handles'
6 | export * from './history'
7 | export * from './mutables/restoreSnapshot'
8 | export * from './selection'
9 | export * from './shapes'
10 | export * from './snaps'
11 | export * from './transform'
12 | export * from './translate'
13 | export * from './mutables'
14 | export * from './performance'
15 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/mutables/index.ts:
--------------------------------------------------------------------------------
1 | export * from './setInitialPoint'
2 | export * from './setSnapshot'
3 | export * from './setViewport'
4 | export * from './restoreSnapshot'
5 | export * from './updatePointer'
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/mutables/restoreSnapshot.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const restoreSnapshot: Action = (data) => {
5 | Object.assign(data, mutables.snapshot)
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/mutables/setInitialPoint.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import type { Action } from 'state/constants'
3 | import { getPagePoint } from 'state/helpers'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const setInitialPoint: Action = (data, payload: TLPointerInfo) => {
7 | mutables.initialPoint = getPagePoint(payload.origin, data.pageState)
8 | mutables.previousPoint = [...mutables.initialPoint]
9 | }
10 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/mutables/setSnapshot.ts:
--------------------------------------------------------------------------------
1 | import { current } from 'immer'
2 | import type { Action } from 'state/constants'
3 | import { mutables } from 'state/mutables'
4 |
5 | export const setSnapshot: Action = (data) => {
6 | mutables.snapshot = current(data)
7 | }
8 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/mutables/setViewport.ts:
--------------------------------------------------------------------------------
1 | import type { TLBounds } from '@tldraw/core'
2 | import Vec from '@tldraw/vec'
3 | import type { Action } from 'state/constants'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const setViewport: Action = (data, payload: { bounds: TLBounds }) => {
7 | const { camera } = data.pageState
8 | const { width, height } = payload.bounds
9 |
10 | const [minX, minY] = Vec.sub(Vec.div([0, 0], camera.zoom), camera.point)
11 | const [maxX, maxY] = Vec.sub(Vec.div([width, height], camera.zoom), camera.point)
12 |
13 | mutables.rendererBounds = { ...payload.bounds }
14 |
15 | mutables.viewport = {
16 | minX,
17 | minY,
18 | maxX,
19 | maxY,
20 | height: maxX - minX,
21 | width: maxY - minY,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/mutables/updatePointer.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import type { Action } from 'state/constants'
3 | import { getPagePoint } from 'state/helpers'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const updatePointer: Action = (data, payload: TLPointerInfo) => {
7 | mutables.previousPoint = [...mutables.currentPoint]
8 | mutables.currentPoint = getPagePoint(payload.point, data.pageState)
9 | }
10 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/performance/clearPerformanceMode.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const clearPerformanceMode: Action = (data) => {
4 | data.performanceMode = undefined
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/performance/index.ts:
--------------------------------------------------------------------------------
1 | export * from './clearPerformanceMode'
2 | export * from './setTranslatePerformanceMode'
3 | export * from './setTransformPerformanceMode'
4 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/performance/setTransformPerformanceMode.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const setTransformPerformanceMode: Action = (data) => {
4 | data.performanceMode = undefined
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/performance/setTranslatePerformanceMode.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const setTranslatePerformanceMode: Action = (data) => {
4 | data.performanceMode = undefined
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/clearBrush.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const clearBrush: Action = (data) => {
4 | data.pageState.brush = undefined
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/clearHoveredShape.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const clearHoveredShape: Action = (data) => {
4 | data.pageState.hoveredId = undefined
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/clearPointedShape.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const clearPointedShape: Action = () => {
5 | mutables.pointedShapeId = undefined
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/deselectAllShapes.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const deselectAllShapes: Action = (data) => {
4 | data.pageState.selectedIds = []
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/index.ts:
--------------------------------------------------------------------------------
1 | export * from './clearBrush'
2 | export * from './clearHoveredShape'
3 | export * from './clearPointedShape'
4 | export * from './deselectAllShapes'
5 | export * from './selectShape'
6 | export * from './setHoveredShape'
7 | export * from './updateBrush'
8 | export * from './selectAllShapes'
9 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/selectAllShapes.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const selectAllShapes: Action = (data) => {
4 | data.pageState.selectedIds = Object.keys(data.page.shapes)
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/selectShape.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import type { Action } from 'state/constants'
3 | import { mutables } from 'state/mutables'
4 |
5 | export const selectShape: Action = (data, payload: TLPointerInfo) => {
6 | const { selectedIds } = data.pageState
7 |
8 | if (payload.shiftKey) {
9 | if (selectedIds.includes(payload.target) && mutables.pointedShapeId !== payload.target) {
10 | selectedIds.splice(selectedIds.indexOf(payload.target), 1)
11 | } else {
12 | mutables.pointedShapeId = payload.target
13 | selectedIds.push(payload.target)
14 | }
15 | } else {
16 | data.pageState.selectedIds = [payload.target]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/setHoveredShape.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import type { Action } from 'state/constants'
3 |
4 | export const setHoveredShape: Action = (data, payload: TLPointerInfo) => {
5 | data.pageState.hoveredId = payload.target
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/selection/updateBrush.ts:
--------------------------------------------------------------------------------
1 | import { TLPointerInfo, Utils } from '@tldraw/core'
2 | import { getShapeUtils } from 'shapes'
3 | import type { Action } from 'state/constants'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const updateBrush: Action = (data, payload: TLPointerInfo) => {
7 | const { initialPoint, snapshot } = mutables
8 |
9 | const brushBounds = Utils.getBoundsFromPoints([mutables.currentPoint, initialPoint])
10 |
11 | data.pageState.brush = brushBounds
12 |
13 | const initialSelectedIds = snapshot.pageState.selectedIds
14 |
15 | const hits = Object.values(snapshot.page.shapes)
16 | .filter((shape) =>
17 | payload.metaKey || payload.ctrlKey
18 | ? Utils.boundsContain(brushBounds, getShapeUtils(shape).getBounds(shape))
19 | : getShapeUtils(shape).hitTestBounds(shape, brushBounds)
20 | )
21 | .map((shape) => shape.id)
22 |
23 | data.pageState.selectedIds = payload.shiftKey
24 | ? Array.from(new Set([...initialSelectedIds, ...hits]).values())
25 | : hits
26 | }
27 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/shapes/createBoxShape.ts:
--------------------------------------------------------------------------------
1 | import { TLBoundsCorner, TLPointerInfo } from '@tldraw/core'
2 | import { shapeUtils } from 'shapes'
3 | import type { Action } from 'state/constants'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const createBoxShape: Action = (data, payload: TLPointerInfo) => {
7 | const shape = shapeUtils.box.getShape({
8 | parentId: 'page1',
9 | point: mutables.currentPoint,
10 | size: [1, 1],
11 | childIndex: Object.values(data.page.shapes).length,
12 | })
13 |
14 | data.page.shapes[shape.id] = shape
15 | data.pageState.selectedIds = [shape.id]
16 |
17 | mutables.pointedBoundsHandleId = TLBoundsCorner.BottomRight
18 | }
19 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/shapes/createPencilShape.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import { shapeUtils } from 'shapes'
3 | import type { Action } from 'state/constants'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const createPencilShape: Action = (data, payload: TLPointerInfo) => {
7 | const shape = shapeUtils.pencil.getShape({
8 | parentId: 'page1',
9 | point: mutables.currentPoint,
10 | points: [[0, 0]],
11 | childIndex: Object.values(data.page.shapes).length,
12 | })
13 |
14 | data.page.shapes[shape.id] = shape
15 | data.pageState.selectedIds = [shape.id]
16 |
17 | mutables.rawPoints = [[0, 0]]
18 | mutables.pointedShapeId = shape.id
19 | }
20 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/shapes/createShapes.ts:
--------------------------------------------------------------------------------
1 | import { nanoid } from 'nanoid'
2 | import { Shape, getShapeUtils } from 'shapes'
3 | import type { Action } from 'state/constants'
4 |
5 | export const createShapes: Action = (
6 | data,
7 | payload: { shapes: (Partial & Pick)[] }
8 | ) => {
9 | try {
10 | payload.shapes.forEach((partial, i) => {
11 | const shape = getShapeUtils(partial.type).getShape({
12 | id: nanoid(),
13 | childIndex: Object.values(data.page.shapes).length,
14 | ...partial,
15 | parentId: 'page1',
16 | })
17 |
18 | data.page.shapes[shape.id] = shape
19 | })
20 | } catch (e: any) {
21 | e.message = 'Could not create shapes: ' + e.message
22 | console.error(e)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/shapes/deleteSelectedShapes.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const deleteSelectedShapes: Action = (data) => {
4 | const { page, pageState } = data
5 | if (pageState.hoveredId && pageState.selectedIds.includes(pageState.hoveredId)) {
6 | pageState.hoveredId = undefined
7 | }
8 | pageState.selectedIds.forEach((id) => delete page.shapes[id])
9 | pageState.selectedIds = []
10 | }
11 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/shapes/deleteShapes.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const deleteShapes: Action = (data, payload: { ids: string[] }) => {
4 | try {
5 | data.pageState.selectedIds = data.pageState.selectedIds.filter(
6 | (id) => !payload.ids.includes(id)
7 | )
8 |
9 | payload.ids.forEach((id) => {
10 | delete data.page.shapes[id]
11 | })
12 | } catch (e: any) {
13 | e.message = 'Could not delete shapes: ' + e.message
14 | console.error(e)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/shapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createPencilShape'
2 | export * from './createArrowShape'
3 | export * from './createBoxShape'
4 | export * from './deleteSelectedShapes'
5 | export * from './createShapes'
6 | export * from './updateShapes'
7 | export * from './deleteShapes'
8 | export * from './extendPencilShape'
9 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/shapes/updateShapes.ts:
--------------------------------------------------------------------------------
1 | import type { Shape } from 'shapes'
2 | import type { Action } from 'state/constants'
3 |
4 | export const updateShapes: Action = (
5 | data,
6 | payload: { shapes: (Partial & Pick)[] }
7 | ) => {
8 | try {
9 | payload.shapes.forEach((partial) => {
10 | Object.assign(data.page.shapes[partial.id], partial)
11 | })
12 | } catch (e: any) {
13 | e.message = 'Could not update shapes: ' + e.message
14 | console.error(e)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/snapping/clearSnapLines.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const clearSnaplines: Action = (data) => {
4 | data.overlays.snapLines = []
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/snaps/clearSnapInfo.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const clearSnapInfo: Action = () => {
5 | mutables.snapInfo = undefined
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/snaps/clearSnapLines.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 |
3 | export const clearSnapLines: Action = (data) => {
4 | data.overlays.snapLines = []
5 | }
6 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/snaps/index.ts:
--------------------------------------------------------------------------------
1 | export * from './clearSnapLines'
2 | export * from './setSnapInfo'
3 | export * from './clearSnapInfo'
4 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/snaps/setSnapInfo.ts:
--------------------------------------------------------------------------------
1 | import { TLBoundsWithCenter, TLPointerInfo, Utils } from '@tldraw/core'
2 | import { getShapeUtils } from 'shapes'
3 | import type { Action } from 'state/constants'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const setSnapInfo: Action = (data, payload: TLPointerInfo) => {
7 | const all: TLBoundsWithCenter[] = []
8 | const others: TLBoundsWithCenter[] = []
9 |
10 | Object.values(data.page.shapes).forEach((shape) => {
11 | const bounds = Utils.getBoundsWithCenter(getShapeUtils(shape).getRotatedBounds(shape))
12 | all.push(bounds)
13 | if (!(data.pageState.selectedIds.includes(shape.id) || shape.type === 'arrow')) {
14 | others.push(bounds)
15 | }
16 | })
17 |
18 | const initialBounds = Utils.getBoundsWithCenter(
19 | Utils.getCommonBounds(
20 | data.pageState.selectedIds
21 | .map((id) => data.page.shapes[id])
22 | .map((shape) => getShapeUtils(shape).getBounds(shape))
23 | )
24 | )
25 |
26 | mutables.snapInfo = {
27 | initialBounds,
28 | all,
29 | others,
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/transform/clearPointedBoundsHandle.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const clearPointedBoundsHandle: Action = (data, payload) => {
5 | mutables.pointedBoundsHandleId = undefined
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/transform/index.ts:
--------------------------------------------------------------------------------
1 | export * from './transformSelectedShapes'
2 | export * from './setPointedBoundsHandle'
3 | export * from './setInitialCommonBounds'
4 | export * from './clearPointedBoundsHandle'
5 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/transform/setInitialCommonBounds.ts:
--------------------------------------------------------------------------------
1 | import { Utils } from '@tldraw/core'
2 | import { getShapeUtils } from 'shapes'
3 | import type { Action } from 'state/constants'
4 | import { mutables } from 'state/mutables'
5 |
6 | export const setInitialCommonBounds: Action = (data) => {
7 | const { snapshot } = mutables
8 | const { selectedIds } = data.pageState
9 |
10 | const initialCommonBounds = Utils.getCommonBounds(
11 | selectedIds
12 | .map((id) => snapshot.page.shapes[id])
13 | .map((shape) => getShapeUtils(shape).getBounds(shape))
14 | )
15 |
16 | mutables.initialCommonBounds = initialCommonBounds
17 | }
18 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/transform/setPointedBoundsHandle.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const setPointedBoundsHandle: Action = (data, payload) => {
5 | mutables.pointedBoundsHandleId = payload.target
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/transform/transformSelectedShapes.ts:
--------------------------------------------------------------------------------
1 | import type { TLPointerInfo } from '@tldraw/core'
2 | import type { Action } from 'state/constants'
3 | import { mutables } from 'state/mutables'
4 | import { resizeSelectedShapes } from './resizeSelectedShapes'
5 | import { rotateSelectedShapes } from './rotateSelectedShapes'
6 |
7 | export const transformSelectedShapes: Action = (data, payload: TLPointerInfo) => {
8 | const { pointedBoundsHandleId } = mutables
9 |
10 | if (pointedBoundsHandleId === 'rotate') {
11 | rotateSelectedShapes(data, payload)
12 | } else {
13 | resizeSelectedShapes(data, payload)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/translate/clearIsCloning.ts:
--------------------------------------------------------------------------------
1 | import type { Action } from 'state/constants'
2 | import { mutables } from 'state/mutables'
3 |
4 | export const clearIsCloning: Action = () => {
5 | mutables.isCloning = false
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/state/actions/translate/index.ts:
--------------------------------------------------------------------------------
1 | export * from './clearIsCloning'
2 | export * from './translateSelectedShapes'
3 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/styles.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700');
2 |
3 | html,
4 | * {
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
10 | Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
11 | overscroll-behavior: none;
12 | margin: 0px;
13 | padding: 0px;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/types.ts:
--------------------------------------------------------------------------------
1 | export {}
2 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": ".",
19 | "paths": {
20 | "*": ["src/*"]
21 | }
22 | },
23 | "include": ["src"],
24 | "references": [
25 | { "path": "./tsconfig.node.json" },
26 | { "path": "../../packages/tldraw" },
27 | { "path": "../../packages/core" }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/examples/core-example-advanced/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import { defineConfig } from 'vite'
3 | import tsconfigPaths from 'vite-tsconfig-paths'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react(), tsconfigPaths()],
8 | server: {
9 | port: 5421,
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/examples/core-example/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .idea
17 | .DS_Store
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/examples/core-example/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | # @tldraw/core-example
6 |
7 | An simple example project for `@tldraw/core`.
8 |
9 | To start this project:
10 |
11 | 1. Run `yarn` from the repository's root directory
12 | 2. Run `yarn build` from the repository's root directory
13 | 3. Run `yarn dev` from the `examples/core-example` directory
14 | 4. Open http://localhost:5422/ in your browser
15 |
--------------------------------------------------------------------------------
/examples/core-example/card-repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/examples/core-example/card-repo.png
--------------------------------------------------------------------------------
/examples/core-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | tldraw - core example
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/core-example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/examples/core-example/public/favicon.ico
--------------------------------------------------------------------------------
/examples/core-example/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './styles.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/examples/core-example/src/shapes/index.ts:
--------------------------------------------------------------------------------
1 | import type { RectShape } from './rect'
2 |
3 | export * from './rect'
4 |
5 | export type Shape = RectShape
6 |
--------------------------------------------------------------------------------
/examples/core-example/src/shapes/rect/RectComponent.tsx:
--------------------------------------------------------------------------------
1 | import { SVGContainer, TLShapeUtil } from '@tldraw/core'
2 | import * as React from 'react'
3 | import type { RectShape } from './RectShape'
4 |
5 | export const RectComponent = TLShapeUtil.Component(
6 | ({ shape, events, meta }, ref) => {
7 | const color = meta.isDarkMode ? 'white' : 'black'
8 |
9 | return (
10 |
11 |
20 |
21 | )
22 | }
23 | )
24 |
--------------------------------------------------------------------------------
/examples/core-example/src/shapes/rect/RectIndicator.tsx:
--------------------------------------------------------------------------------
1 | import { TLShapeUtil } from '@tldraw/core'
2 | import * as React from 'react'
3 | import type { RectShape } from './RectShape'
4 |
5 | export const RectIndicator = TLShapeUtil.Indicator(({ shape }) => {
6 | return (
7 |
15 | )
16 | })
17 |
--------------------------------------------------------------------------------
/examples/core-example/src/shapes/rect/RectShape.ts:
--------------------------------------------------------------------------------
1 | import type { TLShape } from '@tldraw/core'
2 |
3 | export interface RectShape extends TLShape {
4 | type: 'rect'
5 | size: number[]
6 | }
7 |
--------------------------------------------------------------------------------
/examples/core-example/src/shapes/rect/RectUtil.ts:
--------------------------------------------------------------------------------
1 | import { TLBounds, TLShapeUtil } from '@tldraw/core'
2 | import { RectComponent } from './RectComponent'
3 | import { RectIndicator } from './RectIndicator'
4 | import type { RectShape } from './RectShape'
5 |
6 | type T = RectShape
7 | type E = SVGSVGElement
8 |
9 | export class RectUtil extends TLShapeUtil {
10 | Component = RectComponent
11 |
12 | Indicator = RectIndicator
13 |
14 | getBounds = (shape: T) => {
15 | const [x, y] = shape.point
16 | const [width, height] = shape.size
17 |
18 | const bounds: TLBounds = {
19 | minX: x,
20 | maxX: x + width,
21 | minY: y,
22 | maxY: y + height,
23 | width,
24 | height,
25 | } as TLBounds
26 |
27 | return bounds
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/core-example/src/shapes/rect/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RectShape'
2 | export * from './RectUtil'
3 |
--------------------------------------------------------------------------------
/examples/core-example/src/styles.css:
--------------------------------------------------------------------------------
1 | html,
2 | * {
3 | box-sizing: border-box;
4 | }
5 |
6 | body {
7 | overscroll-behavior: none;
8 | margin: 0px;
9 | padding: 0px;
10 | }
11 |
12 | .tldraw {
13 | position: fixed;
14 | top: 0px;
15 | left: 0px;
16 | right: 0px;
17 | bottom: 0px;
18 | width: 100%;
19 | height: 100%;
20 | }
21 |
--------------------------------------------------------------------------------
/examples/core-example/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/core-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "experimentalDecorators": true,
18 | "jsx": "react-jsx"
19 | },
20 | "include": ["src"],
21 | "references": [
22 | { "path": "./tsconfig.node.json" },
23 | { "path": "../../packages/tldraw" },
24 | { "path": "../../packages/core" }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/examples/core-example/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/examples/core-example/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import { defineConfig } from 'vite'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | server: {
8 | port: 5422,
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/examples/tldraw-example/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .idea
17 | .DS_Store
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/examples/tldraw-example/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | # @tldraw/tldraw-example
6 |
7 | An simple example project for `@tldraw/tldraw`.
8 |
9 | To start this project:
10 |
11 | 1. Run `yarn` from the repository's root directory
12 | 2. Run `yarn start` from the repository's root directory
13 | 3. Open http://localhost:5420/ in your browser
14 |
15 | As an alternative to running `yarn start` you can also:
16 |
17 | 1. Run `yarn build` from the repository's root directory
18 | 2. Run `yarn dev` from the `examples/tldraw-example` directory
19 |
--------------------------------------------------------------------------------
/examples/tldraw-example/card-repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/examples/tldraw-example/card-repo.png
--------------------------------------------------------------------------------
/examples/tldraw-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | tldraw - example
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/tldraw-example/public/card-repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/examples/tldraw-example/public/card-repo.png
--------------------------------------------------------------------------------
/examples/tldraw-example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/examples/tldraw-example/public/favicon.ico
--------------------------------------------------------------------------------
/examples/tldraw-example/src/api.tsx:
--------------------------------------------------------------------------------
1 | import { ColorStyle, TDShapeType, Tldraw, TldrawApp } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | declare const window: Window & { app: TldrawApp }
5 |
6 | export default function Api() {
7 | const rTldrawApp = React.useRef()
8 |
9 | const handleMount = React.useCallback((app: TldrawApp) => {
10 | rTldrawApp.current = app
11 |
12 | window.app = app
13 |
14 | app
15 | .createShapes({
16 | id: 'rect1',
17 | type: TDShapeType.Rectangle,
18 | point: [100, 100],
19 | size: [200, 200],
20 | })
21 | .selectAll()
22 | .nudge([1, 1], true)
23 | .duplicate()
24 | .select('rect1')
25 | .style({ color: ColorStyle.Blue })
26 | .selectNone()
27 | }, [])
28 |
29 | return (
30 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/basic.tsx:
--------------------------------------------------------------------------------
1 | import { Tldraw } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function Basic() {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/changing-id.tsx:
--------------------------------------------------------------------------------
1 | import { Tldraw } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function ChangingId() {
5 | const [id, setId] = React.useState('example')
6 |
7 | React.useEffect(() => {
8 | const timeout = setTimeout(() => setId('example2'), 2000)
9 |
10 | return () => clearTimeout(timeout)
11 | }, [])
12 |
13 | return
14 | }
15 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/dark-mode.tsx:
--------------------------------------------------------------------------------
1 | import { Tldraw } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function DarkMode() {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/develop.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { Tldraw, TldrawApp, useFileSystem } from '@tldraw/tldraw'
3 | import * as React from 'react'
4 |
5 | declare const window: Window & { app: TldrawApp }
6 |
7 | export default function Develop() {
8 | const rTldrawApp = React.useRef()
9 |
10 | const fileSystemEvents = useFileSystem()
11 |
12 | const handleMount = React.useCallback((app: TldrawApp) => {
13 | window.app = app
14 | rTldrawApp.current = app
15 | // app.reset()
16 | // app.createShapes({
17 | // id: 'box1',
18 | // type: TDShapeType.Rectangle,
19 | // point: [200, 200],
20 | // size: [200, 200],
21 | // })
22 | }, [])
23 |
24 | const handlePersist = React.useCallback(() => {
25 | // noop
26 | }, [])
27 |
28 | return (
29 |
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/embedded.tsx:
--------------------------------------------------------------------------------
1 | import { Tldraw } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function Embedded() {
5 | return (
6 |
7 |
16 |
17 |
18 |
19 |
27 |
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/file-system.tsx:
--------------------------------------------------------------------------------
1 | import { Tldraw, useFileSystem } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function FileSystem() {
5 | const fileSystemEvents = useFileSystem()
6 |
7 | // Use the Menu > File to create, open, and save .tldr files.
8 |
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/iframe.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export default function IFrame() {
4 | return (
5 |
6 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/loading-files.tsx:
--------------------------------------------------------------------------------
1 | import { TDFile, Tldraw } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function LoadingFiles() {
5 | const [file, setFile] = React.useState()
6 |
7 | React.useEffect(() => {
8 | async function loadFile(): Promise {
9 | const file = await fetch('Example.tldr').then((response) => response.json())
10 | setFile(file)
11 | }
12 |
13 | loadFile()
14 | }, [])
15 |
16 | return
17 | }
18 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/main.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */
2 | import React from 'react'
3 | import { createRoot } from 'react-dom/client'
4 | import { HashRouter } from 'react-router-dom'
5 | import App from './App'
6 |
7 | const container = document.getElementById('root')!
8 | const root = createRoot(container)
9 | root.render(
10 |
11 |
12 |
13 |
14 |
15 | )
16 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/multiplayer/index.ts:
--------------------------------------------------------------------------------
1 | export * from './multiplayer'
2 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/no-size-embedded.tsx:
--------------------------------------------------------------------------------
1 | import { Tldraw } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function NoSizeEmbedded() {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/persisted.tsx:
--------------------------------------------------------------------------------
1 | import { Tldraw } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function Persisted() {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/readonly.tsx:
--------------------------------------------------------------------------------
1 | import { TDFile, Tldraw } from '@tldraw/tldraw'
2 | import * as React from 'react'
3 |
4 | export default function ReadOnly() {
5 | const [file, setFile] = React.useState()
6 |
7 | React.useEffect(() => {
8 | async function loadFile(): Promise {
9 | const file = await fetch('Example.tldr').then((response) => response.json())
10 | setFile(file)
11 | }
12 |
13 | loadFile()
14 | }, [])
15 |
16 | return (
17 |
18 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/scroll.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { Tldraw, TldrawApp } from '@tldraw/tldraw'
3 | import * as React from 'react'
4 |
5 | declare const window: Window & { app: TldrawApp }
6 |
7 | export default function Scroll() {
8 | const rTldrawApp = React.useRef()
9 |
10 | const handleMount = React.useCallback((app: TldrawApp) => {
11 | window.app = app
12 | rTldrawApp.current = app
13 | }, [])
14 |
15 | return (
16 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/styles.css:
--------------------------------------------------------------------------------
1 | html,
2 | * {
3 | box-sizing: border-box;
4 | }
5 |
6 | body {
7 | overscroll-behavior: none;
8 | margin: 0px;
9 | padding: 0px;
10 | font-size: 1em;
11 | font-family: Arial, Helvetica, sans-serif;
12 | }
13 |
14 | .tldraw {
15 | position: fixed;
16 | top: 0px;
17 | left: 0px;
18 | right: 0px;
19 | bottom: 0px;
20 | width: 100%;
21 | height: 100%;
22 | }
23 |
24 | .hero {
25 | width: 100%;
26 | max-width: 720px;
27 | }
28 |
29 | .links {
30 | display: grid;
31 | width: fit-content;
32 | list-style: none;
33 | padding: 1em;
34 | margin: 0px;
35 | }
36 |
37 | .links a {
38 | padding: 0.5em 0.7em;
39 | color: black;
40 | text-decoration: none;
41 | font-size: 1.2em;
42 | display: block;
43 | border-radius: 8px;
44 | }
45 |
46 | .links a:hover {
47 | background-color: rgba(144, 144, 144, 0.1);
48 | }
49 |
--------------------------------------------------------------------------------
/examples/tldraw-example/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/tldraw-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": ".",
19 | "paths": {
20 | "~*": ["./src/*"]
21 | }
22 | },
23 | "include": ["src"],
24 | "references": [
25 | { "path": "../../packages/core" },
26 | { "path": "../../packages/tldraw" },
27 | { "path": "../../packages/vec" }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/examples/tldraw-example/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/examples/tldraw-example/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import { defineConfig } from 'vite'
3 | import tsconfigPaths from 'vite-tsconfig-paths'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react(), tsconfigPaths()],
8 | server: {
9 | port: 5420,
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/guides/development.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | From the root folder:
4 |
5 | - Run `yarn` to install dependencies.
6 |
7 | - Run `yarn start` to start the development server for the package and for the example.
8 |
9 | - Open `localhost:5420` to view the example project.
10 |
11 | **Note:** The multiplayer examples and endpoints currently require an API key from [Liveblocks](https://liveblocks.io/), however the storage services that are used in tldraw are currently in alpha and (as of November 2021) not accessible to the general public. You won't be able to authenticate and run these parts of the project.
12 |
13 | Other scripts:
14 |
15 | - Run `yarn test` to execute unit tests via [Jest](https://jestjs.io).
16 |
--------------------------------------------------------------------------------
/guides/publishing.md:
--------------------------------------------------------------------------------
1 | # Publishing
2 |
3 | At the moment, publishing is done by hand by the project's maintainers.
4 |
5 | This guide will be updated when more information is available.
6 |
--------------------------------------------------------------------------------
/packages/core/src/TLShapeUtil/index.ts:
--------------------------------------------------------------------------------
1 | import type { TLShape } from '~types'
2 | import type { TLShapeUtil } from './TLShapeUtil'
3 |
4 | export type TLShapeUtilsMap = {
5 | [K in T['type']]: TLShapeUtil>
6 | }
7 |
8 | export * from './TLShapeUtil'
9 |
--------------------------------------------------------------------------------
/packages/core/src/components/Binding/Binding.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | interface BindingProps {
4 | point: number[]
5 | type: string
6 | }
7 |
8 | export function Binding({ point: [x, y], type }: BindingProps) {
9 | return (
10 |
11 | {type === 'center' && (
12 |
13 | )}
14 | {type !== 'pin' && (
15 |
16 | )}
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/components/Binding/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Binding'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Bounds/BoundsBg.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Container } from '~components/Container'
3 | import { SVGContainer } from '~components/SVGContainer'
4 | import { useBoundsEvents } from '~hooks'
5 | import type { TLBounds } from '~types'
6 |
7 | export interface BoundsBgProps {
8 | bounds: TLBounds
9 | rotation: number
10 | isHidden: boolean
11 | }
12 |
13 | function _BoundsBg({ bounds, rotation, isHidden }: BoundsBgProps) {
14 | const events = useBoundsEvents()
15 |
16 | return (
17 |
18 |
19 |
27 |
28 |
29 | )
30 | }
31 |
32 | export const BoundsBg = React.memo(_BoundsBg)
33 |
--------------------------------------------------------------------------------
/packages/core/src/components/Bounds/CenterHandle.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { TLBounds } from '~types'
3 |
4 | export interface CenterHandleProps {
5 | bounds: TLBounds
6 | isLocked: boolean
7 | isHidden: boolean
8 | }
9 |
10 | function _CenterHandle({ bounds, isLocked, isHidden }: CenterHandleProps) {
11 | return (
12 |
22 | )
23 | }
24 |
25 | export const CenterHandle = React.memo(_CenterHandle)
26 |
--------------------------------------------------------------------------------
/packages/core/src/components/Bounds/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Bounds'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Brush/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Brush'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Canvas/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Canvas'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Container/Container.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { HTMLProps } from 'react'
3 | import { usePosition } from '~hooks'
4 | import type { TLBounds } from '~types'
5 |
6 | export interface ContainerProps extends HTMLProps {
7 | id?: string
8 | bounds: TLBounds
9 | rotation?: number
10 | isGhost?: boolean
11 | isSelected?: boolean
12 | children: React.ReactNode
13 | }
14 |
15 | function _Container({
16 | id,
17 | bounds,
18 | rotation = 0,
19 | isGhost = false,
20 | isSelected = false,
21 | children,
22 | ...props
23 | }: ContainerProps) {
24 | const rPositioned = usePosition(bounds, rotation)
25 |
26 | return (
27 |
37 | {children}
38 |
39 | )
40 | }
41 |
42 | export const Container = React.memo(_Container)
43 |
--------------------------------------------------------------------------------
/packages/core/src/components/Container/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Container'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Cursor/Cursor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { TLUser } from '~types'
3 |
4 | export type CursorComponent = (props: Pick, 'id' | 'color' | 'metadata'>) => any
5 |
6 | export const Cursor: CursorComponent = React.memo(({ color }) => {
7 | return (
8 |
22 | )
23 | })
24 |
--------------------------------------------------------------------------------
/packages/core/src/components/Cursor/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Cursor'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/EraseLine/EraseLine.tsx:
--------------------------------------------------------------------------------
1 | import getStroke from 'perfect-freehand'
2 | import * as React from 'react'
3 | import Utils from '~utils'
4 |
5 | export interface UiEraseLineProps {
6 | points: number[][]
7 | zoom: number
8 | }
9 |
10 | export type UiEraseLineComponent = (props: UiEraseLineProps) => any | null
11 |
12 | function _EraseLine({ points, zoom }: UiEraseLineProps) {
13 | if (points.length === 0) return null
14 |
15 | const d = Utils.getSvgPathFromStroke(
16 | getStroke(points, { size: 16 / zoom, start: { taper: true } })
17 | )
18 |
19 | return
20 | }
21 |
22 | export const EraseLine = React.memo(_EraseLine)
23 |
--------------------------------------------------------------------------------
/packages/core/src/components/EraseLine/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EraseLine'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Grid/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Grid'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/HTMLContainer/HTMLContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | interface HTMLContainerProps extends React.HTMLProps {
4 | children: React.ReactNode
5 | }
6 |
7 | export const HTMLContainer = React.memo(
8 | React.forwardRef(function HTMLContainer(
9 | { children, className = '', ...rest },
10 | ref
11 | ) {
12 | return (
13 |
16 | )
17 | })
18 | )
19 |
--------------------------------------------------------------------------------
/packages/core/src/components/HTMLContainer/index.ts:
--------------------------------------------------------------------------------
1 | export * from './HTMLContainer'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Handles/Handle.test.tsx:
--------------------------------------------------------------------------------
1 | import { screen } from '@testing-library/react'
2 | import * as React from 'react'
3 | import { renderWithContext } from '~test'
4 | import { Handle } from './Handle'
5 |
6 | describe('handle', () => {
7 | test('mounts component without crashing', () => {
8 | expect(() => renderWithContext()).not.toThrowError()
9 | })
10 | test('validate attributes for handle component', () => {
11 | renderWithContext()
12 | const handle = screen.getByLabelText('handle')
13 | expect(handle.querySelectorAll('circle').length).toBe(2)
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/packages/core/src/components/Handles/Handle.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Container } from '~components/Container'
3 | import { SVGContainer } from '~components/SVGContainer'
4 | import { useHandleEvents } from '~hooks'
5 | import Utils from '~utils'
6 |
7 | interface HandleProps {
8 | id: string
9 | point: number[]
10 | }
11 |
12 | function _Handle({ id, point }: HandleProps) {
13 | const events = useHandleEvents(id)
14 |
15 | return (
16 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export const Handle = React.memo(_Handle)
40 |
--------------------------------------------------------------------------------
/packages/core/src/components/Handles/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Handles'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Overlay/Overlay.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export type OverlayProps = {
4 | camera: { point: number[]; zoom: number }
5 | children: React.ReactNode
6 | }
7 |
8 | function _Overlay({ camera: { zoom, point }, children }: OverlayProps) {
9 | const l = 2.5 / zoom
10 | return (
11 |
22 | )
23 | }
24 |
25 | export const Overlay = React.memo(_Overlay)
26 |
--------------------------------------------------------------------------------
/packages/core/src/components/Overlay/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Overlay'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Page/Page.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { mockDocument, renderWithContext } from '~test'
3 | import { Page } from './Page'
4 |
5 | describe('page', () => {
6 | test('mounts component without crashing', () => {
7 | expect(() =>
8 | renderWithContext(
9 |
21 | )
22 | ).not.toThrowError()
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/packages/core/src/components/Page/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Page'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Renderer/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Renderer'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/SVGContainer/SVGContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export interface SvgContainerProps extends React.SVGProps {
4 | children: React.ReactNode
5 | className?: string
6 | }
7 |
8 | export const SVGContainer = React.memo(
9 | React.forwardRef(function SVGContainer(
10 | { id, className = '', children, ...rest },
11 | ref
12 | ) {
13 | return (
14 |
19 | )
20 | })
21 | )
22 |
--------------------------------------------------------------------------------
/packages/core/src/components/SVGContainer/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SVGContainer'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Shape/Shape.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { TLShapeUtil } from '~TLShapeUtil'
3 | import { BoxUtil, boxShape } from '~TLShapeUtil/TLShapeUtil.spec'
4 | import { renderWithContext } from '~test'
5 | import type { TLShape } from '~types'
6 | import { Shape } from './Shape'
7 |
8 | describe('shape', () => {
9 | test('mounts component without crashing', () => {
10 | expect(() =>
11 | renderWithContext(
12 | }
15 | isEditing={false}
16 | isBinding={false}
17 | isHovered={false}
18 | isSelected={false}
19 | isGhost={false}
20 | isChildOfSelected={false}
21 | />
22 | )
23 | ).not.toThrowError()
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/packages/core/src/components/Shape/ShapeNode.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { TLShapeUtilsMap } from '~TLShapeUtil'
3 | import type { IShapeTreeNode, TLShape } from '~types'
4 | import { Shape } from './Shape'
5 |
6 | export interface ShapeNodeProps extends IShapeTreeNode {
7 | utils: TLShapeUtilsMap
8 | }
9 |
10 | function _ShapeNode({
11 | shape,
12 | utils,
13 | meta,
14 | children,
15 | ...rest
16 | }: ShapeNodeProps) {
17 | return (
18 | <>
19 |
20 | {children &&
21 | children.map((childNode) => (
22 |
23 | ))}
24 | >
25 | )
26 | }
27 |
28 | export const ShapeNode = React.memo(_ShapeNode)
29 |
--------------------------------------------------------------------------------
/packages/core/src/components/Shape/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ShapeNode'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/ShapeIndicator/ShapeIndicator.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { boxShape } from '~TLShapeUtil/TLShapeUtil.spec'
3 | import { renderWithSvg } from '~test'
4 | import { ShapeIndicator } from './ShapeIndicator'
5 |
6 | describe('shape indicator', () => {
7 | test('mounts component without crashing', () => {
8 | expect(() =>
9 | renderWithSvg(
10 |
17 | )
18 | ).not.toThrowError()
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/packages/core/src/components/ShapeIndicator/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ShapeIndicator'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/SnapLines/SnapLines.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { TLSnapLine } from '~types'
3 | import Utils from '~utils'
4 |
5 | function _SnapLines({ snapLines }: { snapLines: TLSnapLine[] }) {
6 | return (
7 | <>
8 | {snapLines.map((snapLine, i) => (
9 |
10 | ))}
11 | >
12 | )
13 | }
14 |
15 | function _SnapLine({ snapLine }: { snapLine: TLSnapLine }) {
16 | const bounds = Utils.getBoundsFromPoints(snapLine)
17 |
18 | return (
19 | <>
20 |
27 | {snapLine.map(([x, y], i) => (
28 |
29 | ))}
30 | >
31 | )
32 | }
33 |
34 | export const SnapLine = React.memo(_SnapLine)
35 | export const SnapLines = React.memo(_SnapLines)
36 |
--------------------------------------------------------------------------------
/packages/core/src/components/SnapLines/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SnapLines'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/User/User.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { CursorComponent } from '~components/Cursor'
3 | import type { TLUser } from '~types'
4 |
5 | interface UserProps {
6 | user: TLUser
7 | Cursor: CursorComponent
8 | }
9 |
10 | export function User({ user, Cursor }: UserProps) {
11 | const rCursor = React.useRef(null)
12 |
13 | React.useLayoutEffect(() => {
14 | if (rCursor.current) {
15 | rCursor.current.style.transform = `translate(${user.point[0]}px, ${user.point[1]}px)`
16 | }
17 | }, [user.point])
18 |
19 | return (
20 |
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/packages/core/src/components/User/index.ts:
--------------------------------------------------------------------------------
1 | export * from './User'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/Users/Users.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { CursorComponent } from '~components/Cursor'
3 | import { User } from '~components/User/User'
4 | import type { TLUsers } from '~types'
5 |
6 | export interface UserProps {
7 | userId?: string
8 | users: TLUsers
9 | Cursor: CursorComponent
10 | }
11 |
12 | export function Users({ userId, users, Cursor }: UserProps) {
13 | return (
14 | <>
15 | {Object.values(users)
16 | .filter((user) => user && user.id !== userId)
17 | .map((user) => (
18 |
19 | ))}
20 | >
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/components/Users/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Users'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/UsersIndicators/index.ts:
--------------------------------------------------------------------------------
1 | export * from './UsersIndicators'
2 |
--------------------------------------------------------------------------------
/packages/core/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Renderer'
2 | export * from './SVGContainer'
3 | export * from './HTMLContainer'
4 | export * from './Cursor'
5 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useTLContext'
2 | export * from './useZoomEvents'
3 | export * from './useSafariFocusOutFix'
4 | export * from './useCanvasEvents'
5 | export * from './useShapeEvents'
6 | export * from './useShapeTree'
7 | export * from './useStyle'
8 | export * from './useCanvasEvents'
9 | export * from './useBoundsHandleEvents'
10 | export * from './useCameraCss'
11 | export * from './useSelection'
12 | export * from './useHandleEvents'
13 | export * from './useHandles'
14 | export * from './usePreventNavigationCss'
15 | export * from './useBoundsEvents'
16 | export * from './usePosition'
17 | export * from './useKeyEvents'
18 | export * from './usePerformanceCss'
19 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useHandles.ts:
--------------------------------------------------------------------------------
1 | import type { TLBinding, TLPage, TLPageState, TLShape } from '~types'
2 |
3 | export function useHandles(page: TLPage, pageState: TLPageState) {
4 | const { selectedIds } = pageState
5 |
6 | let shapeWithHandles: TLShape | undefined = undefined
7 |
8 | if (selectedIds.length === 1) {
9 | const id = selectedIds[0]
10 |
11 | const shape = page.shapes[id]
12 |
13 | if (shape.handles !== undefined) {
14 | shapeWithHandles = shape
15 | }
16 | }
17 |
18 | return { shapeWithHandles }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useKeyEvents.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { useTLContext } from '~hooks'
3 |
4 | export function useKeyEvents() {
5 | const { inputs, callbacks } = useTLContext()
6 |
7 | React.useEffect(() => {
8 | const handleKeyDown = (e: KeyboardEvent) => {
9 | callbacks.onKeyDown?.(e.key, inputs.keydown(e), e)
10 | }
11 | const handleKeyUp = (e: KeyboardEvent) => {
12 | inputs.keyup(e)
13 | callbacks.onKeyUp?.(e.key, inputs.keyup(e), e)
14 | }
15 | window.addEventListener('keydown', handleKeyDown)
16 | window.addEventListener('keyup', handleKeyUp)
17 | return () => {
18 | window.removeEventListener('keydown', handleKeyDown)
19 | window.removeEventListener('keyup', handleKeyUp)
20 | }
21 | }, [inputs, callbacks])
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/usePosition.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { TLBounds } from '~types'
3 |
4 | export function usePosition(bounds: TLBounds, rotation = 0) {
5 | const rBounds = React.useRef(null)
6 |
7 | // Update the transform
8 | React.useLayoutEffect(() => {
9 | const elm = rBounds.current!
10 | const transform = `
11 | translate(
12 | calc(${bounds.minX}px - var(--tl-padding)),
13 | calc(${bounds.minY}px - var(--tl-padding))
14 | )
15 | rotate(${rotation + (bounds.rotation || 0)}rad)`
16 | elm.style.setProperty('transform', transform)
17 | elm.style.setProperty('width', `calc(${Math.floor(bounds.width)}px + (var(--tl-padding) * 2))`)
18 | elm.style.setProperty(
19 | 'height',
20 | `calc(${Math.floor(bounds.height)}px + (var(--tl-padding) * 2))`
21 | )
22 | }, [bounds, rotation])
23 |
24 | return rBounds
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useSafariFocusOutFix.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import Utils from '~utils'
3 | import { useTLContext } from './useTLContext'
4 |
5 | // Send event on iOS when a user presses the "Done" key while editing a text element.
6 |
7 | export function useSafariFocusOutFix(): void {
8 | const { callbacks } = useTLContext()
9 |
10 | useEffect(() => {
11 | function handleFocusOut() {
12 | callbacks.onShapeBlur?.()
13 | }
14 |
15 | if (Utils.isMobileSafari()) {
16 | document.addEventListener('focusout', handleFocusOut)
17 | return () => document.removeEventListener('focusout', handleFocusOut)
18 | }
19 |
20 | return () => null
21 | }, [callbacks])
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/hooks/useTLContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { TLShapeUtilsMap } from '~TLShapeUtil'
3 | import type { Inputs } from '~inputs'
4 | import type { TLBounds, TLCallbacks, TLPageState, TLShape } from '~types'
5 |
6 | export interface TLContextType {
7 | id?: string
8 | callbacks: Partial>
9 | shapeUtils: TLShapeUtilsMap
10 | rPageState: React.MutableRefObject
11 | rSelectionBounds: React.MutableRefObject
12 | inputs: Inputs
13 | bounds: TLBounds
14 | }
15 |
16 | export const TLContext = React.createContext({} as TLContextType)
17 |
18 | export function useTLContext() {
19 | const context = React.useContext(TLContext)
20 |
21 | return context
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './components'
2 | export * from './types'
3 | export * from './utils'
4 | export * from './inputs'
5 | export * from './TLShapeUtil'
6 |
--------------------------------------------------------------------------------
/packages/core/src/test/ContextWrapper.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { BoxShape } from '~TLShapeUtil/TLShapeUtil.spec'
3 | import { TLContext, TLContextType, useTLTheme } from '~hooks'
4 | import { Inputs } from '~inputs'
5 | import type { TLBounds, TLPageState } from '~types'
6 | import { mockDocument } from './mockDocument'
7 | import { mockUtils } from './mockUtils'
8 |
9 | export const ContextWrapper = ({ children }: { children: any }) => {
10 | useTLTheme()
11 | const rSelectionBounds = React.useRef(null)
12 | const rPageState = React.useRef(mockDocument.pageState)
13 |
14 | const [context] = React.useState>(() => ({
15 | callbacks: {},
16 | shapeUtils: mockUtils,
17 | rSelectionBounds,
18 | rPageState,
19 | inputs: new Inputs(),
20 | bounds: {
21 | minX: 0,
22 | minY: 0,
23 | maxX: Infinity,
24 | maxY: Infinity,
25 | width: Infinity,
26 | height: Infinity,
27 | },
28 | }))
29 |
30 | return {children}
31 | }
32 |
--------------------------------------------------------------------------------
/packages/core/src/test/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mockDocument'
2 | export * from './mockUtils'
3 | export * from './renderWithContext'
4 | export * from './renderWithSvg'
5 |
--------------------------------------------------------------------------------
/packages/core/src/test/mockDocument.ts:
--------------------------------------------------------------------------------
1 | import type { BoxShape } from '~TLShapeUtil/TLShapeUtil.spec'
2 | import type { TLBinding, TLPage, TLPageState } from '~types'
3 |
4 | export const mockDocument: { page: TLPage; pageState: TLPageState } = {
5 | page: {
6 | id: 'page1',
7 | shapes: {},
8 | bindings: {},
9 | },
10 | pageState: {
11 | id: 'page1',
12 | selectedIds: [],
13 | camera: {
14 | point: [0, 0],
15 | zoom: 1,
16 | },
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/src/test/mockUtils.tsx:
--------------------------------------------------------------------------------
1 | import { BoxUtil } from '~TLShapeUtil/TLShapeUtil.spec'
2 |
3 | export const mockUtils = {
4 | box: new BoxUtil(),
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/test/renderWithContext.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react'
2 | import * as React from 'react'
3 | import { ContextWrapper } from './ContextWrapper'
4 |
5 | export const renderWithContext = (children: React.ReactNode) => {
6 | return render(
7 |
8 | {children}
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/src/test/renderWithSvg.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react'
2 | import * as React from 'react'
3 | import { ContextWrapper } from './ContextWrapper'
4 |
5 | export const renderWithSvg = (children: React.ReactNode) => {
6 | return render(
7 |
8 |
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import { Utils } from './utils'
2 |
3 | export { Utils } from './utils'
4 |
5 | export default Utils
6 |
--------------------------------------------------------------------------------
/packages/core/src/utils/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * String.prototype.replaceAll() polyfill
3 | * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
4 | * @author Chris Ferdinandi
5 | * @license MIT
6 | */
7 | if (!String.prototype.replaceAll) {
8 | // @ts-ignore
9 | String.prototype.replaceAll = function (str: string, newStr: string) {
10 | // If a regex pattern
11 | if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
12 | return this.replace(str, newStr)
13 | }
14 |
15 | // If a string
16 | return this.replace(new RegExp(str, 'g'), newStr)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "skipLibCheck": true,
14 | "composite": false,
15 | "incremental": false,
16 | "declaration": true,
17 | "declarationMap": true,
18 | "sourceMap": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "skipLibCheck": true,
14 | "composite": false,
15 | "incremental": false,
16 | "declaration": true,
17 | "declarationMap": true,
18 | "sourceMap": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "exclude": ["node_modules", "dist"],
4 | "compilerOptions": {
5 | "experimentalDecorators": true,
6 | "useDefineForClassFields": true,
7 | "outDir": "./dist",
8 | "rootDir": "src",
9 | "baseUrl": ".",
10 | "paths": {
11 | "~*": ["./src/*"]
12 | }
13 | },
14 | "references": [{ "path": "../vec" }, { "path": "../intersect" }]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/curve/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.8.2",
3 | "name": "@tldraw/curve",
4 | "description": "Curve utilities for TLDraw and maybe you, too.",
5 | "author": "@steveruizok",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/tldraw/tldraw.git"
9 | },
10 | "license": "MIT",
11 | "keywords": [
12 | "2d",
13 | "vector",
14 | "curve",
15 | "spline",
16 | "typescript",
17 | "javascript"
18 | ],
19 | "files": [
20 | "dist/**/*"
21 | ],
22 | "main": "./dist/index.js",
23 | "module": "./dist/index.mjs",
24 | "types": "./dist/index.d.ts",
25 | "source": "./dist/index.ts",
26 | "scripts": {
27 | "dev": "yarn lfg -d",
28 | "build": "yarn lfg",
29 | "lint": "TIMING=1 eslint src/ --ext .ts,.tsx",
30 | "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
31 | },
32 | "gitHead": "3ab5db27b9e83736fdae934474e80e90c854922c",
33 | "devDependencies": {
34 | "@tldraw/lfg": "latest"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/curve/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "composite": false,
14 | "incremental": false,
15 | "declaration": true,
16 | "declarationMap": true,
17 | "sourceMap": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/curve/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "composite": false,
14 | "incremental": false,
15 | "declaration": true,
16 | "declarationMap": true,
17 | "sourceMap": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/curve/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "exclude": ["node_modules", "dist"],
5 | "compilerOptions": {
6 | "outDir": "./dist",
7 | "rootDir": "src",
8 | "baseUrl": "."
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/intersect/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.9.2",
3 | "name": "@tldraw/intersect",
4 | "description": "2D intersection utilities for TLDraw and maybe you, too.",
5 | "author": "@steveruizok",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/tldraw/tldraw.git"
9 | },
10 | "license": "MIT",
11 | "keywords": [
12 | "2d",
13 | "vector",
14 | "intersection",
15 | "typescript",
16 | "javascript"
17 | ],
18 | "files": [
19 | "dist/**/*"
20 | ],
21 | "main": "./dist/index.js",
22 | "module": "./dist/index.mjs",
23 | "types": "./dist/index.d.ts",
24 | "source": "./src/index.ts",
25 | "scripts": {
26 | "dev": "yarn lfg -d",
27 | "build": "yarn lfg",
28 | "lint": "TIMING=1 eslint src/ --ext .ts,.tsx",
29 | "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
30 | },
31 | "dependencies": {
32 | "@tldraw/vec": "^1.9.2"
33 | },
34 | "devDependencies": {
35 | "@tldraw/vec": "*",
36 | "@tldraw/lfg": "latest"
37 | },
38 | "gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
39 | }
40 |
--------------------------------------------------------------------------------
/packages/intersect/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "composite": false,
14 | "incremental": false,
15 | "declaration": true,
16 | "declarationMap": true,
17 | "sourceMap": true
18 | },
19 | "references": [{ "path": "../vec" }]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/intersect/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "composite": false,
14 | "incremental": false,
15 | "declaration": true,
16 | "declarationMap": true,
17 | "sourceMap": true
18 | },
19 | "references": [{ "path": "../vec" }]
20 | }
21 |
--------------------------------------------------------------------------------
/packages/intersect/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "exclude": ["node_modules", "dist"],
4 | "compilerOptions": {
5 | "outDir": "./dist",
6 | "rootDir": "src",
7 | "baseUrl": "."
8 | },
9 | "references": [{ "path": "../vec" }]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/tldraw/src/Tldraw.spec.tsx:
--------------------------------------------------------------------------------
1 | import { render, waitFor } from '@testing-library/react'
2 | import * as React from 'react'
3 | import { Tldraw } from './Tldraw'
4 |
5 | describe('Tldraw', () => {
6 | test('mounts component and calls onMount', async () => {
7 | const onMount = jest.fn()
8 | render()
9 | await waitFor(onMount)
10 | })
11 |
12 | test('mounts component and calls onMount when id is present', async () => {
13 | const onMount = jest.fn()
14 | render()
15 | await waitFor(onMount)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/ContextMenu/ContextMenu.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { renderWithContext, renderWithIntlProvider } from '~test'
3 | import { ContextMenu } from './ContextMenu'
4 |
5 | describe('context menu', () => {
6 | test('mounts component without crashing', () => {
7 | renderWithContext(
8 | renderWithIntlProvider(
9 |
10 | Hello
11 |
12 | )
13 | )
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/ContextMenu/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ContextMenu'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/ErrorFallback/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ErrorFallback'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/FocusButton/FocusButton.tsx:
--------------------------------------------------------------------------------
1 | import { DotFilledIcon } from '@radix-ui/react-icons'
2 | import * as React from 'react'
3 | import { IconButton } from '~components/Primitives/IconButton/IconButton'
4 | import { styled } from '~styles'
5 |
6 | interface FocusButtonProps {
7 | onSelect: () => void
8 | }
9 |
10 | export function FocusButton({ onSelect }: FocusButtonProps) {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | const StyledButtonContainer = styled('div', {
21 | opacity: 1,
22 | zIndex: 100,
23 | backgroundColor: 'transparent',
24 |
25 | '& svg': {
26 | color: '$text',
27 | },
28 |
29 | '&:hover svg': {
30 | color: '$text',
31 | },
32 | })
33 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/FocusButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './FocusButton'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Loading/index.ts:
--------------------------------------------------------------------------------
1 | export { Loading } from './Loading'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/AlertDialog/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AlertDialog'
2 | export * from './FilenameDialog'
3 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/Divider/Divider.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '~styles'
2 |
3 | export const Divider = styled('hr', {
4 | height: 0,
5 | paddingTop: 1,
6 | width: 'calc(100%+8px)',
7 | backgroundColor: '$hover',
8 | border: 'none',
9 | margin: '$2 -4px',
10 | })
11 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/Divider/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Divider'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/DropdownMenu/DMDivider.tsx:
--------------------------------------------------------------------------------
1 | import { Separator } from '@radix-ui/react-dropdown-menu'
2 | import { styled } from '~styles/stitches.config'
3 |
4 | export const DMDivider = styled(Separator, {
5 | backgroundColor: '$hover',
6 | height: 1,
7 | marginTop: '$2',
8 | marginRight: '-$2',
9 | marginBottom: '$2',
10 | marginLeft: '-$2',
11 | })
12 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/DropdownMenu/DMItem.tsx:
--------------------------------------------------------------------------------
1 | import { Item } from '@radix-ui/react-dropdown-menu'
2 | import * as React from 'react'
3 | import { RowButton, RowButtonProps } from '~components/Primitives/RowButton'
4 |
5 | export function DMItem({
6 | onSelect,
7 | id,
8 | ...rest
9 | }: RowButtonProps & { onSelect?: (event: Event) => void; id?: string }) {
10 | return (
11 | -
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/DropdownMenu/DMTriggerIcon.tsx:
--------------------------------------------------------------------------------
1 | import { Trigger } from '@radix-ui/react-dropdown-menu'
2 | import * as React from 'react'
3 | import { ToolButton, ToolButtonProps } from '~components/Primitives/ToolButton'
4 |
5 | interface DMTriggerIconProps extends ToolButtonProps {
6 | children: React.ReactNode
7 | id?: string
8 | }
9 |
10 | export function DMTriggerIcon({ id, children, ...rest }: DMTriggerIconProps) {
11 | return (
12 |
13 | {children}
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/DropdownMenu/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './DMItem'
2 | export * from './DMCheckboxItem'
3 | export * from './DMContent'
4 | export * from './DMDivider'
5 | export * from './DMRadioItem'
6 | export * from './DMSubMenu'
7 | export * from './DMTriggerIcon'
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/IconButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './IconButton'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/Kbd/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Kbd'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/MenuContent/MenuContent.ts:
--------------------------------------------------------------------------------
1 | import { styled } from '~styles'
2 |
3 | export const MenuContent = styled('div', {
4 | position: 'relative',
5 | overflow: 'hidden',
6 | userSelect: 'none',
7 | WebkitUserSelect: 'none',
8 | display: 'flex',
9 | flexDirection: 'column',
10 | zIndex: 99997,
11 | minWidth: 180,
12 | pointerEvents: 'all',
13 | backgroundColor: '$panel',
14 | border: '1px solid $panelContrast',
15 | boxShadow: '$panel',
16 | padding: '$2 $2',
17 | borderRadius: '$3',
18 | font: '$ui',
19 | maxHeight: '100vh',
20 | overflowY: 'auto',
21 | overflowX: 'hidden',
22 | '&::webkit-scrollbar': {
23 | display: 'none',
24 | },
25 | '-ms-overflow-style': 'none' /* for Internet Explorer, Edge */,
26 | scrollbarWidth: 'none',
27 | variants: {
28 | size: {
29 | small: {
30 | minWidth: 72,
31 | },
32 | },
33 | overflow: {
34 | true: {
35 | maxHeight: '60vh',
36 | },
37 | },
38 | },
39 | })
40 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/MenuContent/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MenuContent'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/Panel/Panel.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '~styles/stitches.config'
2 |
3 | export const Panel = styled('div', {
4 | backgroundColor: '$panel',
5 | display: 'flex',
6 | alignItems: 'center',
7 | flexDirection: 'row',
8 | boxShadow: '$panel',
9 | padding: '$2',
10 | border: '1px solid $panelContrast',
11 | gap: 0,
12 | overflow: 'hidden',
13 | variants: {
14 | side: {
15 | center: {
16 | borderRadius: 9,
17 | },
18 | left: {
19 | padding: 0,
20 | borderTop: 0,
21 | borderLeft: 0,
22 | borderTopRightRadius: 0,
23 | borderBottomRightRadius: 9,
24 | borderBottomLeftRadius: 0,
25 | },
26 | right: {
27 | padding: 0,
28 | borderTop: 0,
29 | borderRight: 0,
30 | borderTopLeftRadius: 0,
31 | borderBottomLeftRadius: 9,
32 | borderBottomRightRadius: 0,
33 | },
34 | },
35 | },
36 | '& hr': {
37 | height: 10,
38 | width: '100%',
39 | backgroundColor: 'red',
40 | border: 'none',
41 | },
42 | })
43 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/Panel/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Panel'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/RowButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RowButton'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/SmallIcon/SmallIcon.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '~styles'
2 |
3 | export const SmallIcon = styled('div', {
4 | height: '100%',
5 | borderRadius: '4px',
6 | marginRight: '1px',
7 | width: 'fit-content',
8 | display: 'grid',
9 | alignItems: 'center',
10 | justifyContent: 'center',
11 | outline: 'none',
12 | border: 'none',
13 | pointerEvents: 'all',
14 | cursor: 'pointer',
15 | color: 'currentColor',
16 |
17 | '& svg': {
18 | height: 16,
19 | width: 16,
20 | strokeWidth: 1,
21 | },
22 |
23 | '& > *': {
24 | gridRow: 1,
25 | gridColumn: 1,
26 | },
27 | })
28 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/SmallIcon/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SmallIcon'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/TextField/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TextField'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/ToolButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ToolButton'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/Tooltip/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Tooltip'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/BoxIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export function BoxIcon({
4 | fill = 'none',
5 | stroke = 'currentColor',
6 | strokeWidth = 2,
7 | }: {
8 | fill?: string
9 | stroke?: string
10 | strokeWidth?: number
11 | }) {
12 | return (
13 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/CircleIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export function CircleIcon(
4 | props: Pick, 'strokeWidth' | 'stroke' | 'fill'> & {
5 | size: number
6 | }
7 | ) {
8 | const { size = 16, ...rest } = props
9 | return (
10 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/DashDashedIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export function DashDashedIcon() {
4 | return (
5 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/DashDottedIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}`
4 |
5 | export function DashDottedIcon() {
6 | return (
7 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/DashSolidIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export function DashSolidIcon() {
4 | return (
5 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/EraserIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export function EraserIcon() {
4 | return (
5 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/LineIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export function LineIcon() {
4 | return (
5 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/SizeLargeIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export function SizeLargeIcon(props: React.SVGProps) {
4 | return (
5 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/Primitives/icons/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BoxIcon'
2 | export * from './CircleIcon'
3 | export * from './DashDashedIcon'
4 | export * from './DashDottedIcon'
5 | export * from './DashDrawIcon'
6 | export * from './DashSolidIcon'
7 | export * from './TrashIcon'
8 | export * from './UndoIcon'
9 | export * from './SizeSmallIcon'
10 | export * from './SizeMediumIcon'
11 | export * from './SizeLargeIcon'
12 | export * from './EraserIcon'
13 | export * from './DiscordIcon'
14 | export * from './LineIcon'
15 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/ToolsPanel/LockButton.tsx:
--------------------------------------------------------------------------------
1 | import { LockClosedIcon, LockOpen1Icon } from '@radix-ui/react-icons'
2 | import * as React from 'react'
3 | import { ToolButton } from '~components/Primitives/ToolButton'
4 | import { Tooltip } from '~components/Primitives/Tooltip'
5 | import { useTldrawApp } from '~hooks'
6 | import type { TDSnapshot } from '~types'
7 |
8 | const isToolLockedSelector = (s: TDSnapshot) => s.appState.isToolLocked
9 |
10 | export function LockButton() {
11 | const app = useTldrawApp()
12 |
13 | const isToolLocked = app.useStore(isToolLockedSelector)
14 |
15 | return (
16 |
17 |
18 | {isToolLocked ? : }
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/ToolsPanel/ToolsPanel.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { renderWithContext, renderWithIntlProvider } from '~test'
3 | import { ToolsPanel } from './ToolsPanel'
4 |
5 | describe('tools panel', () => {
6 | test('mounts component without crashing', () => {
7 | renderWithContext(renderWithIntlProvider( void null} />))
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/ToolsPanel/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ToolsPanel'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/Menu/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tldraw/tldraw-v1/f786c38ac0fdce337c4405c11cbfa9223d2ee6dd/packages/tldraw/src/components/TopPanel/Menu/index.ts
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/MultiplayerMenu/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MultiplayerMenu'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/PageMenu/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PageMenu'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/PageOptionsDialog/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PageOptionsDialog'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/PreferencesMenu/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PreferencesMenu'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/StyleMenu/StyleMenu.test.tsx:
--------------------------------------------------------------------------------
1 | describe('the style menu', () => {
2 | test.todo('Correctly sets the style properties when shapes are selected')
3 | test.todo('Correctly sets the style properties when nothing is selected')
4 | })
5 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/StyleMenu/index.ts:
--------------------------------------------------------------------------------
1 | export * from './StyleMenu'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/ZoomMenu/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ZoomMenu'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/TopPanel/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TopPanel'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/breakpoints.tsx:
--------------------------------------------------------------------------------
1 | /* -------------------------------------------------- */
2 | /* Breakpoints */
3 | /* -------------------------------------------------- */
4 |
5 | export const breakpoints: any = {
6 | '@initial': 'mobile',
7 | '@micro': 'micro',
8 | '@sm': 'small',
9 | '@md': 'medium',
10 | '@lg': 'large',
11 | }
12 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/preventEvent.ts:
--------------------------------------------------------------------------------
1 | export const preventEvent = (e: Event) => e.preventDefault()
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/components/stopPropagation.ts:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 |
3 | export const stopPropagation = (e: KeyboardEvent | React.SyntheticEvent) =>
4 | e.stopPropagation()
5 |
--------------------------------------------------------------------------------
/packages/tldraw/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useFileSystem'
2 | export * from './useFileSystemHandlers'
3 | export * from './useKeyboardShortcuts'
4 | export * from './useTheme'
5 | export * from './useTldrawApp'
6 | export * from './useTranslation'
7 | export * from './useDialog'
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/hooks/useDialog.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export type DialogState = 'saveFirstTime' | 'saveAgain'
4 |
5 | interface AlertDialogProps {
6 | dialogState: DialogState | null
7 | setDialogState: (dialogState: DialogState | null) => void
8 | onYes: (() => void) | null
9 | onNo: (() => void) | null
10 | onCancel: (() => void) | null
11 | openDialog: (
12 | dialogState: DialogState,
13 | onYes: () => void,
14 | onNo: () => void,
15 | onCancel: () => void
16 | ) => void
17 | }
18 |
19 | export const AlertDialogContext = React.createContext({} as AlertDialogProps)
20 |
21 | export const useDialog = () => {
22 | const context = React.useContext(AlertDialogContext)
23 | if (!context) throw new Error('useCtx must be inside a Provider with a value')
24 | return context
25 | }
26 |
--------------------------------------------------------------------------------
/packages/tldraw/src/hooks/useTheme.ts:
--------------------------------------------------------------------------------
1 | import type { TDSnapshot, Theme } from '~types'
2 | import { useTldrawApp } from './useTldrawApp'
3 |
4 | const themeSelector = (data: TDSnapshot): Theme => (data.settings.isDarkMode ? 'dark' : 'light')
5 |
6 | export function useTheme() {
7 | const app = useTldrawApp()
8 | const theme = app.useStore(themeSelector)
9 |
10 | return {
11 | theme,
12 | toggle: app.toggleDarkMode,
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/tldraw/src/hooks/useTldrawApp.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import type { TldrawApp } from '~state'
3 |
4 | export const TldrawContext = React.createContext({} as TldrawApp)
5 |
6 | const useForceUpdate = () => {
7 | const [_state, setState] = React.useState(0)
8 | React.useEffect(() => setState(1))
9 | }
10 |
11 | export function useTldrawApp() {
12 | const context = React.useContext(TldrawContext)
13 | return context
14 | }
15 |
16 | export const ContainerContext = React.createContext({} as React.RefObject)
17 |
18 | export function useContainer() {
19 | const context = React.useContext(ContainerContext)
20 | useForceUpdate()
21 | return context
22 | }
23 |
--------------------------------------------------------------------------------
/packages/tldraw/src/hooks/useTranslation.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { TDLanguage, getTranslation } from '~translations'
3 |
4 | export function useTranslation(locale?: TDLanguage) {
5 | return React.useMemo(() => {
6 | return getTranslation(locale ?? navigator.language.split(/[-_]/)[0])
7 | }, [locale])
8 | }
9 |
--------------------------------------------------------------------------------
/packages/tldraw/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Tldraw'
2 | export * from './types'
3 | export * from './state/shapes'
4 | export * from './state/TLDR'
5 | export { TldrawApp } from './state'
6 | export { useFileSystem } from './hooks'
7 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/IdbClipboard.ts:
--------------------------------------------------------------------------------
1 | import { del, get, set } from 'idb-keyval'
2 |
3 | // Used for clipboard
4 |
5 | const ID = 'tldraw_clipboard'
6 |
7 | export async function getClipboard(): Promise {
8 | return get(ID)
9 | }
10 |
11 | export async function setClipboard(item: string): Promise {
12 | return set(ID, item)
13 | }
14 |
15 | export function clearClipboard(): Promise {
16 | return del(ID)
17 | }
18 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/StateManager/index.ts:
--------------------------------------------------------------------------------
1 | export * from './StateManager'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/alignShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './alignShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/changePage/changePage.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawTestApp, mockDocument } from '~test'
2 |
3 | describe('Change page command', () => {
4 | const app = new TldrawTestApp()
5 |
6 | it('does, undoes and redoes command', () => {
7 | app.loadDocument(mockDocument)
8 |
9 | const initialId = app.page.id
10 |
11 | app.createPage()
12 |
13 | const nextId = app.page.id
14 |
15 | app.changePage(initialId)
16 |
17 | expect(app.page.id).toBe(initialId)
18 |
19 | app.changePage(nextId)
20 |
21 | expect(app.page.id).toBe(nextId)
22 |
23 | app.undo()
24 |
25 | expect(app.page.id).toBe(initialId)
26 |
27 | app.redo()
28 |
29 | expect(app.page.id).toBe(nextId)
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/changePage/changePage.ts:
--------------------------------------------------------------------------------
1 | import type { TldrawApp } from '~state/TldrawApp'
2 | import type { TldrawCommand } from '~types'
3 |
4 | export function changePage(app: TldrawApp, pageId: string): TldrawCommand {
5 | return {
6 | id: 'change_page',
7 | before: {
8 | appState: {
9 | currentPageId: app.currentPageId,
10 | },
11 | },
12 | after: {
13 | appState: {
14 | currentPageId: pageId,
15 | },
16 | },
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/changePage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './changePage'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/createPage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createPage'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/createShapes/createShapes.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawTestApp, mockDocument } from '~test'
2 |
3 | describe('Create command', () => {
4 | const app = new TldrawTestApp()
5 |
6 | beforeEach(() => {
7 | app.loadDocument(mockDocument)
8 | })
9 |
10 | describe('when no shape is provided', () => {
11 | it('does nothing', () => {
12 | const initialState = app.state
13 | app.create()
14 |
15 | const currentState = app.state
16 |
17 | expect(currentState).toEqual(initialState)
18 | })
19 | })
20 |
21 | it('does, undoes and redoes command', () => {
22 | const shape = { ...app.getShape('rect1'), id: 'rect4' }
23 | app.create([shape])
24 |
25 | expect(app.getShape('rect4')).toBeTruthy()
26 |
27 | app.undo()
28 |
29 | expect(app.getShape('rect4')).toBe(undefined)
30 |
31 | app.redo()
32 |
33 | expect(app.getShape('rect4')).toBeTruthy()
34 | })
35 |
36 | it.todo('Creates bindings')
37 | })
38 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/createShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/deletePage/deletePage.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawTestApp, mockDocument } from '~test'
2 |
3 | describe('Delete page', () => {
4 | const app = new TldrawTestApp()
5 |
6 | beforeEach(() => {
7 | app.loadDocument(mockDocument)
8 | })
9 |
10 | describe('when there are no pages in the current document', () => {
11 | it('does nothing', () => {
12 | app.resetDocument()
13 | const initialState = app.state
14 | app.deletePage('page1')
15 | const currentState = app.state
16 |
17 | expect(currentState).toEqual(initialState)
18 | })
19 | })
20 |
21 | it('does, undoes and redoes command', () => {
22 | const initialId = app.currentPageId
23 |
24 | app.createPage()
25 |
26 | const nextId = app.currentPageId
27 |
28 | app.deletePage()
29 |
30 | expect(app.currentPageId).toBe(initialId)
31 |
32 | app.undo()
33 |
34 | expect(app.currentPageId).toBe(nextId)
35 |
36 | app.redo()
37 |
38 | expect(app.currentPageId).toBe(initialId)
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/deletePage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './deletePage'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/deleteShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './deleteShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/distributeShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './distributeShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/duplicatePage/duplicatePage.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawTestApp, mockDocument } from '~test'
2 |
3 | describe('Duplicate page command', () => {
4 | const app = new TldrawTestApp()
5 |
6 | it('does, undoes and redoes command', () => {
7 | app.loadDocument(mockDocument)
8 |
9 | const initialId = app.page.id
10 |
11 | app.duplicatePage(app.currentPageId)
12 |
13 | const nextId = app.page.id
14 |
15 | app.undo()
16 |
17 | expect(app.page.id).toBe(initialId)
18 |
19 | app.redo()
20 |
21 | expect(app.page.id).toBe(nextId)
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/duplicatePage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './duplicatePage'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/duplicateShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './duplicateShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/flipShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './flipShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/groupShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './groupShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/index.ts:
--------------------------------------------------------------------------------
1 | export * from './alignShapes'
2 | export * from './changePage'
3 | export * from './createPage'
4 | export * from './createShapes'
5 | export * from './deletePage'
6 | export * from './deleteShapes'
7 | export * from './distributeShapes'
8 | export * from './duplicatePage'
9 | export * from './duplicateShapes'
10 | export * from './flipShapes'
11 | export * from './groupShapes'
12 | export * from './moveShapesToPage'
13 | export * from './movePage'
14 | export * from './reorderShapes'
15 | export * from './renamePage'
16 | export * from './resetBounds'
17 | export * from './rotateShapes'
18 | export * from './stretchShapes'
19 | export * from './styleShapes'
20 | export * from './toggleShapesDecoration'
21 | export * from './toggleShapesProp'
22 | export * from './translateShapes'
23 | export * from './ungroupShapes'
24 | export * from './updateShapes'
25 | export * from './setShapesProps'
26 | export * from './insertContent'
27 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/insertContent/index.ts:
--------------------------------------------------------------------------------
1 | export * from './insertContent'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/movePage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './movePage'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/moveShapesToPage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './moveShapesToPage'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/renamePage/index.ts:
--------------------------------------------------------------------------------
1 | export * from './renamePage'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/renamePage/renamePage.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawTestApp, mockDocument } from '~test'
2 |
3 | describe('Rename page command', () => {
4 | const app = new TldrawTestApp()
5 |
6 | it('does, undoes and redoes command', () => {
7 | app.loadDocument(mockDocument)
8 |
9 | const initialId = app.page.id
10 | const initialName = app.page.name
11 |
12 | app.renamePage(initialId, 'My Special Page')
13 |
14 | expect(app.page.name).toBe('My Special Page')
15 |
16 | app.undo()
17 |
18 | expect(app.page.name).toBe(initialName)
19 |
20 | app.redo()
21 |
22 | expect(app.page.name).toBe('My Special Page')
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/renamePage/renamePage.ts:
--------------------------------------------------------------------------------
1 | import type { TldrawApp } from '~state/TldrawApp'
2 | import type { TldrawCommand } from '~types'
3 |
4 | export function renamePage(app: TldrawApp, pageId: string, name: string): TldrawCommand {
5 | const { page } = app
6 |
7 | return {
8 | id: 'rename_page',
9 | before: {
10 | document: {
11 | pages: {
12 | [pageId]: { name: page.name },
13 | },
14 | },
15 | },
16 | after: {
17 | document: {
18 | pages: {
19 | [pageId]: { name: name },
20 | },
21 | },
22 | },
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/reorderShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './reorderShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/resetBounds/index.ts:
--------------------------------------------------------------------------------
1 | export * from './resetBounds'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/rotateShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './rotateShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/setShapesProps/index.ts:
--------------------------------------------------------------------------------
1 | export * from './setShapesProps'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/setShapesProps/setShapesProps.spec.ts:
--------------------------------------------------------------------------------
1 | describe('Set shapes props command', () => {
2 | it.todo('sets the props of the provided shapes')
3 | })
4 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/shared/getIncrementedName.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Get an incremented name (e.g. Page 2) from a name (e.g. Page 1), based on an array of existing names.
3 | *
4 | * @param name The name to increment.
5 | * @param others The array of existing names.
6 | */
7 | export function getIncrementedName(name: string, others: string[]) {
8 | let result = name
9 | const set = new Set(others)
10 |
11 | while (set.has(result)) {
12 | result = /^.*(\d+)$/.exec(result)?.[1]
13 | ? result.replace(/(\d+)(?=\D?)$/, (m) => (+m + 1).toString())
14 | : `${result} 1`
15 | }
16 |
17 | return result
18 | }
19 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getIncrementedName'
2 | export * from './removeShapesFromPage'
3 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/stretchShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './stretchShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/styleShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './styleShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/toggleShapesDecoration/index.ts:
--------------------------------------------------------------------------------
1 | export * from './toggleShapesDecoration'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/toggleShapesProp/index.ts:
--------------------------------------------------------------------------------
1 | export * from './toggleShapesProp'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/translateShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './translateShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/ungroupShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ungroupShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/updateShapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './updateShapes'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/updateShapes/updateShapes.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawTestApp, mockDocument } from '~test'
2 |
3 | describe('Update command', () => {
4 | const app = new TldrawTestApp()
5 |
6 | beforeEach(() => {
7 | app.loadDocument(mockDocument)
8 | })
9 |
10 | describe('when no shape is selected', () => {
11 | it('does nothing', () => {
12 | const initialState = app.state
13 | app.updateShapes()
14 | const currentState = app.state
15 |
16 | expect(currentState).toEqual(initialState)
17 | })
18 | })
19 |
20 | it('does, undoes and redoes command', () => {
21 | app.updateShapes({ id: 'rect1', point: [100, 100] })
22 |
23 | expect(app.getShape('rect1').point).toStrictEqual([100, 100])
24 |
25 | app.undo()
26 |
27 | expect(app.getShape('rect1').point).toStrictEqual([0, 0])
28 |
29 | app.redo()
30 |
31 | expect(app.getShape('rect1').point).toStrictEqual([100, 100])
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/commands/updateShapes/updateShapes.ts:
--------------------------------------------------------------------------------
1 | import { TLDR } from '~state/TLDR'
2 | import type { TldrawApp } from '~state/TldrawApp'
3 | import type { TDShape, TldrawCommand } from '~types'
4 |
5 | export function updateShapes(
6 | app: TldrawApp,
7 | updates: ({ id: string } & Partial)[],
8 | pageId: string
9 | ): TldrawCommand {
10 | const ids = updates.map((update) => update.id)
11 |
12 | const change = TLDR.mutateShapes(
13 | app.state,
14 | ids.filter((id) => !app.getShape(id, pageId).isLocked),
15 | (_shape, i) => updates[i],
16 | pageId
17 | )
18 |
19 | return {
20 | id: 'update',
21 | before: {
22 | document: {
23 | pages: {
24 | [pageId]: {
25 | shapes: change.before,
26 | },
27 | },
28 | },
29 | },
30 | after: {
31 | document: {
32 | pages: {
33 | [pageId]: {
34 | shapes: change.after,
35 | },
36 | },
37 | },
38 | },
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/data/filesystem.spec.ts:
--------------------------------------------------------------------------------
1 | describe('when saving data to the file system', () => {
2 | it.todo('saves a new file in the filesystem')
3 | it.todo('saves a new file in the filesystem')
4 | })
5 |
6 | describe('when opening files from file system', () => {
7 | it.todo('opens a file and loads it into the document')
8 | it.todo('opens an older file, migrates it, and loads it into the document')
9 | it.todo('opens a corrupt file, tries to fix it, and fails without crashing')
10 | })
11 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/data/index.ts:
--------------------------------------------------------------------------------
1 | export * from './migrate'
2 | export * from './filesystem'
3 | export * from 'browser-fs-access'
4 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/data/migrate.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import oldDoc from '~test/documents/old-doc'
3 | import oldDoc2 from '~test/documents/old-doc-2'
4 | import type { TDDocument } from '~types'
5 |
6 | describe('When migrating bindings', () => {
7 | it('migrates a document without a version', () => {
8 | new TldrawApp().loadDocument(oldDoc as unknown as TDDocument)
9 | })
10 |
11 | it('migrates a document with an older version', () => {
12 | const app = new TldrawApp().loadDocument(oldDoc2 as unknown as TDDocument)
13 | expect(app.getShape('d7ab0a49-3cb3-43ae-3d83-f5cf2f4a510a').style.color).toBe('black')
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/index.ts:
--------------------------------------------------------------------------------
1 | import './internal'
2 |
3 | export * from './TldrawApp'
4 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/internal.ts:
--------------------------------------------------------------------------------
1 | export * from './TldrawApp'
2 | export * from './sessions'
3 | export * from './commands'
4 | export * from './tools'
5 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/ArrowSession/__snapshots__/ArrowSession.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Arrow session arrow binding binds on the inside of a shape while alt is held 1`] = `
4 | Array [
5 | 0.76,
6 | 0.09,
7 | ]
8 | `;
9 |
10 | exports[`Arrow session arrow binding snaps to the inside center when the point is close to the center 1`] = `
11 | Array [
12 | 0.81,
13 | 0.19,
14 | ]
15 | `;
16 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/ArrowSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ArrowSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/BaseSession.ts:
--------------------------------------------------------------------------------
1 | import type { TLPerformanceMode } from '@tldraw/core'
2 | import type { TldrawApp } from '~state/TldrawApp'
3 | import type { SessionType, TldrawCommand, TldrawPatch } from '~types'
4 |
5 | export abstract class BaseSession {
6 | abstract type: SessionType
7 | abstract performanceMode: TLPerformanceMode | undefined
8 | constructor(public app: TldrawApp) {}
9 | abstract start: () => TldrawPatch | undefined
10 | abstract update: () => TldrawPatch | undefined
11 | abstract complete: () => TldrawPatch | TldrawCommand | undefined
12 | abstract cancel: () => TldrawPatch | undefined
13 | }
14 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/BrushSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BrushSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/DrawSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DrawSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/EditSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EditSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/EraseSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EraseSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/GridSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './GridSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/HandleSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './HandleSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/RotateSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RotateSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/TransformSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TransformSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/TransformSingleSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TransformSingleSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/TranslateLabelSession/TranslateLabelSession.spec.ts:
--------------------------------------------------------------------------------
1 | describe('Translate label session', () => {
2 | it.todo('begins, updateSession')
3 | it.todo('cancels session')
4 | })
5 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/TranslateLabelSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TranslateLabelSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/sessions/TranslateSession/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TranslateSession'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/ArrowUtil/components/ArrowHead.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export interface ArrowheadProps {
4 | left: number[]
5 | middle: number[]
6 | right: number[]
7 | stroke: string
8 | strokeWidth: number
9 | }
10 |
11 | export function Arrowhead({ left, middle, right, stroke, strokeWidth }: ArrowheadProps) {
12 | return (
13 |
14 |
15 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/ArrowUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ArrowUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/DrawUtil/DrawUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Draw } from '..'
2 |
3 | describe('Draw shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Draw.create({ id: 'draw' })).toMatchSnapshot('draw')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/DrawUtil/__snapshots__/DrawUtil.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Draw shape Creates a shape: draw 1`] = `
4 | Object {
5 | "childIndex": 1,
6 | "id": "draw",
7 | "isComplete": false,
8 | "name": "Draw",
9 | "parentId": "page",
10 | "point": Array [
11 | 0,
12 | 0,
13 | ],
14 | "points": Array [],
15 | "rotation": 0,
16 | "style": Object {
17 | "color": "black",
18 | "dash": "draw",
19 | "isFilled": false,
20 | "scale": 1,
21 | "size": "small",
22 | },
23 | "type": "draw",
24 | }
25 | `;
26 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/DrawUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DrawUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/EllipseUtil/EllipseUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Ellipse } from '..'
2 |
3 | describe('Ellipse shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Ellipse.create({ id: 'ellipse' })).toMatchSnapshot('ellipse')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/EllipseUtil/__snapshots__/EllipseUtil.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Ellipse shape Creates a shape: ellipse 1`] = `
4 | Object {
5 | "childIndex": 1,
6 | "id": "ellipse",
7 | "label": "",
8 | "labelPoint": Array [
9 | 0.5,
10 | 0.5,
11 | ],
12 | "name": "Ellipse",
13 | "parentId": "page",
14 | "point": Array [
15 | 0,
16 | 0,
17 | ],
18 | "radius": Array [
19 | 1,
20 | 1,
21 | ],
22 | "rotation": 0,
23 | "style": Object {
24 | "color": "black",
25 | "dash": "draw",
26 | "isFilled": false,
27 | "scale": 1,
28 | "size": "small",
29 | },
30 | "type": "ellipse",
31 | }
32 | `;
33 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/EllipseUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EllipseUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/GroupUtil/GroupUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Group } from '..'
2 |
3 | describe('Group shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Group.create({ id: 'group' })).toMatchSnapshot('group')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/GroupUtil/__snapshots__/GroupUtil.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Group shape Creates a shape: group 1`] = `
4 | Object {
5 | "childIndex": 1,
6 | "children": Array [],
7 | "id": "group",
8 | "name": "Group",
9 | "parentId": "page",
10 | "point": Array [
11 | 0,
12 | 0,
13 | ],
14 | "rotation": 0,
15 | "size": Array [
16 | 100,
17 | 100,
18 | ],
19 | "style": Object {
20 | "color": "black",
21 | "dash": "draw",
22 | "isFilled": false,
23 | "scale": 1,
24 | "size": "small",
25 | },
26 | "type": "group",
27 | }
28 | `;
29 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/GroupUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './GroupUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/ImageUtil/ImageUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Image } from '..'
2 |
3 | describe('Image shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Image.create({ id: 'image' })).toMatchSnapshot('image')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/ImageUtil/__snapshots__/ImageUtil.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Image shape Creates a shape: image 1`] = `
4 | Object {
5 | "assetId": "assetId",
6 | "childIndex": 1,
7 | "id": "image",
8 | "name": "Image",
9 | "parentId": "page",
10 | "point": Array [
11 | 0,
12 | 0,
13 | ],
14 | "rotation": 0,
15 | "size": Array [
16 | 1,
17 | 1,
18 | ],
19 | "style": Object {
20 | "color": "black",
21 | "dash": "draw",
22 | "isFilled": true,
23 | "scale": 1,
24 | "size": "small",
25 | },
26 | "type": "image",
27 | }
28 | `;
29 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/ImageUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ImageUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/RectangleUtil/RectangleUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Rectangle } from '..'
2 |
3 | describe('Rectangle shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Rectangle.create({ id: 'rectangle' })).toMatchSnapshot('rectangle')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/RectangleUtil/__snapshots__/RectangleUtil.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Rectangle shape Creates a shape: rectangle 1`] = `
4 | Object {
5 | "childIndex": 1,
6 | "id": "rectangle",
7 | "label": "",
8 | "labelPoint": Array [
9 | 0.5,
10 | 0.5,
11 | ],
12 | "name": "Rectangle",
13 | "parentId": "page",
14 | "point": Array [
15 | 0,
16 | 0,
17 | ],
18 | "rotation": 0,
19 | "size": Array [
20 | 1,
21 | 1,
22 | ],
23 | "style": Object {
24 | "color": "black",
25 | "dash": "draw",
26 | "isFilled": false,
27 | "scale": 1,
28 | "size": "small",
29 | },
30 | "type": "rectangle",
31 | }
32 | `;
33 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/RectangleUtil/components/BindingIndicator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { BINDING_DISTANCE } from '~constants'
3 |
4 | interface BindingIndicatorProps {
5 | strokeWidth: number
6 | size: number[]
7 | }
8 | export function BindingIndicator({ strokeWidth, size }: BindingIndicatorProps) {
9 | return (
10 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/RectangleUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RectangleUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/StickyUtil/StickyUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Sticky } from '..'
2 |
3 | describe('Post-It shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Sticky.create).toBeDefined()
6 | // expect(Sticky.create({ id: 'sticky' })).toMatchSnapshot('sticky')
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/StickyUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './StickyUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/TextUtil/TextUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Text } from '..'
2 |
3 | describe('Text shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Text.create({ id: 'text' })).toMatchSnapshot('text')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/TextUtil/__snapshots__/TextUtil.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Text shape Creates a shape: text 1`] = `
4 | Object {
5 | "childIndex": 1,
6 | "id": "text",
7 | "name": "Text",
8 | "parentId": "page",
9 | "point": Array [
10 | 0,
11 | 0,
12 | ],
13 | "rotation": 0,
14 | "style": Object {
15 | "color": "black",
16 | "dash": "draw",
17 | "font": "script",
18 | "isFilled": false,
19 | "scale": 1,
20 | "size": "small",
21 | "textAlign": "middle",
22 | },
23 | "text": " ",
24 | "type": "text",
25 | }
26 | `;
27 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/TextUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TextUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/TriangleUtil/TriangleUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Triangle } from '..'
2 |
3 | describe('Triangle shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Triangle.create({ id: 'triangle' })).toMatchSnapshot('triangle')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/TriangleUtil/__snapshots__/TriangleUtil.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Triangle shape Creates a shape: triangle 1`] = `
4 | Object {
5 | "childIndex": 1,
6 | "id": "triangle",
7 | "label": "",
8 | "labelPoint": Array [
9 | 0.5,
10 | 0.5,
11 | ],
12 | "name": "Triangle",
13 | "parentId": "page",
14 | "point": Array [
15 | 0,
16 | 0,
17 | ],
18 | "rotation": 0,
19 | "size": Array [
20 | 1,
21 | 1,
22 | ],
23 | "style": Object {
24 | "color": "black",
25 | "dash": "draw",
26 | "isFilled": false,
27 | "scale": 1,
28 | "size": "small",
29 | },
30 | "type": "triangle",
31 | }
32 | `;
33 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/TriangleUtil/components/TriangleBindingIndicator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { BINDING_DISTANCE } from '~constants'
3 | import { getTrianglePoints } from '../triangleHelpers'
4 |
5 | interface TriangleBindingIndicatorProps {
6 | size: number[]
7 | }
8 |
9 | export function TriangleBindingIndicator({ size }: TriangleBindingIndicatorProps) {
10 | const trianglePoints = getTrianglePoints(size).join()
11 | return (
12 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/TriangleUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TriangleUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/VideoUtil/VideoUtil.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Video } from '..'
2 |
3 | describe('Video shape', () => {
4 | it('Creates a shape', () => {
5 | expect(Video.create({ id: 'video' })).toMatchSnapshot('video')
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/VideoUtil/__snapshots__/VideoUtil.spec.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Video shape Creates a shape: video 1`] = `
4 | Object {
5 | "assetId": "assetId",
6 | "childIndex": 1,
7 | "currentTime": 0,
8 | "id": "video",
9 | "isPlaying": true,
10 | "name": "Video",
11 | "parentId": "page",
12 | "point": Array [
13 | 0,
14 | 0,
15 | ],
16 | "rotation": 0,
17 | "size": Array [
18 | 1,
19 | 1,
20 | ],
21 | "style": Object {
22 | "color": "black",
23 | "dash": "draw",
24 | "isFilled": false,
25 | "scale": 1,
26 | "size": "small",
27 | },
28 | "type": "video",
29 | }
30 | `;
31 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/VideoUtil/index.ts:
--------------------------------------------------------------------------------
1 | export * from './VideoUtil'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/about-shape-utils.md:
--------------------------------------------------------------------------------
1 | # Shape Utils
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/shared/LabelMask.tsx:
--------------------------------------------------------------------------------
1 | import type { TLBounds } from '@tldraw/core'
2 | import * as React from 'react'
3 |
4 | interface WithLabelMaskProps {
5 | id: string
6 | bounds: TLBounds
7 | labelSize: number[]
8 | offset?: number[]
9 | scale?: number
10 | }
11 |
12 | export function LabelMask({ id, bounds, labelSize, offset, scale = 1 }: WithLabelMaskProps) {
13 | return (
14 |
15 |
16 |
23 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/shared/getBoundsRectangle.ts:
--------------------------------------------------------------------------------
1 | import { TLBounds, TLShape, Utils } from '@tldraw/core'
2 |
3 | /**
4 | * Find the bounds of a rectangular shape.
5 | * @param shape
6 | * @param boundsCache
7 | */
8 | export function getBoundsRectangle(
9 | shape: T,
10 | boundsCache: WeakMap
11 | ) {
12 | const bounds = Utils.getFromCache(boundsCache, shape, () => {
13 | const [width, height] = shape.size
14 | return {
15 | minX: 0,
16 | maxX: width,
17 | minY: 0,
18 | maxY: height,
19 | width,
20 | height,
21 | }
22 | })
23 |
24 | return Utils.translateBounds(bounds, shape.point)
25 | }
26 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/shared/getTextAlign.ts:
--------------------------------------------------------------------------------
1 | import { AlignStyle } from '~types'
2 |
3 | const ALIGN_VALUES = {
4 | [AlignStyle.Start]: 'left',
5 | [AlignStyle.Middle]: 'center',
6 | [AlignStyle.End]: 'right',
7 | [AlignStyle.Justify]: 'justify',
8 | } as const
9 |
10 | export function getTextAlign(alignStyle: AlignStyle = AlignStyle.Start) {
11 | return ALIGN_VALUES[alignStyle]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getBoundsRectangle'
2 | export * from './getTextAlign'
3 | export * from './getTextSize'
4 | export * from './getTextSvgElement'
5 | export * from './LabelMask'
6 | export * from './PolygonUtils'
7 | export * from './shape-styles'
8 | export * from './TextAreaUtils'
9 | export * from './TextLabel'
10 | export * from './transformRectangle'
11 | export * from './transformSingleRectangle'
12 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/shapes/shared/transformSingleRectangle.ts:
--------------------------------------------------------------------------------
1 | import type { TLBounds, TLShape } from '@tldraw/core'
2 | import Vec from '@tldraw/vec'
3 |
4 | /**
5 | * Transform a single rectangular shape.
6 | * @param shape
7 | * @param bounds
8 | */
9 | export function transformSingleRectangle(
10 | shape: T,
11 | bounds: TLBounds
12 | ) {
13 | return {
14 | size: Vec.toFixed([bounds.width, bounds.height]),
15 | point: Vec.toFixed([bounds.minX, bounds.minY]),
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/ArrowTool/ArrowTool.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import { ArrowTool } from '.'
3 |
4 | describe('ArrowTool', () => {
5 | it('creates tool', () => {
6 | const app = new TldrawApp()
7 | new ArrowTool(app)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/ArrowTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ArrowTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/DrawTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DrawTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/EllipseTool/EllipseTool.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import { EllipseTool } from '.'
3 |
4 | describe('EllipseTool', () => {
5 | it('creates tool', () => {
6 | const app = new TldrawApp()
7 | new EllipseTool(app)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/EllipseTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EllipseTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/EraseTool/EraseTool.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import { EraseTool } from './EraseTool'
3 |
4 | describe('EraseTool', () => {
5 | it('creates tool', () => {
6 | const app = new TldrawApp()
7 | new EraseTool(app)
8 | })
9 |
10 | it.todo('restores previous tool after erasing')
11 | })
12 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/EraseTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EraseTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/LineTool/LineTool.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import { LineTool } from '.'
3 |
4 | describe('LineTool', () => {
5 | it('creates tool', () => {
6 | const app = new TldrawApp()
7 | new LineTool(app)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/LineTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './LineTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/RectangleTool/RectangleTool.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import { RectangleTool } from '.'
3 |
4 | describe('RectangleTool', () => {
5 | it('creates tool', () => {
6 | const app = new TldrawApp()
7 | new RectangleTool(app)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/RectangleTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RectangleTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/SelectTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SelectTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/StickyTool/StickyTool.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import { StickyTool } from '.'
3 |
4 | describe('StickyTool', () => {
5 | it('creates tool', () => {
6 | const app = new TldrawApp()
7 | new StickyTool(app)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/StickyTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './StickyTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/TextTool/TextTool.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import { TextTool } from '.'
3 |
4 | describe('TextTool', () => {
5 | it('creates tool', () => {
6 | const app = new TldrawApp()
7 | new TextTool(app)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/TextTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TextTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/TriangleTool/TriangleTool.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawApp } from '~state'
2 | import { TriangleTool } from '.'
3 |
4 | describe('TriangleTool', () => {
5 | it('creates tool', () => {
6 | const app = new TldrawApp()
7 | new TriangleTool(app)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/tldraw/src/state/tools/TriangleTool/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TriangleTool'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/styles/index.ts:
--------------------------------------------------------------------------------
1 | export * from './stitches.config'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/test/badDocument.spec.ts:
--------------------------------------------------------------------------------
1 | import { TldrawTestApp } from './TldrawTestApp'
2 | import { badDocument } from './documents/badDocument'
3 |
4 | describe('When loading a bad document', () => {
5 | it('Fixes the document', () => {
6 | const app = new TldrawTestApp()
7 |
8 | global.console.warn = jest.fn()
9 |
10 | // This doc has parents that are missing and children set to missing shapes
11 | app.loadDocument(badDocument as any)
12 |
13 | for (const pageId in app.document.pages) {
14 | const page = app.document.pages[pageId]
15 | for (const shape of Object.values(page.shapes)) {
16 | if (shape.parentId === pageId) continue
17 | expect(page.shapes[shape.parentId]).toBeDefined()
18 | }
19 | }
20 |
21 | expect(app.getShape('rect2').parentId).toBe('page1')
22 | expect(app.getShape('group1').children!.length).toBe(0)
23 |
24 | expect(global.console.warn).toHaveBeenCalled()
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/packages/tldraw/src/test/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mockDocument'
2 | export * from './renderWithContext'
3 | export * from './TldrawTestApp'
4 | export * from './renderWithIntlProvider'
5 |
--------------------------------------------------------------------------------
/packages/tldraw/src/test/renderWithContext.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react'
2 | import * as React from 'react'
3 | import { TldrawContext, useKeyboardShortcuts } from '~hooks'
4 | import { TldrawApp } from '~state'
5 | import { mockDocument } from './mockDocument'
6 |
7 | export const Wrapper = ({ children }: { children: any }) => {
8 | const [app] = React.useState(() => new TldrawApp())
9 | const [context] = React.useState(() => {
10 | return app
11 | })
12 |
13 | const rWrapper = React.useRef(null)
14 |
15 | useKeyboardShortcuts(rWrapper)
16 |
17 | React.useEffect(() => {
18 | if (!document) return
19 | app.loadDocument(mockDocument)
20 | }, [document, app])
21 |
22 | return (
23 |
24 | {children}
25 |
26 | )
27 | }
28 |
29 | export const renderWithContext = (children: React.ReactNode) => {
30 | return render({children})
31 | }
32 |
--------------------------------------------------------------------------------
/packages/tldraw/src/test/renderWithIntlProvider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { IntlProvider } from 'react-intl'
3 | import messages_fr from '~translations/fr.json'
4 | import messages_en from '~translations/main.json'
5 |
6 | export const renderWithIntlProvider = (children: React.ReactNode) => {
7 | const messages = {
8 | en: messages_en,
9 | fr: messages_fr,
10 | }
11 | const language = navigator.language.split(/[-_]/)[0]
12 | return (
13 | // @ts-ignore
14 |
15 | <>{children}>
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/packages/tldraw/src/test/stylemock.ts:
--------------------------------------------------------------------------------
1 | export {}
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/translations/en.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/packages/tldraw/src/translations/index.ts:
--------------------------------------------------------------------------------
1 | export * from './translations'
2 |
--------------------------------------------------------------------------------
/packages/tldraw/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "skipLibCheck": true,
14 | "composite": false,
15 | "incremental": false,
16 | "declaration": true,
17 | "declarationMap": true,
18 | "sourceMap": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/tldraw/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "skipLibCheck": true,
14 | "composite": false,
15 | "incremental": false,
16 | "declaration": true,
17 | "declarationMap": true,
18 | "sourceMap": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/tldraw/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "exclude": ["node_modules", "dist"],
4 | "include": ["src", "./src/translations/*.json"],
5 | "compilerOptions": {
6 | "skipLibCheck": true,
7 | "outDir": "./dist",
8 | "rootDir": "src",
9 | "baseUrl": ".",
10 | "paths": {
11 | "~*": ["./src/*"]
12 | }
13 | },
14 | "references": [{ "path": "../vec" }, { "path": "../intersect" }, { "path": "../core" }]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vec/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.9.2",
3 | "name": "@tldraw/vec",
4 | "description": "2D vector utilities for TLDraw and maybe you, too.",
5 | "author": "@steveruizok",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/tldraw/tldraw.git"
9 | },
10 | "license": "MIT",
11 | "keywords": [
12 | "2d",
13 | "vector",
14 | "typescript",
15 | "javascript"
16 | ],
17 | "files": [
18 | "dist/**/*"
19 | ],
20 | "main": "./dist/index.js",
21 | "module": "./dist/index.mjs",
22 | "types": "./dist/index.d.ts",
23 | "source": "./src/index.ts",
24 | "scripts": {
25 | "dev": "yarn lfg -d",
26 | "build": "yarn lfg",
27 | "lint": "TIMING=1 eslint src/ --ext .ts,.tsx",
28 | "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
29 | },
30 | "gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296",
31 | "devDependencies": {
32 | "@tldraw/lfg": "latest"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/vec/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "composite": false,
14 | "incremental": false,
15 | "declaration": true,
16 | "declarationMap": true,
17 | "sourceMap": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/vec/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": [
4 | "node_modules",
5 | "**/*.test.tsx",
6 | "**/*.test.ts",
7 | "**/*.spec.tsx",
8 | "**/*.spec.ts",
9 | "src/test",
10 | "dist"
11 | ],
12 | "compilerOptions": {
13 | "composite": false,
14 | "incremental": false,
15 | "declaration": true,
16 | "declarationMap": true,
17 | "sourceMap": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/vec/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "include": ["src"],
4 | "exclude": ["node_modules", "dist"],
5 | "compilerOptions": {
6 | "outDir": "./dist",
7 | "rootDir": "src",
8 | "baseUrl": "."
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/setupTests.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/extend-expect'
2 | import 'fake-indexeddb/auto'
3 |
4 | global.ResizeObserver = require('resize-observer-polyfill')
5 |
6 | Object.defineProperty(window, 'matchMedia', {
7 | writable: true,
8 | value: jest.fn().mockImplementation((query) => ({
9 | matches: false,
10 | media: query,
11 | onchange: null,
12 | addListener: jest.fn(), // Deprecated
13 | removeListener: jest.fn(), // Deprecated
14 | addEventListener: jest.fn(),
15 | removeEventListener: jest.fn(),
16 | dispatchEvent: jest.fn(),
17 | })),
18 | })
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------