@tldraw/editor 3.12.0-canary.423f9b4f2a86 → 3.12.0-canary.47eb43f692c5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +113 -12
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +1 -0
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/GeometryDebuggingView.js +2 -2
- package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +10 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +184 -10
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FocusManager.js +1 -1
- package/dist-cjs/lib/editor/managers/FocusManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +12 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +4 -13
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/types/selection-types.js.map +1 -1
- package/dist-cjs/lib/hooks/useDocumentEvents.js +16 -0
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +10 -20
- package/dist-cjs/lib/license/Watermark.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +133 -16
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +3 -3
- package/dist-cjs/lib/primitives/geometry/Group2d.js +54 -11
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/intersect.js +20 -0
- package/dist-cjs/lib/primitives/intersect.js.map +2 -2
- package/dist-cjs/lib/utils/reorderShapes.js +2 -8
- package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +113 -12
- package/dist-esm/index.mjs +8 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +1 -0
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/GeometryDebuggingView.mjs +3 -3
- package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +10 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +185 -10
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FocusManager.mjs +1 -1
- package/dist-esm/lib/editor/managers/FocusManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +12 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +4 -13
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +16 -0
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +10 -20
- package/dist-esm/lib/license/Watermark.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +137 -14
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +55 -12
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/intersect.mjs +20 -0
- package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
- package/dist-esm/lib/utils/reorderShapes.mjs +2 -8
- package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +11 -2
- package/src/lib/TldrawEditor.tsx +1 -0
- package/src/lib/components/GeometryDebuggingView.tsx +3 -3
- package/src/lib/components/default-components/DefaultCanvas.tsx +6 -1
- package/src/lib/editor/Editor.ts +263 -16
- package/src/lib/editor/managers/FocusManager.ts +1 -1
- package/src/lib/editor/shapes/ShapeUtil.ts +14 -0
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +7 -15
- package/src/lib/editor/types/selection-types.ts +3 -0
- package/src/lib/hooks/useDocumentEvents.ts +18 -0
- package/src/lib/license/Watermark.tsx +18 -29
- package/src/lib/primitives/geometry/Geometry2d.ts +196 -16
- package/src/lib/primitives/geometry/Group2d.ts +76 -13
- package/src/lib/primitives/intersect.ts +41 -0
- package/src/lib/utils/reorderShapes.ts +2 -9
- package/src/version.ts +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/utils/reorderShapes.ts"],
|
|
4
|
-
"sourcesContent": ["import { TLParentId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndicesBetween, sortByIndex } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { Vec } from '../primitives/Vec'\nimport { polygonsIntersect } from '../primitives/intersect'\n\nexport function getReorderingShapesChanges(\n\teditor: Editor,\n\toperation: 'toBack' | 'toFront' | 'forward' | 'backward',\n\tids: TLShapeId[],\n\topts?: { considerAllShapes?: boolean }\n) {\n\tif (ids.length === 0) return []\n\n\t// From the ids that are moving, collect the parents, their children, and which of those children are moving\n\tconst parents = new Map<TLParentId, { moving: Set<TLShape>; children: TLShape[] }>()\n\n\tfor (const shape of compact(ids.map((id) => editor.getShape(id)))) {\n\t\tconst { parentId } = shape\n\t\tif (!parents.has(parentId)) {\n\t\t\tparents.set(parentId, {\n\t\t\t\tchildren: compact(\n\t\t\t\t\teditor.getSortedChildIdsForParent(parentId).map((id) => editor.getShape(id))\n\t\t\t\t),\n\t\t\t\tmoving: new Set(),\n\t\t\t})\n\t\t}\n\t\tparents.get(parentId)!.moving.add(shape)\n\t}\n\n\tconst changes: TLShapePartial[] = []\n\n\tswitch (operation) {\n\t\tcase 'toBack': {\n\t\t\tparents.forEach(({ moving, children }) => reorderToBack(moving, children, changes))\n\t\t\tbreak\n\t\t}\n\t\tcase 'toFront': {\n\t\t\tparents.forEach(({ moving, children }) => reorderToFront(moving, children, changes))\n\t\t\tbreak\n\t\t}\n\t\tcase 'forward': {\n\t\t\tparents.forEach(({ moving, children }) =>\n\t\t\t\treorderForward(editor, moving, children, changes, opts)\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t\tcase 'backward': {\n\t\t\tparents.forEach(({ moving, children }) =>\n\t\t\t\treorderBackward(editor, moving, children, changes, opts)\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn changes\n}\n\n/**\n * Reorders the moving shapes to the back of the parent's children.\n *\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n */\nfunction reorderToBack(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet below: IndexKey | undefined\n\tlet above: IndexKey | undefined\n\n\t// Starting at the bottom of this parent's children...\n\tfor (let i = 0; i < len; i++) {\n\t\tconst shape = children[i]\n\n\t\tif (moving.has(shape)) {\n\t\t\t// If we've found a moving shape before we've found a non-moving shape,\n\t\t\t// then that shape is already at the back; we can remove it from the\n\t\t\t// moving set and mark it as the shape that will be below the moved shapes.\n\t\t\tbelow = shape.index\n\t\t\tmoving.delete(shape)\n\t\t} else {\n\t\t\t// The first non-moving shape we find will be above our moved shapes; we'll\n\t\t\t// put our moving shapes between it and the shape marked as below (if any).\n\t\t\tabove = shape.index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (moving.size === 0) {\n\t\t// If our moving set is empty, there's nothing to do; all of our shapes were\n\t\t// already at the back of the parent's children.\n\t\treturn\n\t} else {\n\t\t// Sort the moving shapes by their current index, then apply the new indices\n\t\tconst indices = getIndicesBetween(below, above, moving.size)\n\t\tchanges.push(\n\t\t\t...Array.from(moving.values())\n\t\t\t\t.sort(sortByIndex)\n\t\t\t\t.map((shape, i) => ({ ...shape, index: indices[i] }))\n\t\t)\n\t}\n}\n\n/**\n * Reorders the moving shapes to the front of the parent's children.\n *\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n */\nfunction reorderToFront(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet below: IndexKey | undefined\n\tlet above: IndexKey | undefined\n\n\t// Starting at the top of this parent's children...\n\tfor (let i = len - 1; i > -1; i--) {\n\t\tconst shape = children[i]\n\n\t\tif (moving.has(shape)) {\n\t\t\t// If we've found a moving shape before we've found a non-moving shape,\n\t\t\t// then that shape is already at the front; we can remove it from the\n\t\t\t// moving set and mark it as the shape that will be above the moved shapes.\n\t\t\tabove = shape.index\n\t\t\tmoving.delete(shape)\n\t\t} else {\n\t\t\t// The first non-moving shape we find will be below our moved shapes; we'll\n\t\t\t// put our moving shapes between it and the shape marked as above (if any).\n\t\t\tbelow = shape.index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (moving.size === 0) {\n\t\t// If our moving set is empty, there's nothing to do; all of our shapes were\n\t\t// already at the front of the parent's children.\n\t\treturn\n\t} else {\n\t\t// Sort the moving shapes by their current index, then apply the new indices\n\t\tconst indices = getIndicesBetween(below, above, moving.size)\n\t\tchanges.push(\n\t\t\t...Array.from(moving.values())\n\t\t\t\t.sort(sortByIndex)\n\t\t\t\t.map((shape, i) => ({ ...shape, index: indices[i] }))\n\t\t)\n\t}\n}\n\nfunction
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { TLParentId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndicesBetween, sortByIndex } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { Vec } from '../primitives/Vec'\nimport { polygonsIntersect } from '../primitives/intersect'\n\nexport function getReorderingShapesChanges(\n\teditor: Editor,\n\toperation: 'toBack' | 'toFront' | 'forward' | 'backward',\n\tids: TLShapeId[],\n\topts?: { considerAllShapes?: boolean }\n) {\n\tif (ids.length === 0) return []\n\n\t// From the ids that are moving, collect the parents, their children, and which of those children are moving\n\tconst parents = new Map<TLParentId, { moving: Set<TLShape>; children: TLShape[] }>()\n\n\tfor (const shape of compact(ids.map((id) => editor.getShape(id)))) {\n\t\tconst { parentId } = shape\n\t\tif (!parents.has(parentId)) {\n\t\t\tparents.set(parentId, {\n\t\t\t\tchildren: compact(\n\t\t\t\t\teditor.getSortedChildIdsForParent(parentId).map((id) => editor.getShape(id))\n\t\t\t\t),\n\t\t\t\tmoving: new Set(),\n\t\t\t})\n\t\t}\n\t\tparents.get(parentId)!.moving.add(shape)\n\t}\n\n\tconst changes: TLShapePartial[] = []\n\n\tswitch (operation) {\n\t\tcase 'toBack': {\n\t\t\tparents.forEach(({ moving, children }) => reorderToBack(moving, children, changes))\n\t\t\tbreak\n\t\t}\n\t\tcase 'toFront': {\n\t\t\tparents.forEach(({ moving, children }) => reorderToFront(moving, children, changes))\n\t\t\tbreak\n\t\t}\n\t\tcase 'forward': {\n\t\t\tparents.forEach(({ moving, children }) =>\n\t\t\t\treorderForward(editor, moving, children, changes, opts)\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t\tcase 'backward': {\n\t\t\tparents.forEach(({ moving, children }) =>\n\t\t\t\treorderBackward(editor, moving, children, changes, opts)\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn changes\n}\n\n/**\n * Reorders the moving shapes to the back of the parent's children.\n *\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n */\nfunction reorderToBack(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet below: IndexKey | undefined\n\tlet above: IndexKey | undefined\n\n\t// Starting at the bottom of this parent's children...\n\tfor (let i = 0; i < len; i++) {\n\t\tconst shape = children[i]\n\n\t\tif (moving.has(shape)) {\n\t\t\t// If we've found a moving shape before we've found a non-moving shape,\n\t\t\t// then that shape is already at the back; we can remove it from the\n\t\t\t// moving set and mark it as the shape that will be below the moved shapes.\n\t\t\tbelow = shape.index\n\t\t\tmoving.delete(shape)\n\t\t} else {\n\t\t\t// The first non-moving shape we find will be above our moved shapes; we'll\n\t\t\t// put our moving shapes between it and the shape marked as below (if any).\n\t\t\tabove = shape.index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (moving.size === 0) {\n\t\t// If our moving set is empty, there's nothing to do; all of our shapes were\n\t\t// already at the back of the parent's children.\n\t\treturn\n\t} else {\n\t\t// Sort the moving shapes by their current index, then apply the new indices\n\t\tconst indices = getIndicesBetween(below, above, moving.size)\n\t\tchanges.push(\n\t\t\t...Array.from(moving.values())\n\t\t\t\t.sort(sortByIndex)\n\t\t\t\t.map((shape, i) => ({ ...shape, index: indices[i] }))\n\t\t)\n\t}\n}\n\n/**\n * Reorders the moving shapes to the front of the parent's children.\n *\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n */\nfunction reorderToFront(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet below: IndexKey | undefined\n\tlet above: IndexKey | undefined\n\n\t// Starting at the top of this parent's children...\n\tfor (let i = len - 1; i > -1; i--) {\n\t\tconst shape = children[i]\n\n\t\tif (moving.has(shape)) {\n\t\t\t// If we've found a moving shape before we've found a non-moving shape,\n\t\t\t// then that shape is already at the front; we can remove it from the\n\t\t\t// moving set and mark it as the shape that will be above the moved shapes.\n\t\t\tabove = shape.index\n\t\t\tmoving.delete(shape)\n\t\t} else {\n\t\t\t// The first non-moving shape we find will be below our moved shapes; we'll\n\t\t\t// put our moving shapes between it and the shape marked as above (if any).\n\t\t\tbelow = shape.index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (moving.size === 0) {\n\t\t// If our moving set is empty, there's nothing to do; all of our shapes were\n\t\t// already at the front of the parent's children.\n\t\treturn\n\t} else {\n\t\t// Sort the moving shapes by their current index, then apply the new indices\n\t\tconst indices = getIndicesBetween(below, above, moving.size)\n\t\tchanges.push(\n\t\t\t...Array.from(moving.values())\n\t\t\t\t.sort(sortByIndex)\n\t\t\t\t.map((shape, i) => ({ ...shape, index: indices[i] }))\n\t\t)\n\t}\n}\n\nfunction getOverlapChecker(editor: Editor, moving: Set<TLShape>) {\n\tconst movingVertices = Array.from(moving)\n\t\t.map((shape) => {\n\t\t\tconst vertices = editor.getShapePageGeometry(shape).vertices\n\t\t\tif (!vertices) return null\n\t\t\treturn { shape, vertices }\n\t\t})\n\t\t.filter(Boolean) as { shape: TLShape; vertices: Vec[] }[]\n\n\tconst isOverlapping = (child: TLShape) => {\n\t\tconst vertices = editor.getShapePageGeometry(child).vertices\n\t\tif (!vertices) return false\n\t\treturn movingVertices.some((other) => {\n\t\t\treturn polygonsIntersect(other.vertices, vertices)\n\t\t})\n\t}\n\n\treturn isOverlapping\n}\n\n/**\n * Reorders the moving shapes forward in the parent's children.\n *\n * @param editor The editor\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n * @param opts The options\n */\nfunction reorderForward(\n\teditor: Editor,\n\tmoving: Set<TLShape>,\n\tchildren: TLShape[],\n\tchanges: TLShapePartial[],\n\topts?: { considerAllShapes?: boolean }\n) {\n\tconst isOverlapping = getOverlapChecker(editor, moving)\n\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet state = { name: 'skipping' } as\n\t\t| { name: 'skipping' }\n\t\t| { name: 'selecting'; selectIndex: number }\n\n\t// Starting at the bottom of this parent's children...\n\tfor (let i = 0; i < len; i++) {\n\t\tconst isMoving = moving.has(children[i])\n\n\t\tswitch (state.name) {\n\t\t\tcase 'skipping': {\n\t\t\t\tif (!isMoving) continue\n\t\t\t\t// If we find a moving shape while skipping, start selecting\n\t\t\t\tstate = { name: 'selecting', selectIndex: i }\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'selecting': {\n\t\t\t\tif (isMoving) continue\n\t\t\t\tif (!opts?.considerAllShapes && !isOverlapping(children[i])) continue\n\t\t\t\t// if we find a non-moving and overlapping shape while selecting, move all selected\n\t\t\t\t// shapes in front of the not moving shape; and start skipping\n\t\t\t\tconst { selectIndex } = state\n\t\t\t\tgetIndicesBetween(children[i].index, children[i + 1]?.index, i - selectIndex).forEach(\n\t\t\t\t\t(index, k) => {\n\t\t\t\t\t\tconst child = children[selectIndex + k]\n\t\t\t\t\t\t// If the shape is not moving (therefore also not overlapping), skip it\n\t\t\t\t\t\tif (!moving.has(child)) return\n\t\t\t\t\t\tchanges.push({ ...child, index })\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t\tstate = { name: 'skipping' }\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Reorders the moving shapes backward in the parent's children.\n *\n * @param editor The editor\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n * @param opts The options\n */\nfunction reorderBackward(\n\teditor: Editor,\n\tmoving: Set<TLShape>,\n\tchildren: TLShape[],\n\tchanges: TLShapePartial[],\n\topts?: { considerAllShapes?: boolean }\n) {\n\tconst isOverlapping = getOverlapChecker(editor, moving)\n\n\tconst len = children.length\n\n\tif (moving.size === len) return\n\n\tlet state = { name: 'skipping' } as\n\t\t| { name: 'skipping' }\n\t\t| { name: 'selecting'; selectIndex: number }\n\n\t// Starting at the top of this parent's children...\n\tfor (let i = len - 1; i > -1; i--) {\n\t\tconst isMoving = moving.has(children[i])\n\n\t\tswitch (state.name) {\n\t\t\tcase 'skipping': {\n\t\t\t\tif (!isMoving) continue\n\t\t\t\t// If we find a moving shape while skipping, start selecting\n\t\t\t\tstate = { name: 'selecting', selectIndex: i }\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'selecting': {\n\t\t\t\tif (isMoving) continue\n\t\t\t\tif (!opts?.considerAllShapes && !isOverlapping(children[i])) continue\n\t\t\t\t// if we find a non-moving and overlapping shape while selecting, move all selected\n\t\t\t\t// shapes in behind of the not moving shape; and start skipping\n\t\t\t\tgetIndicesBetween(children[i - 1]?.index, children[i].index, state.selectIndex - i).forEach(\n\t\t\t\t\t(index, k) => {\n\t\t\t\t\t\tconst child = children[i + k + 1]\n\t\t\t\t\t\t// If the shape is not moving (therefore also not overlapping), skip it\n\t\t\t\t\t\tif (!moving.has(child)) return\n\t\t\t\t\t\tchanges.push({ ...child, index })\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t\tstate = { name: 'skipping' }\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "3.12.0-canary.
|
|
1
|
+
const version = "3.12.0-canary.47eb43f692c5";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2024-09-13T14:36:29.063Z",
|
|
4
|
-
minor: "2025-04-
|
|
5
|
-
patch: "2025-04-
|
|
4
|
+
minor: "2025-04-10T14:01:42.825Z",
|
|
5
|
+
patch: "2025-04-10T14:01:42.825Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
package/dist-esm/version.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.12.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.12.0-canary.47eb43f692c5'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-04-10T14:01:42.825Z',\n\tpatch: '2025-04-10T14:01:42.825Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "A tiny little drawing app (editor).",
|
|
4
|
-
"version": "3.12.0-canary.
|
|
4
|
+
"version": "3.12.0-canary.47eb43f692c5",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@tiptap/core": "^2.9.1",
|
|
49
49
|
"@tiptap/pm": "^2.9.1",
|
|
50
50
|
"@tiptap/react": "^2.9.1",
|
|
51
|
-
"@tldraw/state": "3.12.0-canary.
|
|
52
|
-
"@tldraw/state-react": "3.12.0-canary.
|
|
53
|
-
"@tldraw/store": "3.12.0-canary.
|
|
54
|
-
"@tldraw/tlschema": "3.12.0-canary.
|
|
55
|
-
"@tldraw/utils": "3.12.0-canary.
|
|
56
|
-
"@tldraw/validate": "3.12.0-canary.
|
|
51
|
+
"@tldraw/state": "3.12.0-canary.47eb43f692c5",
|
|
52
|
+
"@tldraw/state-react": "3.12.0-canary.47eb43f692c5",
|
|
53
|
+
"@tldraw/store": "3.12.0-canary.47eb43f692c5",
|
|
54
|
+
"@tldraw/tlschema": "3.12.0-canary.47eb43f692c5",
|
|
55
|
+
"@tldraw/utils": "3.12.0-canary.47eb43f692c5",
|
|
56
|
+
"@tldraw/validate": "3.12.0-canary.47eb43f692c5",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
package/src/index.ts
CHANGED
|
@@ -293,7 +293,11 @@ export {
|
|
|
293
293
|
type TLSvgExportOptions,
|
|
294
294
|
type TLSvgOptions,
|
|
295
295
|
} from './lib/editor/types/misc-types'
|
|
296
|
-
export {
|
|
296
|
+
export {
|
|
297
|
+
type TLAdjacentDirection,
|
|
298
|
+
type TLResizeHandle,
|
|
299
|
+
type TLSelectionHandle,
|
|
300
|
+
} from './lib/editor/types/selection-types'
|
|
297
301
|
export { getSvgAsImage } from './lib/exports/getSvgAsImage'
|
|
298
302
|
export { tlenv } from './lib/globals/environment'
|
|
299
303
|
export { tlmenus } from './lib/globals/menus'
|
|
@@ -360,7 +364,12 @@ export { CubicBezier2d } from './lib/primitives/geometry/CubicBezier2d'
|
|
|
360
364
|
export { CubicSpline2d } from './lib/primitives/geometry/CubicSpline2d'
|
|
361
365
|
export { Edge2d } from './lib/primitives/geometry/Edge2d'
|
|
362
366
|
export { Ellipse2d } from './lib/primitives/geometry/Ellipse2d'
|
|
363
|
-
export {
|
|
367
|
+
export {
|
|
368
|
+
Geometry2d,
|
|
369
|
+
Geometry2dFilters,
|
|
370
|
+
TransformedGeometry2d,
|
|
371
|
+
type Geometry2dOptions,
|
|
372
|
+
} from './lib/primitives/geometry/Geometry2d'
|
|
364
373
|
export { Group2d } from './lib/primitives/geometry/Group2d'
|
|
365
374
|
export { Point2d } from './lib/primitives/geometry/Point2d'
|
|
366
375
|
export { Polygon2d } from './lib/primitives/geometry/Polygon2d'
|
package/src/lib/TldrawEditor.tsx
CHANGED
|
@@ -284,6 +284,7 @@ export const TldrawEditor = memo(function TldrawEditor({
|
|
|
284
284
|
className={classNames(`${TL_CONTAINER_CLASS} tl-theme__light`, className)}
|
|
285
285
|
onPointerDown={stopEventPropagation}
|
|
286
286
|
tabIndex={-1}
|
|
287
|
+
role="application"
|
|
287
288
|
>
|
|
288
289
|
<OptionalErrorBoundary
|
|
289
290
|
fallback={ErrorFallback}
|
|
@@ -114,13 +114,13 @@ export const GeometryDebuggingView = track(function GeometryDebuggingView({
|
|
|
114
114
|
function GeometryStroke({ geometry }: { geometry: Geometry2d }) {
|
|
115
115
|
if (geometry instanceof Group2d) {
|
|
116
116
|
return (
|
|
117
|
-
|
|
117
|
+
<g stroke={geometry.debugColor}>
|
|
118
118
|
{[...geometry.children, ...geometry.ignoredChildren].map((child, i) => (
|
|
119
119
|
<GeometryStroke geometry={child} key={i} />
|
|
120
120
|
))}
|
|
121
|
-
|
|
121
|
+
</g>
|
|
122
122
|
)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
return <path d={geometry.toSimpleSvgPath()} />
|
|
125
|
+
return <path d={geometry.toSimpleSvgPath()} stroke={geometry.debugColor} />
|
|
126
126
|
}
|
|
@@ -361,7 +361,12 @@ function HandleWrapper({
|
|
|
361
361
|
if (!Handle) return null
|
|
362
362
|
|
|
363
363
|
return (
|
|
364
|
-
<g
|
|
364
|
+
<g
|
|
365
|
+
role="button"
|
|
366
|
+
aria-label="handle"
|
|
367
|
+
transform={`translate(${handle.x}, ${handle.y})`}
|
|
368
|
+
{...events}
|
|
369
|
+
>
|
|
365
370
|
<Handle shapeId={shapeId} handle={handle} zoom={zoom} isCoarse={isCoarse} />
|
|
366
371
|
</g>
|
|
367
372
|
)
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -87,6 +87,7 @@ import {
|
|
|
87
87
|
last,
|
|
88
88
|
lerp,
|
|
89
89
|
maxBy,
|
|
90
|
+
minBy,
|
|
90
91
|
sortById,
|
|
91
92
|
sortByIndex,
|
|
92
93
|
structuredClone,
|
|
@@ -176,7 +177,7 @@ import {
|
|
|
176
177
|
TLImageExportOptions,
|
|
177
178
|
TLSvgExportOptions,
|
|
178
179
|
} from './types/misc-types'
|
|
179
|
-
import { TLResizeHandle } from './types/selection-types'
|
|
180
|
+
import { TLAdjacentDirection, TLResizeHandle } from './types/selection-types'
|
|
180
181
|
|
|
181
182
|
/** @public */
|
|
182
183
|
export type TLResizeShapeOptions = Partial<{
|
|
@@ -1813,6 +1814,195 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1813
1814
|
return this
|
|
1814
1815
|
}
|
|
1815
1816
|
|
|
1817
|
+
selectAdjacentShape(direction: TLAdjacentDirection) {
|
|
1818
|
+
const readingOrderShapes = this.getCurrentPageShapesInReadingOrder()
|
|
1819
|
+
const selectedShapeIds = this.getSelectedShapeIds()
|
|
1820
|
+
const currentShapeId: TLShapeId | undefined =
|
|
1821
|
+
selectedShapeIds.length === 1
|
|
1822
|
+
? selectedShapeIds[0]
|
|
1823
|
+
: readingOrderShapes.find((shape) => selectedShapeIds.includes(shape.id))?.id
|
|
1824
|
+
|
|
1825
|
+
let adjacentShapeId: TLShapeId
|
|
1826
|
+
if (direction === 'next' || direction === 'prev') {
|
|
1827
|
+
const shapeIds = readingOrderShapes.map((shape) => shape.id)
|
|
1828
|
+
|
|
1829
|
+
const currentIndex = currentShapeId ? shapeIds.indexOf(currentShapeId) : -1
|
|
1830
|
+
const adjacentIndex =
|
|
1831
|
+
(currentIndex + (direction === 'next' ? 1 : -1) + shapeIds.length) % shapeIds.length
|
|
1832
|
+
adjacentShapeId = shapeIds[adjacentIndex]
|
|
1833
|
+
} else {
|
|
1834
|
+
if (!currentShapeId) return
|
|
1835
|
+
adjacentShapeId = this.getNearestAdjacentShape(currentShapeId, direction)
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
const shape = this.getShape(adjacentShapeId)
|
|
1839
|
+
if (!shape) return
|
|
1840
|
+
|
|
1841
|
+
this.setSelectedShapes([shape.id])
|
|
1842
|
+
this.zoomToSelectionIfOffscreen(256, {
|
|
1843
|
+
animation: {
|
|
1844
|
+
duration: this.options.animationMediumMs,
|
|
1845
|
+
},
|
|
1846
|
+
inset: 0,
|
|
1847
|
+
})
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/**
|
|
1851
|
+
* Generates a reading order for shapes based on rows grouping.
|
|
1852
|
+
* Tries to keep a natural reading order (left-to-right, top-to-bottom).
|
|
1853
|
+
*
|
|
1854
|
+
* @public
|
|
1855
|
+
*/
|
|
1856
|
+
@computed getCurrentPageShapesInReadingOrder(): TLShape[] {
|
|
1857
|
+
const SHALLOW_ANGLE = 20
|
|
1858
|
+
const ROW_THRESHOLD = 100
|
|
1859
|
+
|
|
1860
|
+
const shapes = this.getCurrentPageShapes()
|
|
1861
|
+
const tabbableShapes = shapes.filter((shape) => this.getShapeUtil(shape).canTabTo(shape))
|
|
1862
|
+
|
|
1863
|
+
if (tabbableShapes.length <= 1) return tabbableShapes
|
|
1864
|
+
|
|
1865
|
+
const shapesWithCenters = tabbableShapes.map((shape) => ({
|
|
1866
|
+
shape,
|
|
1867
|
+
center: this.getShapePageBounds(shape)!.center,
|
|
1868
|
+
}))
|
|
1869
|
+
shapesWithCenters.sort((a, b) => a.center.y - b.center.y)
|
|
1870
|
+
|
|
1871
|
+
const rows: Array<typeof shapesWithCenters> = []
|
|
1872
|
+
|
|
1873
|
+
// First, group shapes into rows based on y-coordinates.
|
|
1874
|
+
for (const shapeWithCenter of shapesWithCenters) {
|
|
1875
|
+
let rowIndex = -1
|
|
1876
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
1877
|
+
const row = rows[i]
|
|
1878
|
+
const lastShapeInRow = row[row.length - 1]
|
|
1879
|
+
|
|
1880
|
+
// If the shape is close enough vertically to the last shape in this row.
|
|
1881
|
+
if (Math.abs(shapeWithCenter.center.y - lastShapeInRow.center.y) < ROW_THRESHOLD) {
|
|
1882
|
+
rowIndex = i
|
|
1883
|
+
break
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// If no suitable row found, create a new row.
|
|
1888
|
+
if (rowIndex === -1) {
|
|
1889
|
+
rows.push([shapeWithCenter])
|
|
1890
|
+
} else {
|
|
1891
|
+
rows[rowIndex].push(shapeWithCenter)
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// Then, sort each row by x-coordinate (left-to-right).
|
|
1896
|
+
for (const row of rows) {
|
|
1897
|
+
row.sort((a, b) => a.center.x - b.center.x)
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// Finally, apply angle/distance weight adjustments within rows for closely positioned shapes.
|
|
1901
|
+
for (const row of rows) {
|
|
1902
|
+
if (row.length <= 2) continue
|
|
1903
|
+
|
|
1904
|
+
for (let i = 0; i < row.length - 2; i++) {
|
|
1905
|
+
const currentShape = row[i]
|
|
1906
|
+
const nextShape = row[i + 1]
|
|
1907
|
+
const nextNextShape = row[i + 2]
|
|
1908
|
+
|
|
1909
|
+
// Only consider adjustment if the next two shapes are relatively close to each other.
|
|
1910
|
+
const dist1 = Vec.Dist2(currentShape.center, nextShape.center)
|
|
1911
|
+
const dist2 = Vec.Dist2(currentShape.center, nextNextShape.center)
|
|
1912
|
+
|
|
1913
|
+
// Check if the 2nd shape is actually closer to the current shape.
|
|
1914
|
+
if (dist2 < dist1 * 0.9) {
|
|
1915
|
+
// Check if it's a shallow enough angle.
|
|
1916
|
+
const angle = Math.abs(
|
|
1917
|
+
Vec.Angle(currentShape.center, nextNextShape.center) * (180 / Math.PI)
|
|
1918
|
+
)
|
|
1919
|
+
if (angle <= SHALLOW_ANGLE) {
|
|
1920
|
+
// Swap swap.
|
|
1921
|
+
;[row[i + 1], row[i + 2]] = [row[i + 2], row[i + 1]]
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
return rows.flat().map((item) => item.shape)
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* Find the nearest adjacent shape in a specific direction.
|
|
1932
|
+
*
|
|
1933
|
+
* @public
|
|
1934
|
+
*/
|
|
1935
|
+
getNearestAdjacentShape(
|
|
1936
|
+
currentShapeId: TLShapeId,
|
|
1937
|
+
direction: 'left' | 'right' | 'up' | 'down'
|
|
1938
|
+
): TLShapeId {
|
|
1939
|
+
const directionToAngle = { right: 0, left: 180, down: 90, up: 270 }
|
|
1940
|
+
const currentShape = this.getShape(currentShapeId)
|
|
1941
|
+
if (!currentShape) return currentShapeId
|
|
1942
|
+
|
|
1943
|
+
const shapes = this.getCurrentPageShapes()
|
|
1944
|
+
const tabbableShapes = shapes.filter(
|
|
1945
|
+
(shape) => this.getShapeUtil(shape).canTabTo(shape) && shape.id !== currentShapeId
|
|
1946
|
+
)
|
|
1947
|
+
if (!tabbableShapes.length) return currentShapeId
|
|
1948
|
+
|
|
1949
|
+
const currentCenter = this.getShapePageBounds(currentShape)!.center
|
|
1950
|
+
const shapesWithCenters = tabbableShapes.map((shape) => ({
|
|
1951
|
+
shape,
|
|
1952
|
+
center: this.getShapePageBounds(shape)!.center,
|
|
1953
|
+
}))
|
|
1954
|
+
|
|
1955
|
+
// Filter shapes that are in the same direction.
|
|
1956
|
+
const shapesInDirection = shapesWithCenters.filter(({ center }) => {
|
|
1957
|
+
const isRight = center.x > currentCenter.x
|
|
1958
|
+
const isDown = center.y > currentCenter.y
|
|
1959
|
+
const xDist = center.x - currentCenter.x
|
|
1960
|
+
const yDist = center.y - currentCenter.y
|
|
1961
|
+
const isInXDirection = Math.abs(yDist) < Math.abs(xDist) * 2
|
|
1962
|
+
const isInYDirection = Math.abs(xDist) < Math.abs(yDist) * 2
|
|
1963
|
+
if (direction === 'left' || direction === 'right') {
|
|
1964
|
+
return isInXDirection && (direction === 'right' ? isRight : !isRight)
|
|
1965
|
+
}
|
|
1966
|
+
if (direction === 'up' || direction === 'down') {
|
|
1967
|
+
return isInYDirection && (direction === 'down' ? isDown : !isDown)
|
|
1968
|
+
}
|
|
1969
|
+
})
|
|
1970
|
+
|
|
1971
|
+
if (shapesInDirection.length === 0) return currentShapeId
|
|
1972
|
+
|
|
1973
|
+
// Ok, now score that subset of shapes.
|
|
1974
|
+
const lowestScoringShape = minBy(shapesInDirection, ({ center }) => {
|
|
1975
|
+
// Distance is the primary weighting factor.
|
|
1976
|
+
const distance = Vec.Dist2(currentCenter, center)
|
|
1977
|
+
|
|
1978
|
+
// Distance along the primary axis.
|
|
1979
|
+
const dirProp = ['left', 'right'].includes(direction) ? 'x' : 'y'
|
|
1980
|
+
const directionalDistance = Math.abs(center[dirProp] - currentCenter[dirProp])
|
|
1981
|
+
|
|
1982
|
+
// Distance off the perpendicular to the primary axis.
|
|
1983
|
+
const offProp = ['left', 'right'].includes(direction) ? 'y' : 'x'
|
|
1984
|
+
const offAxisDeviation = Math.abs(center[offProp] - currentCenter[offProp])
|
|
1985
|
+
|
|
1986
|
+
// Angle in degrees
|
|
1987
|
+
const angle = Math.abs(Vec.Angle(currentCenter, center) * (180 / Math.PI))
|
|
1988
|
+
const angleDeviation = Math.abs(angle - directionToAngle[direction])
|
|
1989
|
+
|
|
1990
|
+
// Calculate final score (lower is better).
|
|
1991
|
+
// Weight factors to prioritize:
|
|
1992
|
+
// 1. Shapes directly in line with the current shape
|
|
1993
|
+
// 2. Shapes closer to the current shape
|
|
1994
|
+
// 3. Shapes with less angular deviation from the primary direction
|
|
1995
|
+
return (
|
|
1996
|
+
distance * 1.0 + // Base distance
|
|
1997
|
+
offAxisDeviation * 2.0 + // Heavy penalty for off-axis deviation
|
|
1998
|
+
(distance - directionalDistance) * 1.5 + // Penalty for diagonal distance
|
|
1999
|
+
angleDeviation * 0.5
|
|
2000
|
+
) // Slight penalty for angular deviation
|
|
2001
|
+
})
|
|
2002
|
+
|
|
2003
|
+
return lowestScoringShape!.shape.id
|
|
2004
|
+
}
|
|
2005
|
+
|
|
1816
2006
|
/**
|
|
1817
2007
|
* Clear the selection.
|
|
1818
2008
|
*
|
|
@@ -3054,6 +3244,34 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3054
3244
|
return this
|
|
3055
3245
|
}
|
|
3056
3246
|
|
|
3247
|
+
/**
|
|
3248
|
+
* Zoom the camera to the current selection if offscreen.
|
|
3249
|
+
*
|
|
3250
|
+
* @public
|
|
3251
|
+
*/
|
|
3252
|
+
zoomToSelectionIfOffscreen(
|
|
3253
|
+
padding = 16,
|
|
3254
|
+
opts?: { targetZoom?: number; inset?: number } & TLCameraMoveOptions
|
|
3255
|
+
) {
|
|
3256
|
+
const selectionPageBounds = this.getSelectionPageBounds()
|
|
3257
|
+
const viewportPageBounds = this.getViewportPageBounds()
|
|
3258
|
+
if (selectionPageBounds && !viewportPageBounds.contains(selectionPageBounds)) {
|
|
3259
|
+
const eb = selectionPageBounds
|
|
3260
|
+
.clone()
|
|
3261
|
+
// Expand the bounds by the padding
|
|
3262
|
+
.expandBy(padding / this.getZoomLevel())
|
|
3263
|
+
// then expand the bounds to include the viewport bounds
|
|
3264
|
+
.expand(viewportPageBounds)
|
|
3265
|
+
|
|
3266
|
+
// then use the difference between the centers to calculate the offset
|
|
3267
|
+
const nextBounds = viewportPageBounds.clone().translate({
|
|
3268
|
+
x: (eb.center.x - viewportPageBounds.center.x) * 2,
|
|
3269
|
+
y: (eb.center.y - viewportPageBounds.center.y) * 2,
|
|
3270
|
+
})
|
|
3271
|
+
this.zoomToBounds(nextBounds, opts)
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3057
3275
|
/**
|
|
3058
3276
|
* Zoom the camera to fit a bounding box (in the current page space).
|
|
3059
3277
|
*
|
|
@@ -4330,7 +4548,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4330
4548
|
private _shapeGeometryCaches: Record<string, ComputedCache<Geometry2d, TLShape>> = {}
|
|
4331
4549
|
|
|
4332
4550
|
/**
|
|
4333
|
-
* Get the geometry of a shape.
|
|
4551
|
+
* Get the geometry of a shape in shape-space.
|
|
4334
4552
|
*
|
|
4335
4553
|
* @example
|
|
4336
4554
|
* ```ts
|
|
@@ -4361,6 +4579,44 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4361
4579
|
)! as T
|
|
4362
4580
|
}
|
|
4363
4581
|
|
|
4582
|
+
private _shapePageGeometryCaches: Record<string, ComputedCache<Geometry2d, TLShape>> = {}
|
|
4583
|
+
|
|
4584
|
+
/**
|
|
4585
|
+
* Get the geometry of a shape in page-space.
|
|
4586
|
+
*
|
|
4587
|
+
* @example
|
|
4588
|
+
* ```ts
|
|
4589
|
+
* editor.getShapePageGeometry(myShape)
|
|
4590
|
+
* editor.getShapePageGeometry(myShapeId)
|
|
4591
|
+
* editor.getShapePageGeometry(myShapeId, { context: "arrow" })
|
|
4592
|
+
* ```
|
|
4593
|
+
*
|
|
4594
|
+
* @param shape - The shape (or shape id) to get the geometry for.
|
|
4595
|
+
* @param opts - Additional options about the request for geometry. Passed to {@link ShapeUtil.getGeometry}.
|
|
4596
|
+
*
|
|
4597
|
+
* @public
|
|
4598
|
+
*/
|
|
4599
|
+
getShapePageGeometry<T extends Geometry2d>(shape: TLShape | TLShapeId, opts?: TLGeometryOpts): T {
|
|
4600
|
+
const context = opts?.context ?? 'none'
|
|
4601
|
+
if (!this._shapePageGeometryCaches[context]) {
|
|
4602
|
+
this._shapePageGeometryCaches[context] = this.store.createComputedCache(
|
|
4603
|
+
'bounds',
|
|
4604
|
+
(shape) => {
|
|
4605
|
+
const geometry = this.getShapeGeometry(shape.id, opts)
|
|
4606
|
+
const pageTransform = this.getShapePageTransform(shape.id)
|
|
4607
|
+
return geometry.transform(pageTransform)
|
|
4608
|
+
},
|
|
4609
|
+
{
|
|
4610
|
+
// we only depend directly on the shape id, and changing geometry/transform will update us anyway
|
|
4611
|
+
areRecordsEqual: () => true,
|
|
4612
|
+
}
|
|
4613
|
+
)
|
|
4614
|
+
}
|
|
4615
|
+
return this._shapePageGeometryCaches[context].get(
|
|
4616
|
+
typeof shape === 'string' ? shape : shape.id
|
|
4617
|
+
)! as T
|
|
4618
|
+
}
|
|
4619
|
+
|
|
4364
4620
|
/** @internal */
|
|
4365
4621
|
@computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
|
|
4366
4622
|
return this.store.createComputedCache('handles', (shape) => {
|
|
@@ -4467,15 +4723,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4467
4723
|
/** @internal */
|
|
4468
4724
|
@computed private _getShapePageBoundsCache(): ComputedCache<Box, TLShape> {
|
|
4469
4725
|
return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
if (!pageTransform) return new Box()
|
|
4473
|
-
|
|
4474
|
-
const result = Box.FromPoints(
|
|
4475
|
-
Mat.applyToPoints(pageTransform, this.getShapeGeometry(shape).vertices)
|
|
4476
|
-
)
|
|
4477
|
-
|
|
4478
|
-
return result
|
|
4726
|
+
return this.getShapePageGeometry(shape).bounds
|
|
4479
4727
|
})
|
|
4480
4728
|
}
|
|
4481
4729
|
|
|
@@ -4549,11 +4797,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4549
4797
|
if (frameAncestors.length === 0) return undefined
|
|
4550
4798
|
|
|
4551
4799
|
const pageMask = frameAncestors
|
|
4552
|
-
.map<Vec[] | undefined>(
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
.
|
|
4556
|
-
.applyToPoints(this.getShapeGeometry(s).vertices)
|
|
4800
|
+
.map<Vec[] | undefined>(
|
|
4801
|
+
(s) =>
|
|
4802
|
+
// Apply the frame transform to the frame outline to get the frame outline in the current page space
|
|
4803
|
+
this.getShapePageGeometry(s.id).vertices
|
|
4557
4804
|
)
|
|
4558
4805
|
.reduce((acc, b) => {
|
|
4559
4806
|
if (!(b && acc)) return undefined
|
|
@@ -58,7 +58,7 @@ export class FocusManager {
|
|
|
58
58
|
|
|
59
59
|
private handleKeyDown(keyEvent: KeyboardEvent) {
|
|
60
60
|
const container = this.editor.getContainer()
|
|
61
|
-
if (
|
|
61
|
+
if (['Tab', 'ArrowUp', 'ArrowDown'].includes(keyEvent.key)) {
|
|
62
62
|
container.classList.remove('tl-container__no-focus-ring')
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -193,6 +193,16 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
193
193
|
return true
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Whether the shape can be tabbed to.
|
|
198
|
+
*
|
|
199
|
+
* @param shape - The shape.
|
|
200
|
+
* @public
|
|
201
|
+
*/
|
|
202
|
+
canTabTo(_shape: Shape): boolean {
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
|
|
196
206
|
/**
|
|
197
207
|
* Whether the shape can be scrolled while editing.
|
|
198
208
|
*
|
|
@@ -438,6 +448,10 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
438
448
|
return undefined
|
|
439
449
|
}
|
|
440
450
|
|
|
451
|
+
getAriaDescriptor(_shape: Shape): string | undefined {
|
|
452
|
+
return undefined
|
|
453
|
+
}
|
|
454
|
+
|
|
441
455
|
// Events
|
|
442
456
|
|
|
443
457
|
/**
|
|
@@ -2,8 +2,6 @@ import { TLGroupShape, groupShapeMigrations, groupShapeProps } from '@tldraw/tls
|
|
|
2
2
|
import { SVGContainer } from '../../../components/SVGContainer'
|
|
3
3
|
import { Geometry2d } from '../../../primitives/geometry/Geometry2d'
|
|
4
4
|
import { Group2d } from '../../../primitives/geometry/Group2d'
|
|
5
|
-
import { Polygon2d } from '../../../primitives/geometry/Polygon2d'
|
|
6
|
-
import { Polyline2d } from '../../../primitives/geometry/Polyline2d'
|
|
7
5
|
import { Rectangle2d } from '../../../primitives/geometry/Rectangle2d'
|
|
8
6
|
import { ShapeUtil } from '../ShapeUtil'
|
|
9
7
|
import { DashedOutlineBox } from './DashedOutlineBox'
|
|
@@ -14,6 +12,10 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
|
|
|
14
12
|
static override props = groupShapeProps
|
|
15
13
|
static override migrations = groupShapeMigrations
|
|
16
14
|
|
|
15
|
+
override canTabTo() {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
17
19
|
override hideSelectionBoundsFg() {
|
|
18
20
|
return true
|
|
19
21
|
}
|
|
@@ -35,19 +37,9 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
|
|
|
35
37
|
return new Group2d({
|
|
36
38
|
children: children.map((childId) => {
|
|
37
39
|
const shape = this.editor.getShape(childId)!
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (geometry.isClosed) {
|
|
42
|
-
return new Polygon2d({
|
|
43
|
-
points,
|
|
44
|
-
isFilled: true,
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return new Polyline2d({
|
|
49
|
-
points,
|
|
50
|
-
})
|
|
40
|
+
return this.editor
|
|
41
|
+
.getShapeGeometry(childId)
|
|
42
|
+
.transform(this.editor.getShapeLocalTransform(shape)!)
|
|
51
43
|
}),
|
|
52
44
|
})
|
|
53
45
|
}
|
|
@@ -104,6 +104,7 @@ export function useDocumentEvents() {
|
|
|
104
104
|
|
|
105
105
|
if ((e as any).isKilled) return
|
|
106
106
|
;(e as any).isKilled = true
|
|
107
|
+
const hasSelectedShapes = !!editor.getSelectedShapeIds().length
|
|
107
108
|
|
|
108
109
|
switch (e.key) {
|
|
109
110
|
case '=':
|
|
@@ -124,6 +125,23 @@ export function useDocumentEvents() {
|
|
|
124
125
|
if (areShortcutsDisabled(editor)) {
|
|
125
126
|
return
|
|
126
127
|
}
|
|
128
|
+
if (hasSelectedShapes) {
|
|
129
|
+
// This is used in tandem with shape navigation.
|
|
130
|
+
preventDefault(e)
|
|
131
|
+
}
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
case 'ArrowLeft':
|
|
135
|
+
case 'ArrowRight':
|
|
136
|
+
case 'ArrowUp':
|
|
137
|
+
case 'ArrowDown': {
|
|
138
|
+
if (areShortcutsDisabled(editor)) {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (hasSelectedShapes && (e.metaKey || e.ctrlKey)) {
|
|
142
|
+
// This is used in tandem with shape navigation.
|
|
143
|
+
preventDefault(e)
|
|
144
|
+
}
|
|
127
145
|
break
|
|
128
146
|
}
|
|
129
147
|
case ',': {
|