@tldraw/editor 3.9.0-canary.fae364b5aba5 → 3.9.0-internal.7f0e15f4f7d9

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 (132) hide show
  1. package/dist-cjs/index.d.ts +228 -3
  2. package/dist-cjs/index.js +9 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +33 -6
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/Shape.js +7 -0
  7. package/dist-cjs/lib/components/Shape.js.map +2 -2
  8. package/dist-cjs/lib/editor/Editor.js +63 -8
  9. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  10. package/dist-cjs/lib/editor/managers/FontManager.js +167 -0
  11. package/dist-cjs/lib/editor/managers/FontManager.js.map +7 -0
  12. package/dist-cjs/lib/editor/managers/TextManager.js +23 -17
  13. package/dist-cjs/lib/editor/managers/TextManager.js.map +2 -2
  14. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +11 -0
  15. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  16. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  17. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  18. package/dist-cjs/lib/exports/FontEmbedder.js +7 -2
  19. package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
  20. package/dist-cjs/lib/exports/StyleEmbedder.js +1 -1
  21. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  22. package/dist-cjs/lib/exports/exportToSvg.js +3 -2
  23. package/dist-cjs/lib/exports/exportToSvg.js.map +2 -2
  24. package/dist-cjs/lib/exports/getSvgAsImage.js +1 -1
  25. package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
  26. package/dist-cjs/lib/exports/getSvgJsx.js +18 -1
  27. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  28. package/dist-cjs/lib/exports/parseCss.js +1 -0
  29. package/dist-cjs/lib/exports/parseCss.js.map +2 -2
  30. package/dist-cjs/lib/globals/environment.js +3 -1
  31. package/dist-cjs/lib/globals/environment.js.map +2 -2
  32. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -2
  33. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  34. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -1
  35. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  36. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +48 -0
  37. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +7 -0
  38. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  39. package/dist-cjs/lib/hooks/useViewportHeight.js +56 -0
  40. package/dist-cjs/lib/hooks/useViewportHeight.js.map +7 -0
  41. package/dist-cjs/lib/license/LicenseManager.js +1 -1
  42. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  43. package/dist-cjs/lib/options.js +2 -1
  44. package/dist-cjs/lib/options.js.map +2 -2
  45. package/dist-cjs/lib/utils/browserCanvasMaxSize.js +104 -28
  46. package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +3 -3
  47. package/dist-cjs/lib/utils/dom.js +1 -1
  48. package/dist-cjs/lib/utils/dom.js.map +2 -2
  49. package/dist-cjs/lib/utils/richText.js +46 -0
  50. package/dist-cjs/lib/utils/richText.js.map +7 -0
  51. package/dist-cjs/version.js +3 -3
  52. package/dist-cjs/version.js.map +1 -1
  53. package/dist-esm/index.d.mts +228 -3
  54. package/dist-esm/index.mjs +13 -1
  55. package/dist-esm/index.mjs.map +2 -2
  56. package/dist-esm/lib/TldrawEditor.mjs +34 -7
  57. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  58. package/dist-esm/lib/components/Shape.mjs +8 -1
  59. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  60. package/dist-esm/lib/editor/Editor.mjs +71 -9
  61. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  62. package/dist-esm/lib/editor/managers/FontManager.mjs +153 -0
  63. package/dist-esm/lib/editor/managers/FontManager.mjs.map +7 -0
  64. package/dist-esm/lib/editor/managers/TextManager.mjs +23 -17
  65. package/dist-esm/lib/editor/managers/TextManager.mjs.map +2 -2
  66. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +11 -0
  67. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  68. package/dist-esm/lib/exports/FontEmbedder.mjs +7 -2
  69. package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
  70. package/dist-esm/lib/exports/StyleEmbedder.mjs +1 -1
  71. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  72. package/dist-esm/lib/exports/exportToSvg.mjs +3 -2
  73. package/dist-esm/lib/exports/exportToSvg.mjs.map +2 -2
  74. package/dist-esm/lib/exports/getSvgAsImage.mjs +1 -1
  75. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
  76. package/dist-esm/lib/exports/getSvgJsx.mjs +19 -2
  77. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  78. package/dist-esm/lib/exports/parseCss.mjs +1 -0
  79. package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
  80. package/dist-esm/lib/globals/environment.mjs +3 -1
  81. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  82. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -2
  83. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  84. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -1
  85. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  86. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +28 -0
  87. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +7 -0
  88. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  89. package/dist-esm/lib/hooks/useViewportHeight.mjs +36 -0
  90. package/dist-esm/lib/hooks/useViewportHeight.mjs.map +7 -0
  91. package/dist-esm/lib/license/LicenseManager.mjs +1 -1
  92. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  93. package/dist-esm/lib/options.mjs +2 -1
  94. package/dist-esm/lib/options.mjs.map +2 -2
  95. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +104 -18
  96. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +2 -2
  97. package/dist-esm/lib/utils/dom.mjs +1 -1
  98. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  99. package/dist-esm/lib/utils/richText.mjs +26 -0
  100. package/dist-esm/lib/utils/richText.mjs.map +7 -0
  101. package/dist-esm/version.mjs +3 -3
  102. package/dist-esm/version.mjs.map +1 -1
  103. package/editor.css +127 -13
  104. package/package.json +10 -9
  105. package/src/index.ts +15 -0
  106. package/src/lib/TldrawEditor.tsx +52 -4
  107. package/src/lib/components/Shape.tsx +9 -1
  108. package/src/lib/editor/Editor.ts +91 -7
  109. package/src/lib/editor/managers/FontManager.ts +252 -0
  110. package/src/lib/editor/managers/TextManager.ts +42 -17
  111. package/src/lib/editor/shapes/ShapeUtil.ts +13 -0
  112. package/src/lib/editor/types/emit-types.ts +1 -0
  113. package/src/lib/editor/types/external-content.ts +1 -0
  114. package/src/lib/exports/FontEmbedder.ts +13 -1
  115. package/src/lib/exports/StyleEmbedder.ts +1 -1
  116. package/src/lib/exports/exportToSvg.tsx +4 -3
  117. package/src/lib/exports/getSvgAsImage.ts +1 -1
  118. package/src/lib/exports/getSvgJsx.tsx +22 -2
  119. package/src/lib/exports/parseCss.ts +1 -0
  120. package/src/lib/globals/environment.ts +3 -0
  121. package/src/lib/hooks/useCanvasEvents.ts +2 -1
  122. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -0
  123. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +29 -0
  124. package/src/lib/hooks/usePassThroughWheelEvents.ts +0 -1
  125. package/src/lib/hooks/useViewportHeight.ts +37 -0
  126. package/src/lib/license/LicenseManager.test.ts +16 -13
  127. package/src/lib/license/LicenseManager.ts +2 -2
  128. package/src/lib/options.ts +7 -0
  129. package/src/lib/utils/browserCanvasMaxSize.ts +121 -21
  130. package/src/lib/utils/dom.ts +1 -1
  131. package/src/lib/utils/richText.ts +72 -0
  132. package/src/version.ts +3 -3
