@tldraw/editor 4.4.0-canary.afdcafe834b3 → 4.4.0-canary.b5c642789999
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 +52 -9
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/components/Shape.js +12 -17
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +26 -1
- package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +16 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +19 -11
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +32 -13
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +2 -3
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +8 -2
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/hooks/useShapeCulling.js +75 -0
- package/dist-cjs/lib/hooks/useShapeCulling.js.map +7 -0
- package/dist-cjs/lib/license/LicenseManager.js +6 -6
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/options.js +2 -1
- package/dist-cjs/lib/options.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 +52 -9
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/components/Shape.mjs +12 -17
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +27 -2
- package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +16 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +19 -11
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +32 -13
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +2 -3
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +8 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/hooks/useShapeCulling.mjs +55 -0
- package/dist-esm/lib/hooks/useShapeCulling.mjs.map +7 -0
- package/dist-esm/lib/license/LicenseManager.mjs +6 -6
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/options.mjs +2 -1
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +22 -2
- package/package.json +8 -9
- package/src/lib/components/Shape.tsx +15 -16
- package/src/lib/components/default-components/CanvasShapeIndicators.tsx +46 -2
- package/src/lib/components/default-components/DefaultCanvas.tsx +24 -2
- package/src/lib/editor/Editor.ts +33 -11
- package/src/lib/editor/derivations/notVisibleShapes.ts +39 -17
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +0 -35
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +4 -8
- package/src/lib/editor/shapes/ShapeUtil.ts +19 -5
- package/src/lib/hooks/usePeerIds.ts +9 -2
- package/src/lib/hooks/useShapeCulling.tsx +98 -0
- package/src/lib/license/LicenseManager.ts +6 -6
- package/src/lib/options.ts +10 -2
- package/src/version.ts +3 -3
package/dist-esm/lib/options.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/options.ts"],
|
|
4
|
-
"sourcesContent": ["import { ComponentType, Fragment } from 'react'\n\n/**\n * Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}.\n *\n * @example\n * ```tsx\n * const options: Partial<TldrawOptions> = {\n * maxPages: 3,\n * maxShapesPerPage: 1000,\n * }\n *\n * function MyTldrawComponent() {\n * return <Tldraw options={options} />\n * }\n * ```\n *\n * @public\n */\nexport interface TldrawOptions {\n\treadonly maxShapesPerPage: number\n\treadonly maxFilesAtOnce: number\n\treadonly maxPages: number\n\treadonly animationMediumMs: number\n\treadonly followChaseViewportSnap: number\n\treadonly doubleClickDurationMs: number\n\treadonly multiClickDurationMs: number\n\treadonly coarseDragDistanceSquared: number\n\treadonly dragDistanceSquared: number\n\treadonly uiDragDistanceSquared: number\n\treadonly uiCoarseDragDistanceSquared: number\n\treadonly defaultSvgPadding: number\n\treadonly cameraSlideFriction: number\n\treadonly gridSteps: readonly {\n\t\treadonly min: number\n\t\treadonly mid: number\n\t\treadonly step: number\n\t}[]\n\treadonly collaboratorInactiveTimeoutMs: number\n\treadonly collaboratorIdleTimeoutMs: number\n\treadonly collaboratorCheckIntervalMs: number\n\treadonly cameraMovingTimeoutMs: number\n\treadonly hitTestMargin: number\n\treadonly edgeScrollDelay: number\n\treadonly edgeScrollEaseDuration: number\n\treadonly edgeScrollSpeed: number\n\treadonly edgeScrollDistance: number\n\treadonly coarsePointerWidth: number\n\treadonly coarseHandleRadius: number\n\treadonly handleRadius: number\n\treadonly longPressDurationMs: number\n\treadonly textShadowLod: number\n\treadonly adjacentShapeMargin: number\n\treadonly flattenImageBoundsExpand: number\n\treadonly flattenImageBoundsPadding: number\n\treadonly laserDelayMs: number\n\t/**\n\t * How long (in milliseconds) to fade all laser scribbles after the session ends.\n\t * The total points across all scribbles will be removed proportionally over this duration.\n\t * Defaults to 500ms (0.5 seconds).\n\t */\n\treadonly laserFadeoutMs: number\n\treadonly maxExportDelayMs: number\n\treadonly tooltipDelayMs: number\n\t/**\n\t * How long should previews created by {@link Editor.createTemporaryAssetPreview} last before\n\t * they expire? Defaults to 3 minutes.\n\t */\n\treadonly temporaryAssetPreviewLifetimeMs: number\n\treadonly actionShortcutsLocation: 'menu' | 'toolbar' | 'swap'\n\treadonly createTextOnCanvasDoubleClick: boolean\n\t/**\n\t * The react provider to use when exporting an image. This is useful if your shapes depend on\n\t * external context providers. By default, this is `React.Fragment`.\n\t */\n\treadonly exportProvider: ComponentType<{ children: React.ReactNode }>\n\t/**\n\t * By default, the toolbar items are accessible via number shortcuts according to their order. To disable this, set this option to false.\n\t */\n\treadonly enableToolbarKeyboardShortcuts: boolean\n\t/**\n\t * The maximum number of fonts that will be loaded while blocking the main rendering of the\n\t * canvas. If there are more than this number of fonts needed, we'll just show the canvas right\n\t * away and let the fonts load in in the background.\n\t */\n\treadonly maxFontsToLoadBeforeRender: number\n\t/**\n\t * If you have a CSP policy that blocks inline styles, you can use this prop to provide a\n\t * nonce to use in the editor's styles.\n\t */\n\treadonly nonce: string | undefined\n\t/**\n\t * Branding name of the app, currently only used for adding aria-label for the application.\n\t */\n\treadonly branding?: string\n\t/**\n\t * Whether to use debounced zoom level for certain rendering optimizations. When true,\n\t * `editor.
|
|
5
|
-
"mappings": "AAAA,SAAwB,gBAAgB;
|
|
4
|
+
"sourcesContent": ["import { ComponentType, Fragment } from 'react'\n\n/**\n * Options for configuring tldraw. For defaults, see {@link defaultTldrawOptions}.\n *\n * @example\n * ```tsx\n * const options: Partial<TldrawOptions> = {\n * maxPages: 3,\n * maxShapesPerPage: 1000,\n * }\n *\n * function MyTldrawComponent() {\n * return <Tldraw options={options} />\n * }\n * ```\n *\n * @public\n */\nexport interface TldrawOptions {\n\treadonly maxShapesPerPage: number\n\treadonly maxFilesAtOnce: number\n\treadonly maxPages: number\n\treadonly animationMediumMs: number\n\treadonly followChaseViewportSnap: number\n\treadonly doubleClickDurationMs: number\n\treadonly multiClickDurationMs: number\n\treadonly coarseDragDistanceSquared: number\n\treadonly dragDistanceSquared: number\n\treadonly uiDragDistanceSquared: number\n\treadonly uiCoarseDragDistanceSquared: number\n\treadonly defaultSvgPadding: number\n\treadonly cameraSlideFriction: number\n\treadonly gridSteps: readonly {\n\t\treadonly min: number\n\t\treadonly mid: number\n\t\treadonly step: number\n\t}[]\n\treadonly collaboratorInactiveTimeoutMs: number\n\treadonly collaboratorIdleTimeoutMs: number\n\treadonly collaboratorCheckIntervalMs: number\n\treadonly cameraMovingTimeoutMs: number\n\treadonly hitTestMargin: number\n\treadonly edgeScrollDelay: number\n\treadonly edgeScrollEaseDuration: number\n\treadonly edgeScrollSpeed: number\n\treadonly edgeScrollDistance: number\n\treadonly coarsePointerWidth: number\n\treadonly coarseHandleRadius: number\n\treadonly handleRadius: number\n\treadonly longPressDurationMs: number\n\treadonly textShadowLod: number\n\treadonly adjacentShapeMargin: number\n\treadonly flattenImageBoundsExpand: number\n\treadonly flattenImageBoundsPadding: number\n\treadonly laserDelayMs: number\n\t/**\n\t * How long (in milliseconds) to fade all laser scribbles after the session ends.\n\t * The total points across all scribbles will be removed proportionally over this duration.\n\t * Defaults to 500ms (0.5 seconds).\n\t */\n\treadonly laserFadeoutMs: number\n\treadonly maxExportDelayMs: number\n\treadonly tooltipDelayMs: number\n\t/**\n\t * How long should previews created by {@link Editor.createTemporaryAssetPreview} last before\n\t * they expire? Defaults to 3 minutes.\n\t */\n\treadonly temporaryAssetPreviewLifetimeMs: number\n\treadonly actionShortcutsLocation: 'menu' | 'toolbar' | 'swap'\n\treadonly createTextOnCanvasDoubleClick: boolean\n\t/**\n\t * The react provider to use when exporting an image. This is useful if your shapes depend on\n\t * external context providers. By default, this is `React.Fragment`.\n\t */\n\treadonly exportProvider: ComponentType<{ children: React.ReactNode }>\n\t/**\n\t * By default, the toolbar items are accessible via number shortcuts according to their order. To disable this, set this option to false.\n\t */\n\treadonly enableToolbarKeyboardShortcuts: boolean\n\t/**\n\t * The maximum number of fonts that will be loaded while blocking the main rendering of the\n\t * canvas. If there are more than this number of fonts needed, we'll just show the canvas right\n\t * away and let the fonts load in in the background.\n\t */\n\treadonly maxFontsToLoadBeforeRender: number\n\t/**\n\t * If you have a CSP policy that blocks inline styles, you can use this prop to provide a\n\t * nonce to use in the editor's styles.\n\t */\n\treadonly nonce: string | undefined\n\t/**\n\t * Branding name of the app, currently only used for adding aria-label for the application.\n\t */\n\treadonly branding?: string\n\t/**\n\t * Whether to use debounced zoom level for certain rendering optimizations. When true,\n\t * `editor.getEfficientZoomLevel()` returns a cached zoom value while the camera is moving,\n\t * reducing re-renders. When false, it always returns the current zoom level.\n\t */\n\treadonly debouncedZoom: boolean\n\t/**\n\t * The number of shapes that must be on the page for the debounced zoom level to be used.\n\t * Defaults to 500 shapes.\n\t */\n\treadonly debouncedZoomThreshold: number\n\t/**\n\t * Whether to allow spacebar panning. When true, the spacebar will pan the camera when held down.\n\t * When false, the spacebar will not pan the camera.\n\t */\n\treadonly spacebarPanning: boolean\n\t/**\n\t * The default padding (in pixels) used when zooming to fit content in the viewport.\n\t * This affects methods like `zoomToFit()`, `zoomToSelection()`, and `zoomToBounds()`.\n\t * The actual padding used is the minimum of this value and 28% of the viewport width.\n\t * Defaults to 128 pixels.\n\t */\n\treadonly zoomToFitPadding: number\n\t/**\n\t * The distance (in screen pixels) at which shapes snap to guides and other shapes.\n\t */\n\treadonly snapThreshold: number\n\t/**\n\t * Whether the quick-zoom brush preserves its screen-pixel size when the user\n\t * zooms the overview. When true, zooming in shrinks the target viewport (higher\n\t * return zoom); zooming out expands it. When false, the brush keeps the original\n\t * viewport's page dimensions regardless of overview zoom changes.\n\t */\n\treadonly quickZoomPreservesScreenBounds: boolean\n}\n\n/** @public */\nexport const defaultTldrawOptions = {\n\tmaxShapesPerPage: 4000,\n\tmaxFilesAtOnce: 100,\n\tmaxPages: 40,\n\tanimationMediumMs: 320,\n\tfollowChaseViewportSnap: 2,\n\tdoubleClickDurationMs: 450,\n\tmultiClickDurationMs: 200,\n\tcoarseDragDistanceSquared: 36, // 6 squared\n\tdragDistanceSquared: 16, // 4 squared\n\tuiDragDistanceSquared: 16, // 4 squared\n\t// it's really easy to accidentally drag from the toolbar on mobile, so we use a much larger\n\t// threshold than usual here to try and prevent accidental drags.\n\tuiCoarseDragDistanceSquared: 625, // 25 squared\n\tdefaultSvgPadding: 32,\n\tcameraSlideFriction: 0.09,\n\tgridSteps: [\n\t\t{ min: -1, mid: 0.15, step: 64 },\n\t\t{ min: 0.05, mid: 0.375, step: 16 },\n\t\t{ min: 0.15, mid: 1, step: 4 },\n\t\t{ min: 0.7, mid: 2.5, step: 1 },\n\t],\n\tcollaboratorInactiveTimeoutMs: 60000,\n\tcollaboratorIdleTimeoutMs: 3000,\n\tcollaboratorCheckIntervalMs: 1200,\n\tcameraMovingTimeoutMs: 64,\n\thitTestMargin: 8,\n\tedgeScrollDelay: 200,\n\tedgeScrollEaseDuration: 200,\n\tedgeScrollSpeed: 25,\n\tedgeScrollDistance: 8,\n\tcoarsePointerWidth: 12,\n\tcoarseHandleRadius: 20,\n\thandleRadius: 12,\n\tlongPressDurationMs: 500,\n\ttextShadowLod: 0.35,\n\tadjacentShapeMargin: 10,\n\tflattenImageBoundsExpand: 64,\n\tflattenImageBoundsPadding: 16,\n\tlaserDelayMs: 1200,\n\tlaserFadeoutMs: 500,\n\tmaxExportDelayMs: 5000,\n\ttooltipDelayMs: 700,\n\ttemporaryAssetPreviewLifetimeMs: 180000,\n\tactionShortcutsLocation: 'swap',\n\tcreateTextOnCanvasDoubleClick: true,\n\texportProvider: Fragment,\n\tenableToolbarKeyboardShortcuts: true,\n\tmaxFontsToLoadBeforeRender: Infinity,\n\tnonce: undefined,\n\tdebouncedZoom: true,\n\tdebouncedZoomThreshold: 500,\n\tspacebarPanning: true,\n\tzoomToFitPadding: 128,\n\tsnapThreshold: 8,\n\tquickZoomPreservesScreenBounds: true,\n} as const satisfies TldrawOptions\n"],
|
|
5
|
+
"mappings": "AAAA,SAAwB,gBAAgB;AAoIjC,MAAM,uBAAuB;AAAA,EACnC,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,uBAAuB;AAAA,EACvB,sBAAsB;AAAA,EACtB,2BAA2B;AAAA;AAAA,EAC3B,qBAAqB;AAAA;AAAA,EACrB,uBAAuB;AAAA;AAAA;AAAA;AAAA,EAGvB,6BAA6B;AAAA;AAAA,EAC7B,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,WAAW;AAAA,IACV,EAAE,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG;AAAA,IAC/B,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,GAAG;AAAA,IAClC,EAAE,KAAK,MAAM,KAAK,GAAG,MAAM,EAAE;AAAA,IAC7B,EAAE,KAAK,KAAK,KAAK,KAAK,MAAM,EAAE;AAAA,EAC/B;AAAA,EACA,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,EAC3B,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,iCAAiC;AAAA,EACjC,yBAAyB;AAAA,EACzB,+BAA+B;AAAA,EAC/B,gBAAgB;AAAA,EAChB,gCAAgC;AAAA,EAChC,4BAA4B;AAAA,EAC5B,OAAO;AAAA,EACP,eAAe;AAAA,EACf,wBAAwB;AAAA,EACxB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,gCAAgC;AACjC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "4.4.0-canary.
|
|
1
|
+
const version = "4.4.0-canary.b5c642789999";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2025-09-18T14:39:22.803Z",
|
|
4
|
-
minor: "2026-02-
|
|
5
|
-
patch: "2026-02-
|
|
4
|
+
minor: "2026-02-11T11:56:14.353Z",
|
|
5
|
+
patch: "2026-02-11T11:56:14.353Z"
|
|
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 = '4.4.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 = '4.4.0-canary.b5c642789999'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-02-11T11:56:14.353Z',\n\tpatch: '2026-02-11T11:56:14.353Z',\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/editor.css
CHANGED
|
@@ -22,14 +22,25 @@
|
|
|
22
22
|
--tl-radius-3: 9px;
|
|
23
23
|
--tl-radius-4: 11px;
|
|
24
24
|
|
|
25
|
-
/*
|
|
25
|
+
/*
|
|
26
|
+
* Canvas z-index
|
|
27
|
+
*
|
|
28
|
+
* .tl-canvas has `contain: strict` which creates its own stacking context.
|
|
29
|
+
* background, grid, shapes, overlays, and blocker are all children of .tl-canvas,
|
|
30
|
+
* so their z-indices are relative to the canvas, not the container.
|
|
31
|
+
*
|
|
32
|
+
* watermark and canvas-in-front are siblings of .tl-canvas (outside it),
|
|
33
|
+
* so they compete with the UI layer (.tlui-layout at z-index 300).
|
|
34
|
+
* canvas-in-front must be below the UI layer (300) to sit between
|
|
35
|
+
* the canvas and the UI.
|
|
36
|
+
*/
|
|
26
37
|
--tl-layer-canvas-hidden: -999999;
|
|
27
38
|
--tl-layer-canvas-background: 100;
|
|
28
39
|
--tl-layer-canvas-grid: 150;
|
|
29
40
|
--tl-layer-watermark: 200;
|
|
41
|
+
--tl-layer-canvas-in-front: 250;
|
|
30
42
|
--tl-layer-canvas-shapes: 300;
|
|
31
43
|
--tl-layer-canvas-overlays: 500;
|
|
32
|
-
--tl-layer-canvas-in-front: 600;
|
|
33
44
|
--tl-layer-canvas-blocker: 10000;
|
|
34
45
|
|
|
35
46
|
/* Canvas overlays z-index */
|
|
@@ -60,6 +71,7 @@
|
|
|
60
71
|
|
|
61
72
|
/* Misc */
|
|
62
73
|
--tl-zoom: 1;
|
|
74
|
+
--tl-tab-size: 2;
|
|
63
75
|
|
|
64
76
|
/* Cursor SVGs */
|
|
65
77
|
--tl-cursor-none: none;
|
|
@@ -146,6 +158,8 @@
|
|
|
146
158
|
--tl-color-selection-fill: hsl(210, 100%, 56%, 24%);
|
|
147
159
|
--tl-color-selection-stroke: hsl(214, 84%, 56%);
|
|
148
160
|
--tl-color-background: hsl(210, 20%, 98%);
|
|
161
|
+
/* if you ever update --tl-color-background, update the hardcoded values in theme-init.js and globals.css
|
|
162
|
+
they're there to make sure the background color matches the user's preference before the actual app loads*/
|
|
149
163
|
--tl-color-brush-fill: hsl(0, 0%, 56%, 10.2%);
|
|
150
164
|
--tl-color-brush-stroke: hsl(0, 0%, 56%, 25.1%);
|
|
151
165
|
--tl-color-grid: hsl(0, 0%, 43%);
|
|
@@ -202,6 +216,8 @@
|
|
|
202
216
|
--tl-color-selection-fill: hsl(209, 100%, 57%, 20%);
|
|
203
217
|
--tl-color-selection-stroke: hsl(214, 84%, 56%);
|
|
204
218
|
--tl-color-background: hsl(240, 5%, 6.5%);
|
|
219
|
+
/* if you ever update --tl-color-background, update the hardcoded values in theme-init.js and globals.css
|
|
220
|
+
they're there to make sure the background color matches the user's preference before the actual app loads*/
|
|
205
221
|
--tl-color-brush-fill: hsl(0, 0%, 71%, 5.1%);
|
|
206
222
|
--tl-color-brush-stroke: hsl(0, 0%, 71%, 25.1%);
|
|
207
223
|
--tl-color-grid: hsl(0, 0%, 40%);
|
|
@@ -895,6 +911,10 @@ input,
|
|
|
895
911
|
/* white-space: break-spaces; */
|
|
896
912
|
}
|
|
897
913
|
|
|
914
|
+
.tl-rich-text {
|
|
915
|
+
tab-size: var(--tl-tab-size, 2);
|
|
916
|
+
}
|
|
917
|
+
|
|
898
918
|
.tl-rich-text p {
|
|
899
919
|
margin: 0;
|
|
900
920
|
/* Depending on the extensions, <p> tags can be empty, without a <br />. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (editor).",
|
|
4
|
-
"version": "4.4.0-canary.
|
|
4
|
+
"version": "4.4.0-canary.b5c642789999",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -43,19 +43,18 @@
|
|
|
43
43
|
"prepack": "yarn run -T tsx ../../internal/scripts/prepack.ts",
|
|
44
44
|
"postpack": "../../internal/scripts/postpack.sh",
|
|
45
45
|
"pack-tarball": "yarn pack",
|
|
46
|
-
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
|
|
47
|
-
"context": "yarn run -T tsx ../../internal/scripts/context.ts"
|
|
46
|
+
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
|
|
48
47
|
},
|
|
49
48
|
"dependencies": {
|
|
50
49
|
"@tiptap/core": "^3.12.1",
|
|
51
50
|
"@tiptap/pm": "^3.12.1",
|
|
52
51
|
"@tiptap/react": "^3.12.1",
|
|
53
|
-
"@tldraw/state": "4.4.0-canary.
|
|
54
|
-
"@tldraw/state-react": "4.4.0-canary.
|
|
55
|
-
"@tldraw/store": "4.4.0-canary.
|
|
56
|
-
"@tldraw/tlschema": "4.4.0-canary.
|
|
57
|
-
"@tldraw/utils": "4.4.0-canary.
|
|
58
|
-
"@tldraw/validate": "4.4.0-canary.
|
|
52
|
+
"@tldraw/state": "4.4.0-canary.b5c642789999",
|
|
53
|
+
"@tldraw/state-react": "4.4.0-canary.b5c642789999",
|
|
54
|
+
"@tldraw/store": "4.4.0-canary.b5c642789999",
|
|
55
|
+
"@tldraw/tlschema": "4.4.0-canary.b5c642789999",
|
|
56
|
+
"@tldraw/utils": "4.4.0-canary.b5c642789999",
|
|
57
|
+
"@tldraw/validate": "4.4.0-canary.b5c642789999",
|
|
59
58
|
"@use-gesture/react": "^10.3.1",
|
|
60
59
|
"classnames": "^2.5.1",
|
|
61
60
|
"eventemitter3": "^4.0.7",
|
|
@@ -5,6 +5,7 @@ import { memo, useCallback, useEffect, useLayoutEffect, useRef } from 'react'
|
|
|
5
5
|
import { ShapeUtil } from '../editor/shapes/ShapeUtil'
|
|
6
6
|
import { useEditor } from '../hooks/useEditor'
|
|
7
7
|
import { useEditorComponents } from '../hooks/useEditorComponents'
|
|
8
|
+
import { useShapeCulling } from '../hooks/useShapeCulling'
|
|
8
9
|
import { Mat } from '../primitives/Mat'
|
|
9
10
|
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
10
11
|
import { setStyleProperty } from '../utils/dom'
|
|
@@ -57,7 +58,6 @@ export const Shape = memo(function Shape({
|
|
|
57
58
|
height: 0,
|
|
58
59
|
x: 0,
|
|
59
60
|
y: 0,
|
|
60
|
-
isCulled: false,
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
useQuickReactor(
|
|
@@ -118,22 +118,21 @@ export const Shape = memo(function Shape({
|
|
|
118
118
|
setStyleProperty(bgContainer, 'z-index', backgroundIndex)
|
|
119
119
|
}, [opacity, index, backgroundIndex])
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
// Register container refs with the centralized culling context.
|
|
122
|
+
// This runs on mount and handles initial display state.
|
|
123
|
+
const { register, unregister } = useShapeCulling()
|
|
124
|
+
useLayoutEffect(() => {
|
|
125
|
+
const container = containerRef.current
|
|
126
|
+
if (!container) return
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
[editor]
|
|
136
|
-
)
|
|
128
|
+
// Check initial culling state and register with the context
|
|
129
|
+
const isCulled = editor.getCulledShapes().has(id)
|
|
130
|
+
register(id, container, bgContainerRef.current, isCulled)
|
|
131
|
+
|
|
132
|
+
return () => {
|
|
133
|
+
unregister(id)
|
|
134
|
+
}
|
|
135
|
+
}, [editor, id, register, unregister])
|
|
137
136
|
const annotateError = useCallback(
|
|
138
137
|
(error: any) => editor.annotateError(error, { origin: 'shape', willCrashApp: false }),
|
|
139
138
|
[editor]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useComputed, useQuickReactor } from '@tldraw/state-react'
|
|
2
2
|
import { createComputedCache } from '@tldraw/store'
|
|
3
3
|
import { TLShape, TLShapeId } from '@tldraw/tlschema'
|
|
4
|
-
import { dedupe
|
|
4
|
+
import { dedupe } from '@tldraw/utils'
|
|
5
5
|
import { memo, useEffect, useRef } from 'react'
|
|
6
6
|
import { Editor } from '../../editor/Editor'
|
|
7
7
|
import { TLIndicatorPath } from '../../editor/shapes/ShapeUtil'
|
|
@@ -14,6 +14,50 @@ interface CollaboratorIndicatorData {
|
|
|
14
14
|
shapeIds: TLShapeId[]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
interface RenderData {
|
|
18
|
+
idsToDisplay: Set<TLShapeId>
|
|
19
|
+
renderingShapeIds: Set<TLShapeId>
|
|
20
|
+
hintingShapeIds: TLShapeId[]
|
|
21
|
+
collaboratorIndicators: CollaboratorIndicatorData[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setsEqual<T>(a: Set<T>, b: Set<T>): boolean {
|
|
25
|
+
if (a.size !== b.size) return false
|
|
26
|
+
for (const item of a) {
|
|
27
|
+
if (!b.has(item)) return false
|
|
28
|
+
}
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function arraysEqual<T>(a: readonly T[], b: readonly T[]): boolean {
|
|
33
|
+
if (a.length !== b.length) return false
|
|
34
|
+
for (let i = 0; i < a.length; i++) {
|
|
35
|
+
if (a[i] !== b[i]) return false
|
|
36
|
+
}
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function collaboratorIndicatorsEqual(
|
|
41
|
+
a: CollaboratorIndicatorData[],
|
|
42
|
+
b: CollaboratorIndicatorData[]
|
|
43
|
+
): boolean {
|
|
44
|
+
if (a.length !== b.length) return false
|
|
45
|
+
for (let i = 0; i < a.length; i++) {
|
|
46
|
+
if (a[i].color !== b[i].color) return false
|
|
47
|
+
if (!arraysEqual(a[i].shapeIds, b[i].shapeIds)) return false
|
|
48
|
+
}
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function renderDataEqual(a: RenderData, b: RenderData): boolean {
|
|
53
|
+
return (
|
|
54
|
+
setsEqual(a.idsToDisplay, b.idsToDisplay) &&
|
|
55
|
+
setsEqual(a.renderingShapeIds, b.renderingShapeIds) &&
|
|
56
|
+
arraysEqual(a.hintingShapeIds, b.hintingShapeIds) &&
|
|
57
|
+
collaboratorIndicatorsEqual(a.collaboratorIndicators, b.collaboratorIndicators)
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
17
61
|
const indicatorPathCache = createComputedCache(
|
|
18
62
|
'indicatorPath',
|
|
19
63
|
(editor: Editor, shape: TLShape) => {
|
|
@@ -162,7 +206,7 @@ export const CanvasShapeIndicators = memo(function CanvasShapeIndicators() {
|
|
|
162
206
|
collaboratorIndicators,
|
|
163
207
|
}
|
|
164
208
|
},
|
|
165
|
-
{ isEqual:
|
|
209
|
+
{ isEqual: renderDataEqual },
|
|
166
210
|
[editor, activePeerIds$]
|
|
167
211
|
)
|
|
168
212
|
|
|
@@ -16,6 +16,7 @@ import { useGestureEvents } from '../../hooks/useGestureEvents'
|
|
|
16
16
|
import { useHandleEvents } from '../../hooks/useHandleEvents'
|
|
17
17
|
import { useSharedSafeId } from '../../hooks/useSafeId'
|
|
18
18
|
import { useScreenBounds } from '../../hooks/useScreenBounds'
|
|
19
|
+
import { ShapeCullingProvider, useShapeCulling } from '../../hooks/useShapeCulling'
|
|
19
20
|
import { Box } from '../../primitives/Box'
|
|
20
21
|
import { Mat } from '../../primitives/Mat'
|
|
21
22
|
import { Vec } from '../../primitives/Vec'
|
|
@@ -428,18 +429,39 @@ function ReflowIfNeeded() {
|
|
|
428
429
|
return null
|
|
429
430
|
}
|
|
430
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Centralized culling controller that updates shape container visibility.
|
|
434
|
+
* This single reactor replaces per-shape subscriptions for O(1) instead of O(N) subscriptions.
|
|
435
|
+
*/
|
|
436
|
+
function CullingController() {
|
|
437
|
+
const editor = useEditor()
|
|
438
|
+
const { updateCulling } = useShapeCulling()
|
|
439
|
+
|
|
440
|
+
useQuickReactor(
|
|
441
|
+
'update shape culling',
|
|
442
|
+
() => {
|
|
443
|
+
const culledShapes = editor.getCulledShapes()
|
|
444
|
+
updateCulling(culledShapes)
|
|
445
|
+
},
|
|
446
|
+
[editor, updateCulling]
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
return null
|
|
450
|
+
}
|
|
451
|
+
|
|
431
452
|
function ShapesToDisplay() {
|
|
432
453
|
const editor = useEditor()
|
|
433
454
|
|
|
434
455
|
const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
|
|
435
456
|
|
|
436
457
|
return (
|
|
437
|
-
|
|
458
|
+
<ShapeCullingProvider>
|
|
438
459
|
{renderingShapes.map((result) => (
|
|
439
460
|
<Shape key={result.id + '_shape'} {...result} />
|
|
440
461
|
))}
|
|
462
|
+
<CullingController />
|
|
441
463
|
{tlenv.isSafari && <ReflowIfNeeded />}
|
|
442
|
-
|
|
464
|
+
</ShapeCullingProvider>
|
|
443
465
|
)
|
|
444
466
|
}
|
|
445
467
|
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -153,7 +153,13 @@ import { SpatialIndexManager } from './managers/SpatialIndexManager/SpatialIndex
|
|
|
153
153
|
import { TextManager } from './managers/TextManager/TextManager'
|
|
154
154
|
import { TickManager } from './managers/TickManager/TickManager'
|
|
155
155
|
import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
|
|
156
|
-
import {
|
|
156
|
+
import {
|
|
157
|
+
ShapeUtil,
|
|
158
|
+
TLEditStartInfo,
|
|
159
|
+
TLGeometryOpts,
|
|
160
|
+
TLResizeMode,
|
|
161
|
+
TLShapeUtilCanBindOpts,
|
|
162
|
+
} from './shapes/ShapeUtil'
|
|
157
163
|
import { RootState } from './tools/RootState'
|
|
158
164
|
import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
|
|
159
165
|
import { TLContent } from './types/clipboard-types'
|
|
@@ -443,6 +449,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
443
449
|
const deletedShapeIds = new Set<TLShapeId>()
|
|
444
450
|
const invalidParents = new Set<TLShapeId>()
|
|
445
451
|
let invalidBindingTypes = new Set<TLBinding['type']>()
|
|
452
|
+
|
|
446
453
|
this.disposables.add(
|
|
447
454
|
this.sideEffects.registerOperationCompleteHandler(() => {
|
|
448
455
|
// this needs to be cleared here because further effects may delete more shapes
|
|
@@ -4180,23 +4187,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4180
4187
|
// unmount / remount in the DOM, which is expensive; and computing visibility is
|
|
4181
4188
|
// also expensive in large projects. For this reason, we use a second bounding
|
|
4182
4189
|
// box just for rendering, and we only update after the camera stops moving.
|
|
4183
|
-
private _cameraState = atom('camera state', 'idle' as 'idle' | 'moving')
|
|
4184
4190
|
private _cameraStateTimeoutRemaining = 0
|
|
4185
4191
|
private _decayCameraStateTimeout(elapsed: number) {
|
|
4186
4192
|
this._cameraStateTimeoutRemaining -= elapsed
|
|
4187
4193
|
if (this._cameraStateTimeoutRemaining > 0) return
|
|
4188
4194
|
this.off('tick', this._decayCameraStateTimeout)
|
|
4189
|
-
this.
|
|
4195
|
+
this._setCameraState('idle')
|
|
4190
4196
|
}
|
|
4191
4197
|
private _tickCameraState() {
|
|
4192
4198
|
// always reset the timeout
|
|
4193
4199
|
this._cameraStateTimeoutRemaining = this.options.cameraMovingTimeoutMs
|
|
4194
4200
|
// If the state is idle, then start the tick
|
|
4195
|
-
if (this.
|
|
4196
|
-
this.
|
|
4201
|
+
if (this.getInstanceState().cameraState !== 'idle') return
|
|
4202
|
+
this._setCameraState('moving')
|
|
4197
4203
|
this._debouncedZoomLevel.set(unsafe__withoutCapture(() => this.getCamera().z))
|
|
4198
4204
|
this.on('tick', this._decayCameraStateTimeout)
|
|
4199
4205
|
}
|
|
4206
|
+
private _setCameraState(cameraState: 'idle' | 'moving') {
|
|
4207
|
+
this.updateInstanceState({ cameraState }, { history: 'ignore' })
|
|
4208
|
+
}
|
|
4200
4209
|
|
|
4201
4210
|
/**
|
|
4202
4211
|
* Whether the camera is moving or idle.
|
|
@@ -4209,7 +4218,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4209
4218
|
* @public
|
|
4210
4219
|
*/
|
|
4211
4220
|
getCameraState() {
|
|
4212
|
-
return this.
|
|
4221
|
+
return this.getInstanceState().cameraState
|
|
4213
4222
|
}
|
|
4214
4223
|
|
|
4215
4224
|
/**
|
|
@@ -5481,7 +5490,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5481
5490
|
* @param bounds - The bounds to search within.
|
|
5482
5491
|
* @returns Unordered set of shape IDs within the given bounds.
|
|
5483
5492
|
*
|
|
5484
|
-
* @
|
|
5493
|
+
* @public
|
|
5485
5494
|
*/
|
|
5486
5495
|
getShapeIdsInsideBounds(bounds: Box): Set<TLShapeId> {
|
|
5487
5496
|
return this._spatialIndex.getShapeIdsInsideBounds(bounds)
|
|
@@ -6246,7 +6255,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6246
6255
|
const toShapeType = typeof toShape === 'string' ? toShape : toShape.type
|
|
6247
6256
|
const bindingType = typeof binding === 'string' ? binding : binding.type
|
|
6248
6257
|
|
|
6249
|
-
const canBindOpts = {
|
|
6258
|
+
const canBindOpts: TLShapeUtilCanBindOpts = {
|
|
6259
|
+
fromShape: typeof fromShape === 'string' ? { type: fromShape } : fromShape,
|
|
6260
|
+
toShape: typeof toShape === 'string' ? { type: toShape } : toShape,
|
|
6261
|
+
bindingType,
|
|
6262
|
+
fromShapeType,
|
|
6263
|
+
toShapeType,
|
|
6264
|
+
}
|
|
6250
6265
|
|
|
6251
6266
|
if (fromShapeType === toShapeType) {
|
|
6252
6267
|
return this.getShapeUtil(fromShapeType).canBind(canBindOpts)
|
|
@@ -7839,6 +7854,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7839
7854
|
initialShape: options.initialShape,
|
|
7840
7855
|
initialBounds: options.initialBounds,
|
|
7841
7856
|
isAspectRatioLocked: options.isAspectRatioLocked,
|
|
7857
|
+
initialPageTransform: options.initialPageTransform,
|
|
7842
7858
|
})
|
|
7843
7859
|
|
|
7844
7860
|
// then if the shape is flipped in one axis only, we need to apply an extra rotation
|
|
@@ -10421,9 +10437,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10421
10437
|
if (inputs.getIsPinching()) return
|
|
10422
10438
|
|
|
10423
10439
|
if (!inputs.getIsEditing()) {
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10440
|
+
// Always capture the current selection when pinch starts.
|
|
10441
|
+
// This ensures Safari (which uses gesture events instead of wheel)
|
|
10442
|
+
// doesn't restore a stale selection from an earlier pointer_down.
|
|
10443
|
+
this._selectedShapeIdsAtPointerDown = [...pageState.selectedShapeIds]
|
|
10427
10444
|
|
|
10428
10445
|
this._didPinch = true
|
|
10429
10446
|
|
|
@@ -10734,6 +10751,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10734
10751
|
this.setCurrentTool(this._restoreToolId)
|
|
10735
10752
|
}
|
|
10736
10753
|
}
|
|
10754
|
+
|
|
10755
|
+
// Clear the stashed selection so the next pinch captures fresh state.
|
|
10756
|
+
// This fixes Safari pinch zoom restoring outdated selections.
|
|
10757
|
+
this._selectedShapeIdsAtPointerDown = []
|
|
10758
|
+
|
|
10737
10759
|
break
|
|
10738
10760
|
}
|
|
10739
10761
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed, isUninitialized } from '@tldraw/state'
|
|
2
|
-
import { TLShapeId } from '@tldraw/tlschema'
|
|
2
|
+
import { TLShape, TLShapeId } from '@tldraw/tlschema'
|
|
3
3
|
import { Editor } from '../Editor'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -9,34 +9,56 @@ import { Editor } from '../Editor'
|
|
|
9
9
|
* @returns Incremental derivation of non visible shapes.
|
|
10
10
|
*/
|
|
11
11
|
export function notVisibleShapes(editor: Editor) {
|
|
12
|
+
const emptySet = new Set<TLShapeId>()
|
|
13
|
+
|
|
12
14
|
return computed<Set<TLShapeId>>('notVisibleShapes', function (prevValue) {
|
|
13
|
-
const
|
|
15
|
+
const allShapes = editor.getCurrentPageShapes()
|
|
14
16
|
const viewportPageBounds = editor.getViewportPageBounds()
|
|
15
17
|
const visibleIds = editor.getShapeIdsInsideBounds(viewportPageBounds)
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// Non-visible shapes are all shapes minus visible shapes
|
|
20
|
-
for (const id of allShapeIds) {
|
|
21
|
-
if (!visibleIds.has(id)) {
|
|
22
|
-
const shape = editor.getShape(id)
|
|
23
|
-
if (!shape) continue
|
|
19
|
+
let shape: TLShape | undefined
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
// Fast path: if all shapes are visible, return empty set
|
|
22
|
+
if (visibleIds.size === allShapes.length) {
|
|
23
|
+
if (isUninitialized(prevValue) || prevValue.size > 0) {
|
|
24
|
+
return emptySet
|
|
29
25
|
}
|
|
26
|
+
return prevValue
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
// First run: compute from scratch
|
|
30
|
+
if (isUninitialized(prevValue)) {
|
|
31
|
+
const nextValue = new Set<TLShapeId>()
|
|
32
|
+
for (let i = 0; i < allShapes.length; i++) {
|
|
33
|
+
shape = allShapes[i]
|
|
34
|
+
if (visibleIds.has(shape.id)) continue
|
|
35
|
+
if (!editor.getShapeUtil(shape.type).canCull(shape)) continue
|
|
36
|
+
nextValue.add(shape.id)
|
|
37
|
+
}
|
|
33
38
|
return nextValue
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
// Subsequent runs: single pass to collect IDs and detect changes
|
|
42
|
+
const notVisibleIds: TLShapeId[] = []
|
|
43
|
+
for (let i = 0; i < allShapes.length; i++) {
|
|
44
|
+
shape = allShapes[i]
|
|
45
|
+
if (visibleIds.has(shape.id)) continue
|
|
46
|
+
if (!editor.getShapeUtil(shape.type).canCull(shape)) continue
|
|
47
|
+
notVisibleIds.push(shape.id)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if the result changed
|
|
51
|
+
if (notVisibleIds.length === prevValue.size) {
|
|
52
|
+
let same = true
|
|
53
|
+
for (let i = 0; i < notVisibleIds.length; i++) {
|
|
54
|
+
if (!prevValue.has(notVisibleIds[i])) {
|
|
55
|
+
same = false
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (same) return prevValue
|
|
38
60
|
}
|
|
39
61
|
|
|
40
|
-
return
|
|
62
|
+
return new Set(notVisibleIds)
|
|
41
63
|
})
|
|
42
64
|
}
|
|
@@ -240,41 +240,6 @@ describe('EdgeScrollManager', () => {
|
|
|
240
240
|
})
|
|
241
241
|
})
|
|
242
242
|
|
|
243
|
-
describe('camera movement conditions', () => {
|
|
244
|
-
it('should not move camera when not dragging', () => {
|
|
245
|
-
editor.inputs.setIsDragging(false)
|
|
246
|
-
mockInputs.setCurrentScreenPoint(new Vec(5, 300))
|
|
247
|
-
|
|
248
|
-
edgeScrollManager.updateEdgeScrolling(300)
|
|
249
|
-
|
|
250
|
-
expect(editor.setCamera).not.toHaveBeenCalled()
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('should not move camera when panning', () => {
|
|
254
|
-
editor.inputs.setIsPanning(true)
|
|
255
|
-
mockInputs.setCurrentScreenPoint(new Vec(5, 300))
|
|
256
|
-
|
|
257
|
-
edgeScrollManager.updateEdgeScrolling(300)
|
|
258
|
-
|
|
259
|
-
expect(editor.setCamera).not.toHaveBeenCalled()
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
it('should not move camera when camera is locked', () => {
|
|
263
|
-
editor.getCameraOptions.mockReturnValue({
|
|
264
|
-
isLocked: true,
|
|
265
|
-
panSpeed: 1,
|
|
266
|
-
zoomSpeed: 1,
|
|
267
|
-
zoomSteps: [1],
|
|
268
|
-
wheelBehavior: 'pan' as const,
|
|
269
|
-
})
|
|
270
|
-
mockInputs.setCurrentScreenPoint(new Vec(5, 300))
|
|
271
|
-
|
|
272
|
-
edgeScrollManager.updateEdgeScrolling(300)
|
|
273
|
-
|
|
274
|
-
expect(editor.setCamera).not.toHaveBeenCalled()
|
|
275
|
-
})
|
|
276
|
-
})
|
|
277
|
-
|
|
278
243
|
describe('camera movement calculation', () => {
|
|
279
244
|
it('should calculate scroll speed based on user preference', () => {
|
|
280
245
|
editor.user.getEdgeScrollSpeed.mockReturnValue(2)
|
|
@@ -21,6 +21,9 @@ export class EdgeScrollManager {
|
|
|
21
21
|
*/
|
|
22
22
|
updateEdgeScrolling(elapsed: number) {
|
|
23
23
|
const { editor } = this
|
|
24
|
+
|
|
25
|
+
if (editor.getCameraOptions().isLocked) return
|
|
26
|
+
|
|
24
27
|
const edgeScrollProximityFactor = this.getEdgeScroll()
|
|
25
28
|
if (edgeScrollProximityFactor.x === 0 && edgeScrollProximityFactor.y === 0) {
|
|
26
29
|
if (this._isEdgeScrolling) {
|
|
@@ -106,15 +109,8 @@ export class EdgeScrollManager {
|
|
|
106
109
|
* @public
|
|
107
110
|
*/
|
|
108
111
|
private moveCameraWhenCloseToEdge(proximityFactor: { x: number; y: number }) {
|
|
109
|
-
const { editor } = this
|
|
110
|
-
if (
|
|
111
|
-
!editor.inputs.getIsDragging() ||
|
|
112
|
-
editor.inputs.getIsPanning() ||
|
|
113
|
-
editor.getCameraOptions().isLocked
|
|
114
|
-
)
|
|
115
|
-
return
|
|
116
|
-
|
|
117
112
|
if (proximityFactor.x === 0 && proximityFactor.y === 0) return
|
|
113
|
+
const { editor } = this
|
|
118
114
|
|
|
119
115
|
const screenBounds = editor.getViewportScreenBounds()
|
|
120
116
|
|
|
@@ -35,17 +35,31 @@ export interface TLShapeUtilConstructor<T extends TLShape, U extends ShapeUtil<T
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Options passed to {@link ShapeUtil.canBind}. A binding that could be made. At least one of
|
|
38
|
-
* `
|
|
38
|
+
* `fromShape` or `toShape` will belong to this shape util.
|
|
39
|
+
*
|
|
40
|
+
* The shapes may be full {@link @tldraw/tlschema#TLShape} objects when available, or just
|
|
41
|
+
* `{ type }` stubs when the shape hasn't been created yet (e.g. during arrow creation). Use
|
|
42
|
+
* `'id' in shape` to check whether the full shape is available.
|
|
39
43
|
*
|
|
40
44
|
* @public
|
|
41
45
|
*/
|
|
42
46
|
export interface TLShapeUtilCanBindOpts<Shape extends TLShape = TLShape> {
|
|
43
|
-
/** The
|
|
44
|
-
|
|
45
|
-
/** The
|
|
46
|
-
|
|
47
|
+
/** The shape referenced by the `fromId` of the binding, or a `{ type }` stub if unavailable. */
|
|
48
|
+
fromShape: TLShape | { type: TLShape['type'] }
|
|
49
|
+
/** The shape referenced by the `toId` of the binding, or a `{ type }` stub if unavailable. */
|
|
50
|
+
toShape: TLShape | { type: TLShape['type'] }
|
|
47
51
|
/** The type of binding. */
|
|
48
52
|
bindingType: string
|
|
53
|
+
/**
|
|
54
|
+
* The type of shape referenced by the `fromId` of the binding.
|
|
55
|
+
* @deprecated Use `fromShape.type` instead.
|
|
56
|
+
*/
|
|
57
|
+
fromShapeType: TLShape['type']
|
|
58
|
+
/**
|
|
59
|
+
* The type of shape referenced by the `toId` of the binding.
|
|
60
|
+
* @deprecated Use `toShape.type` instead.
|
|
61
|
+
*/
|
|
62
|
+
toShapeType: TLShape['type']
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
/**
|