@tldraw/editor 3.12.0-canary.a6c2905c622f → 3.12.0-canary.a880bf3ec9cb
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 +158 -17
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +5 -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/Shape.js +10 -14
- package/dist-cjs/lib/components/Shape.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 +224 -32
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +1 -1
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.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/managers/FontManager.js +1 -1
- package/dist-cjs/lib/editor/managers/FontManager.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/tools/StateNode.js +4 -1
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/selection-types.js.map +1 -1
- package/dist-cjs/lib/exports/StyleEmbedder.js +19 -5
- package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
- package/dist-cjs/lib/exports/cssRules.js +127 -0
- package/dist-cjs/lib/exports/cssRules.js.map +7 -0
- package/dist-cjs/lib/exports/parseCss.js +0 -69
- package/dist-cjs/lib/exports/parseCss.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +13 -8
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +3 -3
- package/dist-cjs/lib/hooks/useDocumentEvents.js +16 -0
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useGestureEvents.js +12 -6
- package/dist-cjs/lib/hooks/useGestureEvents.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 +53 -10
- 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/debug-flags.js +2 -1
- package/dist-cjs/lib/utils/debug-flags.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 +158 -17
- package/dist-esm/index.mjs +8 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +5 -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/Shape.mjs +11 -15
- package/dist-esm/lib/components/Shape.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 +225 -32
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +1 -1
- package/dist-esm/lib/editor/derivations/notVisibleShapes.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/managers/FontManager.mjs +1 -1
- package/dist-esm/lib/editor/managers/FontManager.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/editor/tools/StateNode.mjs +4 -1
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/exports/StyleEmbedder.mjs +21 -12
- package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
- package/dist-esm/lib/exports/cssRules.mjs +107 -0
- package/dist-esm/lib/exports/cssRules.mjs.map +7 -0
- package/dist-esm/lib/exports/parseCss.mjs +0 -69
- package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +13 -8
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +3 -3
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +16 -0
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs +12 -6
- package/dist-esm/lib/hooks/useGestureEvents.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 +54 -11
- 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/debug-flags.mjs +2 -1
- package/dist-esm/lib/utils/debug-flags.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/editor.css +51 -30
- package/package.json +7 -7
- package/src/index.ts +11 -2
- package/src/lib/TldrawEditor.tsx +30 -3
- package/src/lib/components/GeometryDebuggingView.tsx +3 -3
- package/src/lib/components/Shape.tsx +15 -19
- package/src/lib/components/default-components/DefaultCanvas.tsx +6 -1
- package/src/lib/editor/Editor.ts +334 -39
- package/src/lib/editor/derivations/notVisibleShapes.ts +1 -1
- package/src/lib/editor/managers/FocusManager.ts +1 -1
- package/src/lib/editor/managers/FontManager.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/tools/StateNode.ts +6 -1
- package/src/lib/editor/types/selection-types.ts +3 -0
- package/src/lib/exports/StyleEmbedder.ts +25 -15
- package/src/lib/exports/cssRules.ts +126 -0
- package/src/lib/exports/parseCss.ts +0 -79
- package/src/lib/hooks/useCanvasEvents.ts +15 -8
- package/src/lib/hooks/useDocumentEvents.ts +18 -0
- package/src/lib/hooks/useGestureEvents.ts +12 -6
- package/src/lib/license/Watermark.tsx +18 -29
- package/src/lib/primitives/geometry/Geometry2d.ts +196 -16
- package/src/lib/primitives/geometry/Group2d.ts +75 -12
- package/src/lib/primitives/intersect.ts +41 -0
- package/src/lib/utils/debug-flags.ts +1 -0
- package/src/lib/utils/reorderShapes.ts +2 -9
- package/src/version.ts +3 -3
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<{
|
|
@@ -241,10 +242,33 @@ export interface TLEditorOptions {
|
|
|
241
242
|
fontAssetUrls?: { [key: string]: string | undefined }
|
|
242
243
|
/**
|
|
243
244
|
* A predicate that should return true if the given shape should be hidden.
|
|
245
|
+
*
|
|
246
|
+
* @deprecated Use {@link Editor#getShapeVisibility} instead.
|
|
247
|
+
*
|
|
244
248
|
* @param shape - The shape to check.
|
|
245
249
|
* @param editor - The editor instance.
|
|
246
250
|
*/
|
|
247
251
|
isShapeHidden?(shape: TLShape, editor: Editor): boolean
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Provides a way to hide shapes.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```ts
|
|
258
|
+
* getShapeVisibility={(shape, editor) => shape.meta.hidden ? 'hidden' : 'inherit'}
|
|
259
|
+
* ```
|
|
260
|
+
*
|
|
261
|
+
* - `'inherit' | undefined` - (default) The shape will be visible unless its parent is hidden.
|
|
262
|
+
* - `'hidden'` - The shape will be hidden.
|
|
263
|
+
* - `'visible'` - The shape will be visible.
|
|
264
|
+
*
|
|
265
|
+
* @param shape - The shape to check.
|
|
266
|
+
* @param editor - The editor instance.
|
|
267
|
+
*/
|
|
268
|
+
getShapeVisibility?(
|
|
269
|
+
shape: TLShape,
|
|
270
|
+
editor: Editor
|
|
271
|
+
): 'visible' | 'hidden' | 'inherit' | null | undefined
|
|
248
272
|
}
|
|
249
273
|
|
|
250
274
|
/**
|
|
@@ -281,12 +305,21 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
281
305
|
autoFocus,
|
|
282
306
|
inferDarkMode,
|
|
283
307
|
options,
|
|
308
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
284
309
|
isShapeHidden,
|
|
310
|
+
getShapeVisibility,
|
|
285
311
|
fontAssetUrls,
|
|
286
312
|
}: TLEditorOptions) {
|
|
287
313
|
super()
|
|
314
|
+
assert(
|
|
315
|
+
!(isShapeHidden && getShapeVisibility),
|
|
316
|
+
'Cannot use both isShapeHidden and getShapeVisibility'
|
|
317
|
+
)
|
|
288
318
|
|
|
289
|
-
this.
|
|
319
|
+
this._getShapeVisibility = isShapeHidden
|
|
320
|
+
? // eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
321
|
+
(shape: TLShape, editor: Editor) => (isShapeHidden(shape, editor) ? 'hidden' : 'inherit')
|
|
322
|
+
: getShapeVisibility
|
|
290
323
|
|
|
291
324
|
this.options = { ...defaultTldrawOptions, ...options }
|
|
292
325
|
|
|
@@ -773,18 +806,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
773
806
|
}
|
|
774
807
|
}
|
|
775
808
|
|
|
776
|
-
private readonly
|
|
809
|
+
private readonly _getShapeVisibility?: TLEditorOptions['getShapeVisibility']
|
|
777
810
|
@computed
|
|
778
811
|
private getIsShapeHiddenCache() {
|
|
779
|
-
if (!this.
|
|
812
|
+
if (!this._getShapeVisibility) return null
|
|
780
813
|
return this.store.createComputedCache<boolean, TLShape>('isShapeHidden', (shape: TLShape) => {
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
814
|
+
const visibility = this._getShapeVisibility!(shape, this)
|
|
815
|
+
const isParentHidden = PageRecordType.isId(shape.parentId)
|
|
816
|
+
? false
|
|
817
|
+
: this.isShapeHidden(shape.parentId)
|
|
818
|
+
|
|
819
|
+
if (isParentHidden) return visibility !== 'visible'
|
|
820
|
+
return visibility === 'hidden'
|
|
784
821
|
})
|
|
785
822
|
}
|
|
786
823
|
isShapeHidden(shapeOrId: TLShape | TLShapeId): boolean {
|
|
787
|
-
if (!this.
|
|
824
|
+
if (!this._getShapeVisibility) return false
|
|
788
825
|
return !!this.getIsShapeHiddenCache!()!.get(
|
|
789
826
|
typeof shapeOrId === 'string' ? shapeOrId : shapeOrId.id
|
|
790
827
|
)
|
|
@@ -1216,7 +1253,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1216
1253
|
run(fn: () => void, opts?: TLEditorRunOptions): this {
|
|
1217
1254
|
const previousIgnoreShapeLock = this._shouldIgnoreShapeLock
|
|
1218
1255
|
this._shouldIgnoreShapeLock = opts?.ignoreShapeLock ?? previousIgnoreShapeLock
|
|
1219
|
-
|
|
1220
1256
|
try {
|
|
1221
1257
|
this.history.batch(fn, opts)
|
|
1222
1258
|
} finally {
|
|
@@ -1778,6 +1814,195 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
1778
1814
|
return this
|
|
1779
1815
|
}
|
|
1780
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
|
+
|
|
1781
2006
|
/**
|
|
1782
2007
|
* Clear the selection.
|
|
1783
2008
|
*
|
|
@@ -3019,6 +3244,34 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3019
3244
|
return this
|
|
3020
3245
|
}
|
|
3021
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
|
+
|
|
3022
3275
|
/**
|
|
3023
3276
|
* Zoom the camera to fit a bounding box (in the current page space).
|
|
3024
3277
|
*
|
|
@@ -3712,7 +3965,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3712
3965
|
const addShapeById = (id: TLShapeId, opacity: number, isAncestorErasing: boolean) => {
|
|
3713
3966
|
const shape = this.getShape(id)
|
|
3714
3967
|
if (!shape) return
|
|
3715
|
-
|
|
3968
|
+
|
|
3969
|
+
if (this.isShapeHidden(shape)) {
|
|
3970
|
+
// process children just in case they are overriding the hidden state
|
|
3971
|
+
const isErasing = isAncestorErasing || erasingShapeIds.includes(id)
|
|
3972
|
+
for (const childId of this.getSortedChildIdsForParent(id)) {
|
|
3973
|
+
addShapeById(childId, opacity, isErasing)
|
|
3974
|
+
}
|
|
3975
|
+
return
|
|
3976
|
+
}
|
|
3716
3977
|
|
|
3717
3978
|
opacity *= shape.opacity
|
|
3718
3979
|
let isShapeErasing = false
|
|
@@ -4287,7 +4548,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4287
4548
|
private _shapeGeometryCaches: Record<string, ComputedCache<Geometry2d, TLShape>> = {}
|
|
4288
4549
|
|
|
4289
4550
|
/**
|
|
4290
|
-
* Get the geometry of a shape.
|
|
4551
|
+
* Get the geometry of a shape in shape-space.
|
|
4291
4552
|
*
|
|
4292
4553
|
* @example
|
|
4293
4554
|
* ```ts
|
|
@@ -4318,6 +4579,44 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4318
4579
|
)! as T
|
|
4319
4580
|
}
|
|
4320
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
|
+
|
|
4321
4620
|
/** @internal */
|
|
4322
4621
|
@computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
|
|
4323
4622
|
return this.store.createComputedCache('handles', (shape) => {
|
|
@@ -4424,15 +4723,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4424
4723
|
/** @internal */
|
|
4425
4724
|
@computed private _getShapePageBoundsCache(): ComputedCache<Box, TLShape> {
|
|
4426
4725
|
return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
if (!pageTransform) return new Box()
|
|
4430
|
-
|
|
4431
|
-
const result = Box.FromPoints(
|
|
4432
|
-
Mat.applyToPoints(pageTransform, this.getShapeGeometry(shape).vertices)
|
|
4433
|
-
)
|
|
4434
|
-
|
|
4435
|
-
return result
|
|
4726
|
+
return this.getShapePageGeometry(shape).bounds
|
|
4436
4727
|
})
|
|
4437
4728
|
}
|
|
4438
4729
|
|
|
@@ -4506,11 +4797,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4506
4797
|
if (frameAncestors.length === 0) return undefined
|
|
4507
4798
|
|
|
4508
4799
|
const pageMask = frameAncestors
|
|
4509
|
-
.map<Vec[] | undefined>(
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
.
|
|
4513
|
-
.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
|
|
4514
4804
|
)
|
|
4515
4805
|
.reduce((acc, b) => {
|
|
4516
4806
|
if (!(b && acc)) return undefined
|
|
@@ -8139,8 +8429,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
8139
8429
|
if (!currentTool) return styles
|
|
8140
8430
|
|
|
8141
8431
|
if (currentTool.shapeType) {
|
|
8142
|
-
|
|
8143
|
-
|
|
8432
|
+
if (
|
|
8433
|
+
currentTool.shapeType === 'frame' &&
|
|
8434
|
+
!(this.getShapeUtil('frame')!.options as any).showColors
|
|
8435
|
+
) {
|
|
8436
|
+
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
|
8437
|
+
if (style.id === 'tldraw:color') continue
|
|
8438
|
+
styles.applyValue(style, this.getStyleForNextShape(style))
|
|
8439
|
+
}
|
|
8440
|
+
} else {
|
|
8441
|
+
for (const style of this.styleProps[currentTool.shapeType].keys()) {
|
|
8442
|
+
styles.applyValue(style, this.getStyleForNextShape(style))
|
|
8443
|
+
}
|
|
8144
8444
|
}
|
|
8145
8445
|
}
|
|
8146
8446
|
|
|
@@ -9849,12 +10149,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9849
10149
|
|
|
9850
10150
|
const { x: cx, y: cy, z: cz } = unsafe__withoutCapture(() => this.getCamera())
|
|
9851
10151
|
|
|
9852
|
-
const { panSpeed
|
|
10152
|
+
const { panSpeed } = cameraOptions
|
|
9853
10153
|
this._setCamera(
|
|
9854
10154
|
new Vec(
|
|
9855
|
-
cx + (dx * panSpeed) / cz - x / cz + x /
|
|
9856
|
-
cy + (dy * panSpeed) / cz - y / cz + y /
|
|
9857
|
-
z
|
|
10155
|
+
cx + (dx * panSpeed) / cz - x / cz + x / z,
|
|
10156
|
+
cy + (dy * panSpeed) / cz - y / cz + y / z,
|
|
10157
|
+
z
|
|
9858
10158
|
),
|
|
9859
10159
|
{ immediate: true }
|
|
9860
10160
|
)
|
|
@@ -9929,14 +10229,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9929
10229
|
}
|
|
9930
10230
|
|
|
9931
10231
|
const zoom = cz + (delta ?? 0) * zoomSpeed * cz
|
|
9932
|
-
this._setCamera(
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
cy + (y / zoom - y) - (y / cz - y),
|
|
9936
|
-
zoom
|
|
9937
|
-
),
|
|
9938
|
-
{ immediate: true }
|
|
9939
|
-
)
|
|
10232
|
+
this._setCamera(new Vec(cx + x / zoom - x / cz, cy + y / zoom - y / cz, zoom), {
|
|
10233
|
+
immediate: true,
|
|
10234
|
+
})
|
|
9940
10235
|
this.maybeTrackPerformance('Zooming')
|
|
9941
10236
|
return
|
|
9942
10237
|
}
|
|
@@ -31,7 +31,7 @@ export const notVisibleShapes = (editor: Editor) => {
|
|
|
31
31
|
})
|
|
32
32
|
return notVisibleShapes
|
|
33
33
|
}
|
|
34
|
-
return computed<Set<TLShapeId>>('
|
|
34
|
+
return computed<Set<TLShapeId>>('notVisibleShapes', (prevValue) => {
|
|
35
35
|
if (isUninitialized(prevValue)) {
|
|
36
36
|
return fromScratch(editor)
|
|
37
37
|
}
|
|
@@ -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
|
}
|
|
@@ -94,7 +94,7 @@ export class FontManager {
|
|
|
94
94
|
const shapeUtil = this.editor.getShapeUtil(shape)
|
|
95
95
|
return shapeUtil.getFontFaces(shape)
|
|
96
96
|
},
|
|
97
|
-
{ areResultsEqual: areArraysShallowEqual }
|
|
97
|
+
{ areResultsEqual: areArraysShallowEqual, areRecordsEqual: (a, b) => a.props === b.props }
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
this.shapeFontLoadStateCache = editor.store.createCache<(FontState | null)[], TLShape>(
|
|
@@ -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
|
}
|
|
@@ -38,6 +38,7 @@ export interface TLStateNodeConstructor {
|
|
|
38
38
|
initial?: string
|
|
39
39
|
children?(): TLStateNodeConstructor[]
|
|
40
40
|
isLockable: boolean
|
|
41
|
+
useCoalescedEvents: boolean
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/** @public */
|
|
@@ -47,7 +48,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|
|
47
48
|
public editor: Editor,
|
|
48
49
|
parent?: StateNode
|
|
49
50
|
) {
|
|
50
|
-
const { id, children, initial, isLockable } = this
|
|
51
|
+
const { id, children, initial, isLockable, useCoalescedEvents } = this
|
|
52
|
+
.constructor as TLStateNodeConstructor
|
|
51
53
|
|
|
52
54
|
this.id = id
|
|
53
55
|
this._isActive = atom<boolean>('toolIsActive' + this.id, false)
|
|
@@ -83,6 +85,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
this.isLockable = isLockable
|
|
88
|
+
this.useCoalescedEvents = useCoalescedEvents
|
|
86
89
|
this.performanceTracker = new PerformanceTracker()
|
|
87
90
|
}
|
|
88
91
|
|
|
@@ -90,6 +93,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|
|
90
93
|
static initial?: string
|
|
91
94
|
static children?: () => TLStateNodeConstructor[]
|
|
92
95
|
static isLockable = true
|
|
96
|
+
static useCoalescedEvents = false
|
|
93
97
|
|
|
94
98
|
id: string
|
|
95
99
|
type: 'branch' | 'leaf' | 'root'
|
|
@@ -97,6 +101,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|
|
97
101
|
initial?: string
|
|
98
102
|
children?: Record<string, StateNode>
|
|
99
103
|
isLockable: boolean
|
|
104
|
+
useCoalescedEvents: boolean
|
|
100
105
|
parent: StateNode
|
|
101
106
|
|
|
102
107
|
/**
|