package/editor.css CHANGED
@@ -156,6 +156,8 @@
156
156
  --color-text-1: hsl(0, 0%, 18%);
157
157
  --color-text-3: hsl(220, 2%, 65%);
158
158
  --color-text-shadow: hsl(0, 0%, 100%);
159
+ --color-text-highlight: hsl(52, 100%, 50%);
160
+ --color-text-highlight-p3: color(display-p3 0.972 0.8205 0.05);
159
161
  --color-primary: hsl(214, 84%, 56%);
160
162
  --color-success: hsl(123, 46%, 34%);
161
163
  --color-info: hsl(201, 98%, 41%);
@@ -202,6 +204,8 @@
202
204
  --color-text-1: hsl(0, 0%, 85%);
203
205
  --color-text-3: hsl(210, 6%, 45%);
204
206
  --color-text-shadow: hsl(210, 13%, 18%);
207
+ --color-text-highlight: hsl(52, 100%, 41%);
208
+ --color-text-highlight-p3: color(display-p3 0.8078 0.6225 0.0312);
205
209
  --color-primary: hsl(214, 84%, 56%);
206
210
  --color-success: hsl(123, 38%, 57%);
207
211
  --color-info: hsl(199, 92%, 56%);
@@ -248,7 +252,7 @@
248
252
  input,
249
253
  *[contenteditable],
250
254
  *[contenteditable] * {
251
- -webkit-user-select: text;
255
+ user-select: text;
252
256
  }
253
257
 
254
258
  /* -------------------------------------------------- */
@@ -722,17 +726,14 @@ input,
722
726
  opacity: 0.7;
723
727
  }
724
728
 
725
- /* -------------------------------------------------- */
726
- /* Spinner */
727
- /* -------------------------------------------------- */
728
-
729
+ /* --------------------- Spinner -------------------- */
729
730
  @keyframes spinner {
730
731
  to {
731
732
  transform: rotate(360deg);
732
733
  }
733
734
  }
734
735
 
735
- /* ------------------- Text Shape ------------------- */
736
+ /* ---------------------- Text ---------------------- */
736
737
 
737
738
  .tl-text-shape-label {
738
739
  position: relative;
@@ -782,10 +783,14 @@ input,
782
783
  text-align: right;
783
784
  }
