@tldraw/editor 3.16.0-canary.322bcb9a6f7c → 3.16.0-canary.376ee1848cc0

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.
Files changed (109) hide show
  1. package/dist-cjs/index.d.ts +59 -3
  2. package/dist-cjs/index.js +6 -2
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +2 -2
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +11 -1
  7. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  8. package/dist-cjs/lib/editor/Editor.js +9 -4
  9. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  10. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +4 -0
  11. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
  12. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +10 -0
  13. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  14. package/dist-cjs/lib/hooks/useCanvasEvents.js +15 -12
  15. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  16. package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
  17. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  18. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
  19. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  20. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  21. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  22. package/dist-cjs/lib/hooks/useHandleEvents.js +3 -3
  23. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  24. package/dist-cjs/lib/hooks/useSelectionEvents.js +4 -4
  25. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  26. package/dist-cjs/lib/license/LicenseManager.js +133 -38
  27. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  28. package/dist-cjs/lib/license/LicenseProvider.js +36 -3
  29. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  30. package/dist-cjs/lib/license/Watermark.js +65 -3
  31. package/dist-cjs/lib/license/Watermark.js.map +3 -3
  32. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +24 -2
  33. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  34. package/dist-cjs/lib/primitives/geometry/Group2d.js +5 -1
  35. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  36. package/dist-cjs/lib/utils/dom.js +12 -1
  37. package/dist-cjs/lib/utils/dom.js.map +2 -2
  38. package/dist-cjs/lib/utils/getPointerInfo.js +2 -2
  39. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  40. package/dist-cjs/version.js +3 -3
  41. package/dist-cjs/version.js.map +1 -1
  42. package/dist-esm/index.d.mts +59 -3
  43. package/dist-esm/index.mjs +9 -3
  44. package/dist-esm/index.mjs.map +2 -2
  45. package/dist-esm/lib/TldrawEditor.mjs +3 -3
  46. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  47. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +12 -2
  48. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  49. package/dist-esm/lib/editor/Editor.mjs +9 -4
  50. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  51. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +4 -0
  52. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
  53. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +10 -0
  54. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  55. package/dist-esm/lib/hooks/useCanvasEvents.mjs +17 -13
  56. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  57. package/dist-esm/lib/hooks/useDocumentEvents.mjs +11 -6
  58. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  59. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +2 -3
  60. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  61. package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
  62. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  63. package/dist-esm/lib/hooks/useHandleEvents.mjs +9 -4
  64. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  65. package/dist-esm/lib/hooks/useSelectionEvents.mjs +6 -5
  66. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  67. package/dist-esm/lib/license/LicenseManager.mjs +134 -39
  68. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  69. package/dist-esm/lib/license/LicenseProvider.mjs +36 -4
  70. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  71. package/dist-esm/lib/license/Watermark.mjs +66 -4
  72. package/dist-esm/lib/license/Watermark.mjs.map +3 -3
  73. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +24 -2
  74. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  75. package/dist-esm/lib/primitives/geometry/Group2d.mjs +5 -1
  76. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  77. package/dist-esm/lib/utils/dom.mjs +12 -1
  78. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  79. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -2
  80. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  81. package/dist-esm/version.mjs +3 -3
  82. package/dist-esm/version.mjs.map +1 -1
  83. package/editor.css +8 -3
  84. package/package.json +7 -7
  85. package/src/index.ts +3 -0
  86. package/src/lib/TldrawEditor.tsx +3 -4
  87. package/src/lib/components/default-components/DefaultCanvas.tsx +8 -2
  88. package/src/lib/editor/Editor.test.ts +90 -0
  89. package/src/lib/editor/Editor.ts +16 -4
  90. package/src/lib/editor/derivations/notVisibleShapes.ts +6 -0
  91. package/src/lib/editor/shapes/ShapeUtil.ts +11 -0
  92. package/src/lib/hooks/useCanvasEvents.ts +17 -11
  93. package/src/lib/hooks/useDocumentEvents.ts +11 -6
  94. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +2 -2
  95. package/src/lib/hooks/useGestureEvents.ts +2 -2
  96. package/src/lib/hooks/useHandleEvents.ts +9 -4
  97. package/src/lib/hooks/useSelectionEvents.ts +6 -5
  98. package/src/lib/license/LicenseManager.test.ts +719 -387
  99. package/src/lib/license/LicenseManager.ts +187 -49
  100. package/src/lib/license/LicenseProvider.tsx +69 -5
  101. package/src/lib/license/Watermark.tsx +71 -4
  102. package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
  103. package/src/lib/primitives/geometry/Geometry2d.ts +29 -2
  104. package/src/lib/primitives/geometry/Group2d.ts +6 -1
  105. package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
  106. package/src/lib/utils/dom.test.ts +94 -0
  107. package/src/lib/utils/dom.ts +38 -1
  108. package/src/lib/utils/getPointerInfo.ts +2 -1
  109. package/src/version.ts +3 -3