784
785
 
785
- .tl-text-wrapper[data-isediting='true'] .tl-text-content {
786
+ .tl-plain-text-wrapper[data-isediting='true'] .tl-text-content {
786
787
  opacity: 0;
787
788
  }
788
789
 
790
+ .tl-rich-text-wrapper[data-isediting='true'] .tl-text-content {
791
+ display: none;
792
+ }
793
+
789
794
  .tl-text {
790
795
  /* remove overflow from textarea on windows */
791
796
  margin: 0px;
@@ -852,7 +857,6 @@ input,
852
857
  width: 100%;
853
858
  min-width: 1px;
854
859
  min-height: 1px;
855
- overflow: visible;
856
860
  outline: none;
857
861
  }
858
862
 
@@ -868,6 +872,7 @@ input,
868
872
  }
869
873
 
870
874
  .tl-text-content {
875
+ overflow: visible;
871
876
  pointer-events: none;
872
877
  }
873
878
 
@@ -875,10 +880,17 @@ input,
875
880
  resize: none;
876
881
  user-select: all;
877
882
  -webkit-user-select: text;
878
- overflow: hidden;
879
883
  cursor: var(--tl-cursor-text);
880
884
  }
881
885
 
886
+ .tl-text-input:not(.tl-rich-text) {
887
+ /*
888
+ * Note: this `overflow: hidden` is key for scrollbars to not show up
889
+ * plaintext/<textarea> editors.
890
+ */
891
+ overflow: hidden;
892
+ }
893
+
882
894
  .tl-text-wrapper[data-isediting='false'] .tl-text-input,
883
895
  .tl-arrow-label[data-isediting='false'] .tl-text-input {
884
896
  opacity: 0;
@@ -891,6 +903,109 @@ input,
891
903
  text-shadow: none;
892
904
  }
893
905
 
906
+ .tl-rich-text[data-iseditinganything='true'] {
907
+ cursor: var(--tl-cursor-text);
908
+ }
909
+
910
+ .tl-rich-text .ProseMirror {
911
+ word-wrap: break-word;
912
+ overflow-wrap: break-word;
913
+ white-space: pre-wrap;
914
+
915
+ /**
916
+ * Note: ProseMirror disables this in https://github.com/ProseMirror/prosemirror-view/commit/6b3b2205e2f3029cb8e8e86c55a190a22491df31
917
+ * However, that was from 8 years ago and the browser caret issue
918
+ * it mentions seems to be fixed. So, we're re-enabling it.
919
+ * We'll tell ProseMirror maybe to get rid of this on their end.
920
+ */
921
+ -webkit-font-variant-ligatures: inherit;
922
+ font-variant-ligatures: inherit;
923
+ font-feature-settings: inherit;
924
+
925
+ /**
926
+ * N.B. This following CSS Rule comes standard with the tiptap editor.
927
+ * Combined with the above rule that it supersedes, it allows for
928
+ * the auto-linking to work in text. Say, when typing example.com
929
+ * this helps it automatically turn that bit of text into a link.
930
+ *
931
+ * However, specifically, the break-spaces features seems to cause
932
+ * rendering differences when going in-and-out of edit mode. For example,
933
+ * the statically rendered text 'the rain in spain falls mainly on the plain'
934
+ * in a note shape will render differently when going in-and-out of edit mode.
935
+ *
936
+ * So, this is commented out to help make both the autolinking work (which now
937
+ * relies on the white-space: pre-wrap to work and to make the static/edit-mode
938
+ * rendering consistent.
939
+ * In the future, we might consider just making the static rendering just use
940
+ * white-space: break-spaces to make it consistent with the edit mode if need be.
941
+ *
942
+ * Also, the amount of ink I've spilt in my career writing comments explaining
943
+ * white-space in contenteditable is incredible.
944
+ *
945
+ /* white-space: break-spaces; */
946
+ }
947
+
948
+ .tl-rich-text p {
949
+ margin: 0;
950
+ }
951
+
952
+ .tl-rich-text ul,
953
+ .tl-rich-text ol {
954
+ text-align: left;
955
+ margin: 0;
956
+ }
957
+
958
+ .tl-rich-text h1,
959
+ .tl-rich-text h2,
960
+ .tl-rich-text h3,
961
+ .tl-rich-text h4,
962
+ .tl-rich-text h5,
963
+ .tl-rich-text h6 {
964
+ margin-top: 5px;
965
+ margin-bottom: 10px;
966
+ }
967
+
968
+ .tl-rich-text a {
969
+ color: var(--color-primary);
970
+ text-decoration: underline;
971
+ }
972
+
973
+ .tl-rich-text code {
974
+ font-family: var(--tl-font-mono);
975
+ }
976
+
977
+ .tl-rich-text mark {
978
+ background-color: #fddd00;
979
+ color: currentColor;
980
+ border-radius: 2px;
981
+ }
982
+
983
+ .tl-theme__light .tl-rich-text mark {
984
+ text-shadow: none;
985
+ }
986
+
987
+ .tl-theme__dark .tl-rich-text mark {
988
+ background-color: var(--color-text-highlight);
989
+ color: currentColor;
990
+ }
991
+
992
+ @supports (color: color(display-p3 1 1 1)) {
993
+ @media (color-gamut: p3) {
994
+ .tl-container:not(.tl-theme__force-sRGB) .tl-rich-text mark {
995
+ background-color: var(--color-text-highlight-p3);
996
+ }
997
+ }
998
+ }
999
+
1000
+ .tl-text-label__inner > .tl-text-input.tl-rich-text {
1001
+ display: none;
1002
+ position: static;
1003
+ }
1004
+
1005
+ .tl-text-wrapper[data-isediting='true'] .tl-rich-text {
1006
+ display: block;
1007
+ }
1008
+
894
1009
  /* ------------------- Snap Lines ------------------- */