@@ -1,8 +1,8 @@
1
- const version = "3.16.0-canary.322bcb9a6f7c";
1
+ const version = "3.16.0-canary.376ee1848cc0";
2
2
  const publishDates = {
3
3
  major: "2024-09-13T14:36:29.063Z",
4
- minor: "2025-09-05T08:45:56.996Z",
5
- patch: "2025-09-05T08:45:56.996Z"
4
+ minor: "2025-09-16T11:52:54.438Z",
5
+ patch: "2025-09-16T11:52:54.438Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -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.16.0-canary.322bcb9a6f7c'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-09-05T08:45:56.996Z',\n\tpatch: '2025-09-05T08:45:56.996Z',\n}\n"],
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.16.0-canary.376ee1848cc0'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-09-16T11:52:54.438Z',\n\tpatch: '2025-09-16T11:52:54.438Z',\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
@@ -689,11 +689,17 @@ input,
689
689
  }
690
690
 
691
691
  .tl-text-measure {
692
- position: absolute;
693
692
  z-index: var(--tl-layer-canvas-hidden);
693
+ opacity: 0;
694
+ visibility: hidden;
695
+
696
+ /* pointer-events: all; */
697
+ /* opacity: 1; */
698
+ /* z-index: 99999; */
699
+
700
+ position: absolute;
694
701
  top: 0px;
695
702
  left: 0px;
696
- opacity: 0;
697
703
  width: max-content;
698
704
  box-sizing: border-box;
699
705
  pointer-events: none;
@@ -704,7 +710,6 @@ input,
704
710
  border: none;
705
711
  user-select: none;
706
712
  contain: style paint;
707
- visibility: hidden;
708
713
  /* N.B. This property, while discouraged ("intended for Document Type Definition (DTD) designers") is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs. */
709
714
  unicode-bidi: plaintext;
710
715
  -webkit-user-select: none;
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": "3.16.0-canary.322bcb9a6f7c",
4
+ "version": "3.16.0-canary.376ee1848cc0",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -50,12 +50,12 @@
50
50
  "@tiptap/core": "^2.9.1",
51
51
  "@tiptap/pm": "^2.9.1",
52
52
  "@tiptap/react": "^2.9.1",
53
- "@tldraw/state": "3.16.0-canary.322bcb9a6f7c",
54
- "@tldraw/state-react": "3.16.0-canary.322bcb9a6f7c",
55
- "@tldraw/store": "3.16.0-canary.322bcb9a6f7c",
56
- "@tldraw/tlschema": "3.16.0-canary.322bcb9a6f7c",
57
- "@tldraw/utils": "3.16.0-canary.322bcb9a6f7c",
58
- "@tldraw/validate": "3.16.0-canary.322bcb9a6f7c",
53
+ "@tldraw/state": "3.16.0-canary.376ee1848cc0",
54
+ "@tldraw/state-react": "3.16.0-canary.376ee1848cc0",
55
+ "@tldraw/store": "3.16.0-canary.376ee1848cc0",
56
+ "@tldraw/tlschema": "3.16.0-canary.376ee1848cc0",
57
+ "@tldraw/utils": "3.16.0-canary.376ee1848cc0",
58
+ "@tldraw/validate": "3.16.0-canary.376ee1848cc0",
59
59
  "@types/core-js": "^2.5.8",
60
60
  "@use-gesture/react": "^10.3.1",
61
61
  "classnames": "^2.5.1",
package/src/index.ts CHANGED
@@ -334,6 +334,7 @@ export {
334
334
  type TestEnvironment,
335
335
  type ValidLicenseKeyResult,
336
336
  } from './lib/license/LicenseManager'
337
+ export { LICENSE_TIMEOUT } from './lib/license/LicenseProvider'
337
338
  export { defaultTldrawOptions, type TldrawOptions } from './lib/options'
338
339
  export {
339
340
  Box,
@@ -446,10 +447,12 @@ export {
446
447
  export {
447
448
  activeElementShouldCaptureKeys,
448
449
  loopToHtmlElement,
450
+ markEventAsHandled,
449
451
  preventDefault,
450
452
  releasePointerCapture,
451
453
  setPointerCapture,
452
454
  stopEventPropagation,
455
+ wasEventAlreadyHandled,
453
456
  } from './lib/utils/dom'
454
457
  export { EditorAtom } from './lib/utils/EditorAtom'
455
458
  export { getIncrementedName } from './lib/utils/getIncrementedName'
@@ -1,6 +1,7 @@
1
1
  import { MigrationSequence, Store } from '@tldraw/store'
2
2
  import { TLShape, TLStore, TLStoreSnapshot } from '@tldraw/tlschema'
3
3
  import { annotateError, Required } from '@tldraw/utils'
4
+ import classNames from 'classnames'
4
5
  import React, {
5
6
  memo,
6
7
  ReactNode,
@@ -12,8 +13,6 @@ import React, {
12
13
  useState,
13
14
  useSyncExternalStore,
14
15
  } from 'react'
15
-
16
- import classNames from 'classnames'
17
16
  import { version } from '../version'
18
17
  import { DefaultErrorFallback } from './components/default-components/DefaultErrorFallback'
19
18
  import { OptionalErrorBoundary } from './components/ErrorBoundary'
@@ -45,7 +44,7 @@ import { LicenseProvider } from './license/LicenseProvider'
45
44
  import { Watermark } from './license/Watermark'
46
45
  import { TldrawOptions } from './options'
47
46
  import { TLDeepLinkOptions } from './utils/deepLinks'
48
- import { stopEventPropagation } from './utils/dom'
47
+ import { markEventAsHandled } from './utils/dom'
49
48
  import { TLTextOptions } from './utils/richText'
50
49
  import { TLStoreWithStatus } from './utils/sync/StoreWithStatus'
51
50
 
@@ -276,7 +275,7 @@ export const TldrawEditor = memo(function TldrawEditor({
276
275
  data-tldraw={version}
277
276
  draggable={false}
278
277
  className={classNames(`${TL_CONTAINER_CLASS} tl-theme__light`, className)}
279
- onPointerDown={stopEventPropagation}
278
+ onPointerDown={markEventAsHandled}
280
279
  tabIndex={-1}
281
280
  role="application"
282
281
  aria-label={_options?.branding ?? 'tldraw'}
@@ -21,7 +21,7 @@ import { Mat } from '../../primitives/Mat'
21
21
  import { Vec } from '../../primitives/Vec'
22
22
  import { toDomPrecision } from '../../primitives/utils'
23
23
  import { debugFlags } from '../../utils/debug-flags'
24
- import { setStyleProperty } from '../../utils/dom'
24
+ import { markEventAsHandled, setStyleProperty } from '../../utils/dom'
25
25
  import { GeometryDebuggingView } from '../GeometryDebuggingView'
26
26
  import { LiveCollaborators } from '../LiveCollaborators'
27
27
  import { MenuClickCapture } from '../MenuClickCapture'
@@ -172,7 +172,13 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
172
172
  <LiveCollaborators />
173
173
  </div>
174
174
  </div>
175
- <div className="tl-canvas__in-front">
175
+ <div
176
+ className="tl-canvas__in-front"
177
+ onPointerDown={markEventAsHandled}
178
+ onPointerUp={markEventAsHandled}
179
+ onTouchStart={markEventAsHandled}
180
+ onTouchEnd={markEventAsHandled}
181
+ >
176
182
  <InFrontOfTheCanvasWrapper />
177
183
  </div>
178
184
  <MovingCameraHitTestBlocker />
@@ -833,3 +833,93 @@ describe('selectAll', () => {
833
833
  setSelectedShapesSpy.mockRestore()
834
834
  })
835
835
  })
836
+
837
+ describe('putExternalContent', () => {
838
+ let mockHandler: any
839
+
840
+ beforeEach(() => {
841
+ mockHandler = vi.fn()
842
+ editor.registerExternalContentHandler('text', mockHandler)
843
+ })
844
+
845
+ it('calls external content handler when not readonly', async () => {
846
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
847
+
848
+ const info = { type: 'text' as const, text: 'test-data' }
849
+ await editor.putExternalContent(info)
850
+
851
+ expect(mockHandler).toHaveBeenCalledWith(info)
852
+ })
853
+
854
+ it('does not call external content handler when readonly', async () => {
855
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
856
+
857
+ const info = { type: 'text' as const, text: 'test-data' }
858
+ await editor.putExternalContent(info)
859
+
860
+ expect(mockHandler).not.toHaveBeenCalled()
861
+ })
862
+
863
+ it('calls external content handler when readonly but force is true', async () => {
864
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
865
+
866
+ const info = { type: 'text' as const, text: 'test-data' }
867
+ await editor.putExternalContent(info, { force: true })
868
+
869
+ expect(mockHandler).toHaveBeenCalledWith(info)
870
+ })
871
+
872
+ it('calls external content handler when force is false and not readonly', async () => {
873
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
874
+
875
+ const info = { type: 'text' as const, text: 'test-data' }
876
+ await editor.putExternalContent(info, { force: false })
877
+
878
+ expect(mockHandler).toHaveBeenCalledWith(info)
879
+ })
880
+ })
881
+
882
+ describe('replaceExternalContent', () => {
883
+ let mockHandler: any
884
+
885
+ beforeEach(() => {
886
+ mockHandler = vi.fn()
887
+ editor.registerExternalContentHandler('text', mockHandler)
888
+ })
889
+
890
+ it('calls external content handler when not readonly', async () => {
891
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
892
+
893
+ const info = { type: 'text' as const, text: 'test-data' }
894
+ await editor.replaceExternalContent(info)
895
+
896
+ expect(mockHandler).toHaveBeenCalledWith(info)
897
+ })
898
+
899
+ it('does not call external content handler when readonly', async () => {
900
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
901
+
902
+ const info = { type: 'text' as const, text: 'test-data' }
903
+ await editor.replaceExternalContent(info)
904
+
905
+ expect(mockHandler).not.toHaveBeenCalled()
906
+ })
907
+
908
+ it('calls external content handler when readonly but force is true', async () => {
909
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
910
+
911
+ const info = { type: 'text' as const, text: 'test-data' }
912
+ await editor.replaceExternalContent(info, { force: true })
913
+
914
+ expect(mockHandler).toHaveBeenCalledWith(info)
915
+ })
916
+
917
+ it('calls external content handler when force is false and not readonly', async () => {
918
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
919
+
920
+ const info = { type: 'text' as const, text: 'test-data' }
921
+ await editor.replaceExternalContent(info, { force: false })
922
+
923
+ expect(mockHandler).toHaveBeenCalledWith(info)
924
+ })
925
+ })
@@ -4680,8 +4680,10 @@ export class Editor extends EventEmitter<TLEventMap> {
4680
4680
  return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
4681
4681
  const pageTransform = this.getShapePageTransform(shape)
4682
4682
  if (!pageTransform) return undefined
4683
- const geometry = this.getShapeGeometry(shape)
4684
- return Box.FromPoints(pageTransform.applyToPoints(geometry.vertices))
4683
+
4684
+ return Box.FromPoints(
4685
+ pageTransform.applyToPoints(this.getShapeGeometry(shape).boundsVertices)
4686
+ )
4685
4687
  })
4686
4688
  }
4687
4689
 
@@ -8831,8 +8833,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8831
8833
  * Handle external content, such as files, urls, embeds, or plain text which has been put into the app, for example by pasting external text or dropping external images onto canvas.
8832
8834
  *
8833
8835
  * @param info - Info about the external content.
8836
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8834
8837
  */
8835
- async putExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8838
+ async putExternalContent<E>(
8839
+ info: TLExternalContent<E>,
8840
+ opts = {} as { force?: boolean }
8841
+ ): Promise<void> {
8842
+ if (!opts.force && this.getIsReadonly()) return
8836
8843
  return this.externalContentHandlers[info.type]?.(info as any)
8837
8844
  }
8838
8845
 
@@ -8840,8 +8847,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8840
8847
  * Handle replacing external content.
8841
8848
  *
8842
8849
  * @param info - Info about the external content.
8850
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8843
8851
  */
8844
- async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8852
+ async replaceExternalContent<E>(
8853
+ info: TLExternalContent<E>,
8854
+ opts = {} as { force?: boolean }
8855
+ ): Promise<void> {
8856
+ if (!opts.force && this.getIsReadonly()) return
8845
8857
  return this.externalContentHandlers[info.type]?.(info as any)
8846
8858
  }
8847
8859
 
@@ -7,6 +7,12 @@ function fromScratch(editor: Editor): Set<TLShapeId> {
7
7
  const viewportPageBounds = editor.getViewportPageBounds()
8
8
  const notVisibleShapes = new Set<TLShapeId>()
9
9
  shapesIds.forEach((id) => {
10
+ const shape = editor.getShape(id)
11
+ if (!shape) return
12
+
13
+ const canCull = editor.getShapeUtil(shape.type).canCull(shape)
14
+ if (!canCull) return
15
+
10
16
  // If the shape is fully outside of the viewport page bounds, add it to the set.
11
17
  // We'll ignore masks here, since they're more expensive to compute and the overhead is not worth it.
12
18
  const pageBounds = editor.getShapePageBounds(id)
@@ -283,6 +283,17 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
283
283
  return true
284
284
  }
285
285
 
286
+ /**
287
+ * Whether this shape can be culled. By default, shapes are culled for
288
+ * performance reasons when they are outside of the viewport. Culled shapes are still rendered
289
+ * to the DOM, but have their `display` property set to `none`.
290
+ *
291
+ * @param shape - The shape.
292
+ */
293
+ canCull(_shape: Shape): boolean {
294
+ return true
295
+ }
296
+
286
297
  /**
287
298
  * Does this shape provide a background for its children? If this is true,
288
299
  * then any children with a `renderBackground` method will have their
@@ -2,10 +2,11 @@ import { useValue } from '@tldraw/state-react'
2
2
  import React, { useEffect, useMemo } from 'react'
3
3
  import { RIGHT_MOUSE_BUTTON } from '../constants'
4
4
  import {
5
+ markEventAsHandled,
5
6
  preventDefault,
6
7
  releasePointerCapture,
7
8
  setPointerCapture,
8
- stopEventPropagation,
9
+ wasEventAlreadyHandled,
9
10
  } from '../utils/dom'
10
11
  import { getPointerInfo } from '../utils/getPointerInfo'
11
12
  import { useEditor } from './useEditor'
@@ -17,7 +18,7 @@ export function useCanvasEvents() {
17
18
  const events = useMemo(
18
19
  function canvasEvents() {
19
20
  function onPointerDown(e: React.PointerEvent) {
20
- if ((e as any).isKilled) return
21
+ if (wasEventAlreadyHandled(e)) return
21
22
 
22
23
  if (e.button === RIGHT_MOUSE_BUTTON) {
23
24
  editor.dispatch({
@@ -42,7 +43,7 @@ export function useCanvasEvents() {
42
43
  }
43
44
 
44
45
  function onPointerUp(e: React.PointerEvent) {
45
- if ((e as any).isKilled) return
46
+ if (wasEventAlreadyHandled(e)) return
46
47
  if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
47
48
 
48
49
  releasePointerCapture(e.currentTarget, e)
@@ -56,26 +57,28 @@ export function useCanvasEvents() {
56
57
  }
57
58
 
58
59
  function onPointerEnter(e: React.PointerEvent) {
59
- if ((e as any).isKilled) return
60
+ if (wasEventAlreadyHandled(e)) return
60
61
  if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
61
62
  const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
62
63
  editor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })
63
64
  }
64
65
 
65
66
  function onPointerLeave(e: React.PointerEvent) {
66
- if ((e as any).isKilled) return
67
+ if (wasEventAlreadyHandled(e)) return
67
68
  if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
68
69
  const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
69
70
  editor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })
70
71
  }
71
72
 
72
73
  function onTouchStart(e: React.TouchEvent) {
73
- ;(e as any).isKilled = true
74
+ if (wasEventAlreadyHandled(e)) return
75
+ markEventAsHandled(e)
74
76
  preventDefault(e)
75
77
  }
76
78
 
77
79
  function onTouchEnd(e: React.TouchEvent) {
78
- ;(e as any).isKilled = true
80
+ if (wasEventAlreadyHandled(e)) return
81
+ markEventAsHandled(e)
79
82
  // check that e.target is an HTMLElement
80
83
  if (!(e.target instanceof HTMLElement)) return
81
84
 
@@ -94,12 +97,14 @@ export function useCanvasEvents() {
94
97
  }
95
98
 
96
99
  function onDragOver(e: React.DragEvent<Element>) {
100
+ if (wasEventAlreadyHandled(e)) return
97
101
  preventDefault(e)
98
102
  }
99
103
 
100
104
  async function onDrop(e: React.DragEvent<Element>) {
105
+ if (wasEventAlreadyHandled(e)) return
101
106
  preventDefault(e)
102
- stopEventPropagation(e)
107
+ e.stopPropagation()
103
108
 
104
109
  if (e.dataTransfer?.files?.length) {
105
110
  const files = Array.from(e.dataTransfer.files)
@@ -124,7 +129,8 @@ export function useCanvasEvents() {
124
129
  }
125
130
 
126
131
  function onClick(e: React.MouseEvent) {
127
- stopEventPropagation(e)
132
+ if (wasEventAlreadyHandled(e)) return
133
+ e.stopPropagation()
128
134
  }
129
135
 
130
136
  return {
@@ -151,8 +157,8 @@ export function useCanvasEvents() {
151
157
  let lastX: number, lastY: number
152
158
 
153
159
  function onPointerMove(e: PointerEvent) {
154
- if ((e as any).isKilled) return
155
- ;(e as any).isKilled = true
160
+ if (wasEventAlreadyHandled(e)) return
161
+ markEventAsHandled(e)
156
162
 
157
163
  if (e.clientX === lastX && e.clientY === lastY) return
158
164
  lastX = e.clientX
@@ -2,7 +2,12 @@ import { useValue } from '@tldraw/state-react'
2
2
  import { useEffect } from 'react'
3
3
  import { Editor } from '../editor/Editor'
4
4
  import { TLKeyboardEventInfo } from '../editor/types/event-types'
5
- import { activeElementShouldCaptureKeys, preventDefault, stopEventPropagation } from '../utils/dom'
5
+ import {
6
+ activeElementShouldCaptureKeys,
7
+ markEventAsHandled,
8
+ preventDefault,
9
+ wasEventAlreadyHandled,
10
+ } from '../utils/dom'
6
11
  import { isAccelKey } from '../utils/keyboard'
7
12
  import { useContainer } from './useContainer'
8
13
  import { useEditor } from './useEditor'
@@ -29,7 +34,7 @@ export function useDocumentEvents() {
29
34
  // re-dispatched, which would lead to an infinite loop.
30
35
  if ((e as any).isSpecialRedispatchedEvent) return
31
36
  preventDefault(e)
32
- stopEventPropagation(e)
37
+ e.stopPropagation()
33
38
  const cvs = container.querySelector('.tl-canvas')
34
39
  if (!cvs) return
35
40
  const newEvent = new DragEvent(e.type, e)
@@ -103,8 +108,8 @@ export function useDocumentEvents() {
103
108
  preventDefault(e)
104
109
  }
105
110
 
106
- if ((e as any).isKilled) return
107
- ;(e as any).isKilled = true
111
+ if (wasEventAlreadyHandled(e)) return
112
+ markEventAsHandled(e)
108
113
  const hasSelectedShapes = !!editor.getSelectedShapeIds().length
109
114
 
110
115
  switch (e.key) {
@@ -211,8 +216,8 @@ export function useDocumentEvents() {
211
216
  }
212
217
 
213
218
  const handleKeyUp = (e: KeyboardEvent) => {
214
- if ((e as any).isKilled) return
215
- ;(e as any).isKilled = true
219
+ if (wasEventAlreadyHandled(e)) return
220
+ markEventAsHandled(e)
216
221
 
217
222
  if (areShortcutsDisabled(editor)) {
218
223
  return
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from 'react'
2
- import { preventDefault } from '../utils/dom'
2
+ import { markEventAsHandled, preventDefault } from '../utils/dom'
3
3
  import { useEditor } from './useEditor'
4
4
 
5
5
  const IGNORED_TAGS = ['textarea', 'input']
@@ -19,7 +19,7 @@ export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLE
19
19
 
20
20
  const handleEvent = (e: PointerEvent | TouchEvent) => {
21
21
  if (e instanceof PointerEvent && e.pointerType === 'pen') {
22
- ;(e as any).isKilled = true
22
+ markEventAsHandled(e)
23
23
  const { target } = e
24
24
 
25
25
  // Allow events to propagate if the app is editing a shape, or if the event is occurring in a text area or input
@@ -3,7 +3,7 @@ import { createUseGesture, pinchAction, wheelAction } from '@use-gesture/react'
3
3
  import * as React from 'react'
4
4
  import { TLWheelEventInfo } from '../editor/types/event-types'
5
5
  import { Vec } from '../primitives/Vec'
6
- import { preventDefault, stopEventPropagation } from '../utils/dom'
6
+ import { preventDefault } from '../utils/dom'
7
7
  import { isAccelKey } from '../utils/keyboard'
8
8
  import { normalizeWheel } from '../utils/normalizeWheel'
9
9
  import { useEditor } from './useEditor'
@@ -113,7 +113,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
113
113
  }
114
114
 
115
115
  preventDefault(event)
116
- stopEventPropagation(event)
116
+ event.stopPropagation()
117
117
  const delta = normalizeWheel(event)
118
118
 
119
119
  if (delta.x === 0 && delta.y === 0) return
@@ -1,7 +1,12 @@
1
1
  import { TLArrowShape, TLLineShape, TLShapeId } from '@tldraw/tlschema'
2
2
  import * as React from 'react'
3
3
  import { Editor } from '../editor/Editor'
4
- import { loopToHtmlElement, releasePointerCapture, setPointerCapture } from '../utils/dom'
4
+ import {
5
+ loopToHtmlElement,
6
+ releasePointerCapture,
7
+ setPointerCapture,
8
+ wasEventAlreadyHandled,
9
+ } from '../utils/dom'
5
10
  import { getPointerInfo } from '../utils/getPointerInfo'
6
11
  import { useEditor } from './useEditor'
7
12
 
@@ -16,7 +21,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
16
21
 
17
22
  return React.useMemo(() => {
18
23
  const onPointerDown = (e: React.PointerEvent) => {
19
- if ((e as any).isKilled) return
24
+ if (wasEventAlreadyHandled(e)) return
20
25
 
21
26
  // Must set pointer capture on an HTML element!
22
27
  const target = loopToHtmlElement(e.currentTarget)
@@ -40,7 +45,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
40
45
  let lastX: number, lastY: number
41
46
 
42
47
  const onPointerMove = (e: React.PointerEvent) => {
43
- if ((e as any).isKilled) return
48
+ if (wasEventAlreadyHandled(e)) return
44
49
  if (e.clientX === lastX && e.clientY === lastY) return
45
50
  lastX = e.clientX
46
51
  lastY = e.clientY
@@ -60,7 +65,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
60
65
  }
61
66
 
62
67
  const onPointerUp = (e: React.PointerEvent) => {
63
- if ((e as any).isKilled) return
68
+ if (wasEventAlreadyHandled(e)) return
64
69
 
65
70
  const target = loopToHtmlElement(e.currentTarget)
66
71
  releasePointerCapture(target, e)
@@ -3,9 +3,10 @@ import { RIGHT_MOUSE_BUTTON } from '../constants'
3
3
  import { TLSelectionHandle } from '../editor/types/selection-types'
4
4
  import {
5
5
  loopToHtmlElement,
6
+ markEventAsHandled,
6
7
  releasePointerCapture,
7
8
  setPointerCapture,
8
- stopEventPropagation,
9
+ wasEventAlreadyHandled,
9
10
  } from '../utils/dom'
10
11
  import { getPointerInfo } from '../utils/getPointerInfo'
11
12
  import { useEditor } from './useEditor'
@@ -17,7 +18,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
17
18
  const events = useMemo(
18
19
  function selectionEvents() {
19
20
  const onPointerDown: React.PointerEventHandler = (e) => {
20
- if ((e as any).isKilled) return
21
+ if (wasEventAlreadyHandled(e)) return
21
22
 
22
23
  if (e.button === RIGHT_MOUSE_BUTTON) {
23
24
  editor.dispatch({
@@ -54,14 +55,14 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
54
55
  handle,
55
56
  ...getPointerInfo(e),
56
57
  })
57
- stopEventPropagation(e)
58
+ markEventAsHandled(e)
58
59
  }
59
60
 
60
61
  // Track the last screen point
61
62
  let lastX: number, lastY: number
62
63
 
63
64
  function onPointerMove(e: React.PointerEvent) {
64
- if ((e as any).isKilled) return
65
+ if (wasEventAlreadyHandled(e)) return
65
66
  if (e.button !== 0) return
66
67
  if (e.clientX === lastX && e.clientY === lastY) return
67
68
  lastX = e.clientX
@@ -77,7 +78,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
77
78
  }
78
79
 
79
80
  const onPointerUp: React.PointerEventHandler = (e) => {
80
- if ((e as any).isKilled) return
81
+ if (wasEventAlreadyHandled(e)) return
81
82
  if (e.button !== 0) return
82
83
 
83
84
  editor.dispatch({