895
1010
 
896
1011
  .tl-snap-indicator {
@@ -1175,10 +1290,6 @@ input,
1175
1290
  min-height: auto;
1176
1291
  }
1177
1292
 
1178
- .tl-text-label[data-isediting='true'] p {
1179
- opacity: 0;
1180
- }
1181
-
1182
1293
  .tl-arrow-hint {
1183
1294
  stroke: var(--color-text-1);
1184
1295
  fill: none;
@@ -1270,6 +1381,9 @@ input,
1270
1381
  opacity: 0;
1271
1382
  animation: fade-in 0.2s ease-in-out forwards;
1272
1383
  animation-delay: 0.2s;
1384
+ position: absolute;
1385
+ inset: 0px;
1386
+ z-index: var(--layer-canvas-blocker);
1273
1387
  }
1274
1388
 
1275
1389
  @keyframes fade-in {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "A tiny little drawing app (editor).",
4
- "version": "3.9.0-canary.fae364b5aba5",
4
+ "version": "3.9.0-internal.7f0e15f4f7d9",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -45,15 +45,17 @@
45
45
  "lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@tldraw/state": "3.9.0-canary.fae364b5aba5",
49
- "@tldraw/state-react": "3.9.0-canary.fae364b5aba5",
50
- "@tldraw/store": "3.9.0-canary.fae364b5aba5",
51
- "@tldraw/tlschema": "3.9.0-canary.fae364b5aba5",
52
- "@tldraw/utils": "3.9.0-canary.fae364b5aba5",
53
- "@tldraw/validate": "3.9.0-canary.fae364b5aba5",
48
+ "@tiptap/core": "^2.9.1",
49
+ "@tiptap/pm": "^2.9.1",
50
+ "@tiptap/react": "^2.9.1",
51
+ "@tldraw/state": "3.9.0-internal.7f0e15f4f7d9",
52
+ "@tldraw/state-react": "3.9.0-internal.7f0e15f4f7d9",
53
+ "@tldraw/store": "3.9.0-internal.7f0e15f4f7d9",
54
+ "@tldraw/tlschema": "3.9.0-internal.7f0e15f4f7d9",
55
+ "@tldraw/utils": "3.9.0-internal.7f0e15f4f7d9",
56
+ "@tldraw/validate": "3.9.0-internal.7f0e15f4f7d9",
54
57
  "@types/core-js": "^2.5.8",
55
58
  "@use-gesture/react": "^10.3.1",
56
- "canvas-size": "~2.0.0",
57
59
  "classnames": "^2.5.1",
58
60
  "core-js": "^3.40.0",
59
61
  "eventemitter3": "^4.0.7",
@@ -70,7 +72,6 @@
70
72
  "@testing-library/jest-dom": "^5.17.0",
71
73
  "@testing-library/react": "^15.0.7",
72
74
  "@types/benchmark": "^2.1.5",
73
- "@types/canvas-size": "^1.2.2",
74
75
  "@types/wicg-file-system-access": "^2020.9.8",
75
76
  "benchmark": "^2.1.4",
76
77
  "fake-indexeddb": "^4.0.2",
package/src/index.ts CHANGED
@@ -26,6 +26,14 @@ export {
26
26
  useValue,
27
27
  } from '@tldraw/state-react'
28
28
  export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
29
+ export {
30
+ getFontsFromRichText,
31
+ type RichTextFontVisitor,
32
+ type RichTextFontVisitorState,
33
+ type TLTextOptions,
34
+ type TiptapEditor,
35
+ type TiptapNode,
36
+ } from './lib/utils/richText'
29
37
  export { LocalIndexedDb, Table, type StoreName } from './lib/utils/sync/LocalIndexedDb'
30
38
  // eslint-disable-next-line local/no-export-star
31
39
  export * from '@tldraw/store'
@@ -163,6 +171,11 @@ export {
163
171
  } from './lib/editor/bindings/BindingUtil'
164
172
  export { ClickManager, type TLClickState } from './lib/editor/managers/ClickManager'
165
173
  export { EdgeScrollManager } from './lib/editor/managers/EdgeScrollManager'
174
+ export {
175
+ FontManager,
176
+ type TLFontFace,
177
+ type TLFontFaceSource,
178
+ } from './lib/editor/managers/FontManager'
166
179
  export { HistoryManager } from './lib/editor/managers/HistoryManager'
167
180
  export { ScribbleManager, type ScribbleItem } from './lib/editor/managers/ScribbleManager'
168
181
  export {
@@ -297,6 +310,7 @@ export { useIsCropping } from './lib/hooks/useIsCropping'
297
310
  export { useIsDarkMode } from './lib/hooks/useIsDarkMode'
298
311
  export { useIsEditing } from './lib/hooks/useIsEditing'
299
312
  export { useLocalStore } from './lib/hooks/useLocalStore'
313
+ export { usePassThroughMouseOverEvents } from './lib/hooks/usePassThroughMouseOverEvents'
300
314
  export { usePassThroughWheelEvents } from './lib/hooks/usePassThroughWheelEvents'
301
315
  export { usePeerIds } from './lib/hooks/usePeerIds'
302
316
  export { usePresence } from './lib/hooks/usePresence'
@@ -311,6 +325,7 @@ export {
311
325
  export { useSelectionEvents } from './lib/hooks/useSelectionEvents'
312
326
  export { useTLSchemaFromUtils, useTLStore } from './lib/hooks/useTLStore'
313
327
  export { useTransform } from './lib/hooks/useTransform'
328
+ export { useViewportHeight } from './lib/hooks/useViewportHeight'
314
329
  export {
315
330
  LicenseManager,
316
331
  type InvalidLicenseKeyResult,
@@ -14,7 +14,6 @@ import React, {
14
14
  } from 'react'
15
15
 
16
16
  import classNames from 'classnames'
17
- import { TLDeepLinkOptions } from '..'
18
17
  import { version } from '../version'
19
18
  import { OptionalErrorBoundary } from './components/ErrorBoundary'
20
19
  import { DefaultErrorFallback } from './components/default-components/DefaultErrorFallback'
@@ -44,7 +43,9 @@ import { useZoomCss } from './hooks/useZoomCss'
44
43
  import { LicenseProvider } from './license/LicenseProvider'
45
44
  import { Watermark } from './license/Watermark'
46
45
  import { TldrawOptions } from './options'
46
+ import { TLDeepLinkOptions } from './utils/deepLinks'
47
47
  import { stopEventPropagation } from './utils/dom'
48
+ import { TLTextOptions } from './utils/richText'
48
49
  import { TLStoreWithStatus } from './utils/sync/StoreWithStatus'
49
50
 
50
51
  /**
@@ -167,6 +168,11 @@ export interface TldrawEditorBaseProps {
167
168
  */
168
169
  cameraOptions?: Partial<TLCameraOptions>
169
170
 
171
+ /**
172
+ * Text options for the editor.
173
+ */
174
+ textOptions?: TLTextOptions
175
+
170
176
  /**
171
177
  * Options for the editor.
172
178
  */
@@ -190,6 +196,11 @@ export interface TldrawEditorBaseProps {
190
196
  * remain in the store and participate in all other operations.
191
197
  */
192
198
  isShapeHidden?(shape: TLShape, editor: Editor): boolean
199
+
200
+ /**
201
+ * The URLs for the fonts to use in the editor.
202
+ */
203
+ assetUrls?: { fonts?: { [key: string]: string | undefined } }
193
204
  }
194
205
 
195
206
  /**
@@ -372,10 +383,12 @@ function TldrawEditorWithReadyStore({
372
383
  autoFocus = true,
373
384
  inferDarkMode,
374
385
  cameraOptions,
386
+ textOptions,
375
387
  options,
376
388
  licenseKey,
377
389
  deepLinks: _deepLinks,
378
390
  isShapeHidden,
391
+ assetUrls,
379
392
  }: Required<
380
393
  TldrawEditorProps & {
381
394
  store: TLStore
@@ -430,9 +443,11 @@ function TldrawEditorWithReadyStore({
430
443
  autoFocus,
431
444
  inferDarkMode,
432
445
  cameraOptions,
446
+ textOptions,
433
447
  options,
434
448
  licenseKey,
435
449
  isShapeHidden,
450
+ fontAssetUrls: assetUrls?.fonts,
436
451
  })
437
452
 
438
453
  editor.updateViewportScreenBounds(canvasRef.current ?? container)
@@ -467,6 +482,8 @@ function TldrawEditorWithReadyStore({
467
482
  setEditor,
468
483
  licenseKey,
469
484
  isShapeHidden,
485
+ textOptions,
486
+ assetUrls,
470
487
  ]
471
488
  )
472
489
 
@@ -532,10 +549,41 @@ function TldrawEditorWithReadyStore({
532
549
  [editor, autoFocus]
533
550
  )
534
551
 
535
- const { Canvas } = useEditorComponents()
552
+ const [_fontLoadingState, setFontLoadingState] = useState<{
553
+ editor: Editor
554
+ isLoaded: boolean
555
+ } | null>(null)
556
+ let fontLoadingState = _fontLoadingState
557
+ if (editor !== fontLoadingState?.editor) {
558
+ const newFontLoadingState = editor ? { editor, isLoaded: false } : null
559
+ if (fontLoadingState !== newFontLoadingState) setFontLoadingState(newFontLoadingState)
560
+ fontLoadingState = newFontLoadingState
561
+ }
562
+ useEffect(() => {
563
+ if (!editor) return
564
+ let isCancelled = false
536
565
 
537
- if (!editor) {
538
- return <div className="tl-canvas" ref={canvasRef} />
566
+ editor.fonts
567
+ .loadRequiredFontsForCurrentPage(editor.options.maxFontsToLoadBeforeRender)
568
+ .finally(() => {
569
+ if (isCancelled) return
570
+ setFontLoadingState({ editor, isLoaded: true })
571
+ })
572
+
573
+ return () => {
574
+ isCancelled = true
575
+ }
576
+ }, [editor])
577
+
578
+ const { Canvas, LoadingScreen } = useEditorComponents()
579
+
580
+ if (!editor || !fontLoadingState?.isLoaded) {
581
+ return (
582
+ <>
583
+ {LoadingScreen && <LoadingScreen />}
584
+ <div className="tl-canvas" ref={canvasRef} />
585
+ </>
586
+ )
539
587
  }
540
588
 
541
589
  return (
@@ -1,6 +1,7 @@
1
+ import { react } from '@tldraw/state'
1
2
  import { useQuickReactor, useStateTracking } from '@tldraw/state-react'
2
3
  import { TLShape, TLShapeId } from '@tldraw/tlschema'
3
- import { memo, useCallback, useRef } from 'react'
4
+ import { memo, useCallback, useEffect, useRef } from 'react'
4
5
  import { ShapeUtil } from '../editor/shapes/ShapeUtil'
5
6
  import { useEditor } from '../hooks/useEditor'
6
7
  import { useEditorComponents } from '../hooks/useEditorComponents'
@@ -41,6 +42,13 @@ export const Shape = memo(function Shape({
41
42
  const containerRef = useRef<HTMLDivElement>(null)
42
43
  const bgContainerRef = useRef<HTMLDivElement>(null)
43
44
 
45
+ useEffect(() => {
46
+ return react('load fonts', () => {
47
+ const fonts = editor.fonts.getShapeFontFaces(shape)
48
+ editor.fonts.requestFonts(fonts)
49
+ })
50
+ }, [editor, shape])
51
+
44
52
  const memoizedStuffRef = useRef({
45
53
  transform: '',
46
54
  clipPath: 'none',
@@ -1,4 +1,12 @@
1
- import { EMPTY_ARRAY, atom, computed, react, transact, unsafe__withoutCapture } from '@tldraw/state'
1
+ import {
2
+ Atom,
3
+ EMPTY_ARRAY,
4
+ atom,
5
+ computed,
6
+ react,
7
+ transact,
8
+ unsafe__withoutCapture,
9
+ } from '@tldraw/state'
2
10
  import {
3
11
  ComputedCache,
4
12
  RecordType,
@@ -34,6 +42,7 @@ import {
34
42
  TLImageAsset,
35
43
  TLInstance,
36
44
  TLInstancePageState,
45
+ TLNoteShape,
37
46
  TLPOINTER_ID,
38
47
  TLPage,
39
48
  TLPageId,
@@ -129,6 +138,7 @@ import {
129
138
  import { getIncrementedName } from '../utils/getIncrementedName'
130
139
  import { isAccelKey } from '../utils/keyboard'
131
140
  import { getReorderingShapesChanges } from '../utils/reorderShapes'
141
+ import { TLTextOptions, TiptapEditor } from '../utils/richText'
132
142
  import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
133
143
  import { BindingOnDeleteOptions, BindingUtil } from './bindings/BindingUtil'
134
144
  import { bindingsIndex } from './derivations/bindingsIndex'
@@ -138,6 +148,7 @@ import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage
138
148
  import { ClickManager } from './managers/ClickManager'
139
149
  import { EdgeScrollManager } from './managers/EdgeScrollManager'
140
150
  import { FocusManager } from './managers/FocusManager'
151
+ import { FontManager } from './managers/FontManager'
141
152
  import { HistoryManager } from './managers/HistoryManager'
142
153
  import { ScribbleManager } from './managers/ScribbleManager'
143
154
  import { SnapManager } from './managers/SnapManager/SnapManager'
@@ -224,8 +235,10 @@ export interface TLEditorOptions {
224
235
  * Options for the editor's camera.
225
236
  */
226
237
  cameraOptions?: Partial<TLCameraOptions>
238
+ textOptions?: TLTextOptions
227
239
  options?: Partial<TldrawOptions>
228
240
  licenseKey?: string
241
+ fontAssetUrls?: { [key: string]: string | undefined }
229
242
  /**
230
243
  * A predicate that should return true if the given shape should be hidden.
231
244
  * @param shape - The shape to check.
@@ -262,11 +275,13 @@ export class Editor extends EventEmitter<TLEventMap> {
262
275
  tools,
263
276
  getContainer,
264
277
  cameraOptions,
278
+ textOptions,
265
279
  initialState,
266
280
  autoFocus,
267
281
  inferDarkMode,
268
282
  options,
269
283
  isShapeHidden,
284
+ fontAssetUrls,
270
285
  }: TLEditorOptions) {
271
286
  super()
272
287
 
@@ -290,12 +305,16 @@ export class Editor extends EventEmitter<TLEventMap> {
290
305
 
291
306
  this._cameraOptions.set({ ...DEFAULT_CAMERA_OPTIONS, ...cameraOptions })
292
307
 
308
+ this._textOptions = atom('text options', textOptions ?? null)
309
+
293
310
  this.user = new UserPreferencesManager(user ?? createTLUser(), inferDarkMode ?? false)
294
311
  this.disposables.add(() => this.user.dispose())
295
312
 
296
313
  this.getContainer = getContainer
297
314
 
298
315
  this.textMeasure = new TextManager(this)
316
+ this.fonts = new FontManager(this, fontAssetUrls)
317
+
299
318
  this._tickManager = new TickManager(this)
300
319
 
301
320
  class NewRoot extends RootState {
@@ -834,6 +853,13 @@ export class Editor extends EventEmitter<TLEventMap> {
834
853
  */
835
854
  readonly textMeasure: TextManager
836
855
 
856
+ /**
857
+ * A utility for managing the set of fonts that should be rendered in the document.
858
+ *
859
+ * @public
860
+ */
861
+ readonly fonts: FontManager
862
+
837
863
  /**
838
864
  * A manager for the editor's environment.
839
865
  *
@@ -2022,6 +2048,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2022
2048
  */
2023
2049
  setEditingShape(shape: TLShapeId | TLShape | null): this {
2024
2050
  const id = typeof shape === 'string' ? shape : (shape?.id ?? null)
2051
+ this.setRichTextEditor(null)
2025
2052
  if (id !== this.getEditingShapeId()) {
2026
2053
  if (id) {
2027
2054
  const shape = this.getShape(id)
@@ -2040,6 +2067,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2040
2067
  this.run(
2041
2068
  () => {
2042
2069
  this._updateCurrentPageState({ editingShapeId: null })
2070
+ this._currentRichTextEditor.set(null)
2043
2071
  },
2044
2072
  { history: 'ignore' }
2045
2073
  )
@@ -2047,6 +2075,42 @@ export class Editor extends EventEmitter<TLEventMap> {
2047
2075
  return this
2048
2076
  }
2049
2077
 
2078
+ // Rich text editor
2079
+
2080
+ private _currentRichTextEditor = atom('rich text editor', null as TiptapEditor | null)
2081
+
2082
+ /**
2083
+ * The current editing shape's text editor.
2084
+ *
2085
+ * @public
2086
+ */
2087
+ @computed getRichTextEditor(): TiptapEditor | null {
2088
+ return this._currentRichTextEditor.get()
2089
+ }
2090
+
2091
+ /**
2092
+ * Set the current editing shape's rich text editor.
2093
+ *
2094
+ * @example
2095
+ * ```ts
2096
+ * editor.setRichTextEditor(richTextEditorView)
2097
+ * ```
2098
+ *
2099
+ * @param textEditor - The text editor to set as the current editing shape's text editor.
2100
+ *
2101
+ * @public
2102
+ */
2103
+ setRichTextEditor(textEditor: TiptapEditor | null) {
2104
+ // If the new editor is different from the current one, destroy the current one
2105
+ const current = this._currentRichTextEditor.__unsafe__getWithoutCapture()
2106
+ if (current !== textEditor) {
2107
+ current?.destroy()
2108
+ }
2109
+
2110
+ this._currentRichTextEditor.set(textEditor)
2111
+ return this
2112
+ }
2113
+
2050
2114
  // Hovered
2051
2115
 
2052
2116
  /**
@@ -2103,6 +2167,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2103
2167
  @computed getHintingShapeIds() {
2104
2168
  return this.getCurrentPageState().hintingShapeIds
2105
2169
  }
2170
+
2106
2171
  /**
2107
2172
  * The editor's current hinting shapes.
2108
2173
  *
@@ -2251,6 +2316,21 @@ export class Editor extends EventEmitter<TLEventMap> {
2251
2316
  return this
2252
2317
  }
2253
2318
 
2319
+ private _textOptions: Atom<TLTextOptions | null>
2320
+
2321
+ /**
2322
+ * Get the current text options.
2323
+ *
2324
+ * @example
2325
+ * ```ts
2326
+ * editor.getTextOptions()
2327
+ * ```
2328
+ *
2329
+ * @public */
2330
+ getTextOptions() {
2331
+ return assertExists(this._textOptions.get(), 'Cannot use text without setting textOptions')
2332
+ }
2333
+
2254
2334
  /* --------------------- Camera --------------------- */
2255
2335
 
2256
2336
  /** @internal */
@@ -4207,8 +4287,11 @@ export class Editor extends EventEmitter<TLEventMap> {
4207
4287
  private _getShapeGeometryCache(): ComputedCache<Geometry2d, TLShape> {
4208
4288
  return this.store.createComputedCache(
4209
4289
  'bounds',
4210
- (shape) => this.getShapeUtil(shape).getGeometry(shape),
4211
- (a, b) => a.props === b.props
4290
+ (shape) => {
4291
+ this.fonts.trackFontsForShape(shape)
4292
+ return this.getShapeUtil(shape).getGeometry(shape)
4293
+ },
4294
+ { areRecordsEqual: (a, b) => a.props === b.props }
4212
4295
  )
4213
4296
  }
4214
4297
 
@@ -4758,9 +4841,10 @@ export class Editor extends EventEmitter<TLEventMap> {
4758
4841
  // Check labels first
4759
4842
  if (
4760
4843
  this.isShapeOfType<TLFrameShape>(shape, 'frame') ||
4761
- ((this.isShapeOfType<TLArrowShape>(shape, 'arrow') ||
4844
+ (this.isShapeOfType<TLArrowShape>(shape, 'arrow') && shape.props.text.trim()) ||
4845
+ ((this.isShapeOfType<TLNoteShape>(shape, 'note') ||
4762
4846
  (this.isShapeOfType<TLGeoShape>(shape, 'geo') && shape.props.fill === 'none')) &&
4763
- shape.props.text.trim())
4847
+ this.getShapeUtil(shape).getText(shape)?.trim())
4764
4848
  ) {
4765
4849
  for (const childGeometry of (geometry as Group2d).children) {
4766
4850
  if (childGeometry.isLabel && childGeometry.isPointInBounds(pointInShapeSpace)) {
@@ -7007,7 +7091,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7007
7091
  * @example
7008
7092
  * ```ts
7009
7093
  * editor.createShape(myShape)
7010
- * editor.createShape({ id: 'box1', type: 'text', props: { text: "ok" } })
7094
+ * editor.createShape({ id: 'box1', type: 'text', props: { richText: toRichText("ok") } })
7011
7095
  * ```
7012
7096
  *
7013
7097
  * @param shape - The shape (or shape partial) to create.
@@ -7025,7 +7109,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7025
7109
  * @example
7026
7110
  * ```ts
7027
7111
  * editor.createShapes([myShape])
7028
- * editor.createShapes([{ id: 'box1', type: 'text', props: { text: "ok" } }])
7112
+ * editor.createShapes([{ id: 'box1', type: 'text', props: { richText: toRichText("ok") } }])
7029
7113
  * ```
7030
7114
  *
7031
7115
  * @param shapes - The shapes (or shape partials) to create.