@excalidraw/excalidraw 0.17.1-d2f67e6 → 0.17.1-e63dd02

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 (252) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/browser/dev/excalidraw-assets-dev/CascadiaCode-Regular-TMZI7IJ5.woff2 +0 -0
  3. package/dist/browser/dev/excalidraw-assets-dev/ComicShanns-Regular-6TOETDFT.woff2 +0 -0
  4. package/dist/browser/dev/excalidraw-assets-dev/Excalifont-Regular-CPKEUDVM.woff2 +0 -0
  5. package/dist/browser/dev/excalidraw-assets-dev/LiberationSans-Regular-ZQD73GJM.woff2 +0 -0
  6. package/dist/browser/dev/excalidraw-assets-dev/Virgil-Regular-YHAB2VGJ.woff2 +0 -0
  7. package/dist/browser/dev/excalidraw-assets-dev/{chunk-EM6LVGFW.js → chunk-IT7T3AIK.js} +23 -5
  8. package/dist/browser/dev/excalidraw-assets-dev/chunk-IT7T3AIK.js.map +7 -0
  9. package/dist/browser/dev/excalidraw-assets-dev/{chunk-B4UMSLQQ.js → chunk-RNHSD5AR.js} +7451 -2098
  10. package/dist/browser/dev/excalidraw-assets-dev/chunk-RNHSD5AR.js.map +7 -0
  11. package/dist/browser/dev/excalidraw-assets-dev/{dist-6QVAH5JA.js → dist-DNSPZDOZ.js} +31 -19
  12. package/dist/browser/dev/excalidraw-assets-dev/dist-DNSPZDOZ.js.map +7 -0
  13. package/dist/browser/dev/excalidraw-assets-dev/{en-AZFA5HJJ.js → en-XV7OZCPP.js} +6 -2
  14. package/dist/browser/dev/excalidraw-assets-dev/{image-V7E6IT6R.js → image-77HZYGLG.js} +2 -2
  15. package/dist/browser/dev/excalidraw-assets-dev/{image-O66MQ7WQ.css → image-WDHYGKKP.css} +1 -1
  16. package/dist/browser/dev/excalidraw-assets-dev/{image-O66MQ7WQ.css.map → image-WDHYGKKP.css.map} +2 -2
  17. package/dist/browser/dev/index.css +449 -114
  18. package/dist/browser/dev/index.css.map +3 -3
  19. package/dist/browser/dev/index.js +4143 -5956
  20. package/dist/browser/dev/index.js.map +4 -4
  21. package/dist/browser/prod/excalidraw-assets/CascadiaCode-Regular-TMZI7IJ5.woff2 +0 -0
  22. package/dist/browser/prod/excalidraw-assets/ComicShanns-Regular-6TOETDFT.woff2 +0 -0
  23. package/dist/browser/prod/excalidraw-assets/Excalifont-Regular-CPKEUDVM.woff2 +0 -0
  24. package/dist/browser/prod/excalidraw-assets/LiberationSans-Regular-ZQD73GJM.woff2 +0 -0
  25. package/dist/browser/prod/excalidraw-assets/Virgil-Regular-YHAB2VGJ.woff2 +0 -0
  26. package/dist/browser/prod/excalidraw-assets/chunk-OYEADJSR.js +63 -0
  27. package/dist/browser/prod/excalidraw-assets/{chunk-7DXALCB2.js → chunk-PDYFZJMS.js} +3 -3
  28. package/dist/browser/prod/excalidraw-assets/dist-NLUQPPQQ.js +7 -0
  29. package/dist/browser/prod/excalidraw-assets/en-YVAVVILW.js +1 -0
  30. package/dist/browser/prod/excalidraw-assets/image-X3GFZHNN.js +1 -0
  31. package/dist/browser/prod/index.css +1 -1
  32. package/dist/browser/prod/index.js +40 -50
  33. package/dist/dev/CascadiaCode-Regular-TMZI7IJ5.woff2 +0 -0
  34. package/dist/dev/ComicShanns-Regular-6TOETDFT.woff2 +0 -0
  35. package/dist/dev/Excalifont-Regular-CPKEUDVM.woff2 +0 -0
  36. package/dist/dev/LiberationSans-Regular-ZQD73GJM.woff2 +0 -0
  37. package/dist/dev/Virgil-Regular-YHAB2VGJ.woff2 +0 -0
  38. package/dist/dev/{en-EB2MBPAV.json → en-YNVBSAIL.json} +18 -4
  39. package/dist/dev/index.css +449 -114
  40. package/dist/dev/index.css.map +3 -3
  41. package/dist/dev/index.js +21626 -18122
  42. package/dist/dev/index.js.map +4 -4
  43. package/dist/excalidraw/actions/actionAddToLibrary.d.ts +9 -3
  44. package/dist/excalidraw/actions/actionBoundText.d.ts +6 -2
  45. package/dist/excalidraw/actions/actionCanvas.d.ts +36 -12
  46. package/dist/excalidraw/actions/actionClipboard.d.ts +22 -7
  47. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +12 -5
  48. package/dist/excalidraw/actions/actionDeleteSelected.js +24 -5
  49. package/dist/excalidraw/actions/actionDuplicateSelection.js +1 -2
  50. package/dist/excalidraw/actions/actionElementLock.d.ts +6 -2
  51. package/dist/excalidraw/actions/actionExport.d.ts +27 -9
  52. package/dist/excalidraw/actions/actionFinalize.d.ts +6 -2
  53. package/dist/excalidraw/actions/actionFinalize.js +2 -2
  54. package/dist/excalidraw/actions/actionFlip.js +2 -2
  55. package/dist/excalidraw/actions/actionFrame.d.ts +12 -4
  56. package/dist/excalidraw/actions/actionGroup.d.ts +6 -2
  57. package/dist/excalidraw/actions/actionHistory.js +4 -4
  58. package/dist/excalidraw/actions/actionLinearEditor.d.ts +3 -1
  59. package/dist/excalidraw/actions/actionLinearEditor.js +3 -2
  60. package/dist/excalidraw/actions/actionLink.d.ts +3 -1
  61. package/dist/excalidraw/actions/actionMenu.d.ts +9 -3
  62. package/dist/excalidraw/actions/actionNavigate.d.ts +6 -2
  63. package/dist/excalidraw/actions/actionProperties.d.ts +411 -56
  64. package/dist/excalidraw/actions/actionProperties.js +383 -58
  65. package/dist/excalidraw/actions/actionSelectAll.d.ts +3 -1
  66. package/dist/excalidraw/actions/actionStyles.d.ts +3 -1
  67. package/dist/excalidraw/actions/actionStyles.js +3 -2
  68. package/dist/excalidraw/actions/actionToggleGridMode.d.ts +3 -1
  69. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +3 -1
  70. package/dist/excalidraw/actions/actionToggleStats.d.ts +3 -1
  71. package/dist/excalidraw/actions/actionToggleViewMode.d.ts +3 -1
  72. package/dist/excalidraw/actions/actionToggleZenMode.d.ts +3 -1
  73. package/dist/excalidraw/actions/types.d.ts +1 -1
  74. package/dist/excalidraw/analytics.js +9 -7
  75. package/dist/excalidraw/appState.d.ts +1 -0
  76. package/dist/excalidraw/appState.js +9 -1
  77. package/dist/excalidraw/binaryheap.d.ts +12 -0
  78. package/dist/excalidraw/binaryheap.js +93 -0
  79. package/dist/excalidraw/change.d.ts +2 -1
  80. package/dist/excalidraw/change.js +6 -4
  81. package/dist/excalidraw/charts.js +0 -10
  82. package/dist/excalidraw/components/Actions.js +7 -5
  83. package/dist/excalidraw/components/App.d.ts +5 -9
  84. package/dist/excalidraw/components/App.js +218 -161
  85. package/dist/excalidraw/components/ButtonIcon.d.ts +15 -0
  86. package/dist/excalidraw/components/ButtonIcon.js +8 -0
  87. package/dist/excalidraw/components/ButtonIconSelect.js +2 -3
  88. package/dist/excalidraw/components/ButtonSeparator.d.ts +2 -0
  89. package/dist/excalidraw/components/ButtonSeparator.js +7 -0
  90. package/dist/excalidraw/components/ColorPicker/ColorPicker.js +47 -79
  91. package/dist/excalidraw/components/ColorPicker/Picker.js +1 -1
  92. package/dist/excalidraw/components/FontPicker/FontPicker.d.ts +21 -0
  93. package/dist/excalidraw/components/FontPicker/FontPicker.js +49 -0
  94. package/dist/excalidraw/components/FontPicker/FontPickerList.d.ts +25 -0
  95. package/dist/excalidraw/components/FontPicker/FontPickerList.js +119 -0
  96. package/dist/excalidraw/components/FontPicker/FontPickerTrigger.d.ts +7 -0
  97. package/dist/excalidraw/components/FontPicker/FontPickerTrigger.js +13 -0
  98. package/dist/excalidraw/components/FontPicker/keyboardNavHandlers.d.ts +14 -0
  99. package/dist/excalidraw/components/FontPicker/keyboardNavHandlers.js +38 -0
  100. package/dist/excalidraw/components/HelpDialog.js +1 -1
  101. package/dist/excalidraw/components/HintViewer.js +6 -3
  102. package/dist/excalidraw/components/PropertiesPopover.d.ts +15 -0
  103. package/dist/excalidraw/components/PropertiesPopover.js +31 -0
  104. package/dist/excalidraw/components/QuickSearch.d.ts +9 -0
  105. package/dist/excalidraw/components/QuickSearch.js +8 -0
  106. package/dist/excalidraw/components/ScrollableList.d.ts +9 -0
  107. package/dist/excalidraw/components/ScrollableList.js +8 -0
  108. package/dist/excalidraw/components/Stats/Angle.d.ts +7 -3
  109. package/dist/excalidraw/components/Stats/Angle.js +39 -31
  110. package/dist/excalidraw/components/Stats/Dimension.d.ts +6 -3
  111. package/dist/excalidraw/components/Stats/Dimension.js +51 -49
  112. package/dist/excalidraw/components/Stats/DragInput.d.ts +15 -6
  113. package/dist/excalidraw/components/Stats/DragInput.js +59 -26
  114. package/dist/excalidraw/components/Stats/FontSize.d.ts +8 -4
  115. package/dist/excalidraw/components/Stats/FontSize.js +39 -36
  116. package/dist/excalidraw/components/Stats/MultiAngle.d.ts +5 -3
  117. package/dist/excalidraw/components/Stats/MultiAngle.js +43 -34
  118. package/dist/excalidraw/components/Stats/MultiDimension.d.ts +5 -3
  119. package/dist/excalidraw/components/Stats/MultiDimension.js +101 -99
  120. package/dist/excalidraw/components/Stats/MultiFontSize.d.ts +6 -3
  121. package/dist/excalidraw/components/Stats/MultiFontSize.js +47 -32
  122. package/dist/excalidraw/components/Stats/MultiPosition.d.ts +3 -1
  123. package/dist/excalidraw/components/Stats/MultiPosition.js +52 -48
  124. package/dist/excalidraw/components/Stats/Position.d.ts +5 -1
  125. package/dist/excalidraw/components/Stats/Position.js +31 -29
  126. package/dist/excalidraw/components/Stats/index.js +5 -17
  127. package/dist/excalidraw/components/Stats/utils.d.ts +14 -3
  128. package/dist/excalidraw/components/Stats/utils.js +48 -9
  129. package/dist/excalidraw/components/TTDDialog/common.d.ts +2 -2
  130. package/dist/excalidraw/components/TTDDialog/common.js +3 -7
  131. package/dist/excalidraw/components/UserList.js +22 -22
  132. package/dist/excalidraw/components/canvases/StaticCanvas.js +1 -0
  133. package/dist/excalidraw/components/dropdownMenu/DropdownMenu.d.ts +12 -3
  134. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItem.d.ts +24 -4
  135. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItem.js +55 -14
  136. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContent.d.ts +2 -1
  137. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContent.js +2 -2
  138. package/dist/excalidraw/components/dropdownMenu/common.d.ts +1 -1
  139. package/dist/excalidraw/components/dropdownMenu/common.js +3 -2
  140. package/dist/excalidraw/components/icons.d.ts +4 -0
  141. package/dist/excalidraw/components/icons.js +7 -0
  142. package/dist/excalidraw/components/main-menu/MainMenu.d.ts +12 -3
  143. package/dist/excalidraw/components/welcome-screen/WelcomeScreen.Center.js +2 -2
  144. package/dist/excalidraw/components/welcome-screen/WelcomeScreen.Hints.js +3 -3
  145. package/dist/excalidraw/constants.d.ts +17 -2
  146. package/dist/excalidraw/constants.js +21 -4
  147. package/dist/excalidraw/data/reconcile.js +18 -1
  148. package/dist/excalidraw/data/restore.js +55 -9
  149. package/dist/excalidraw/data/transform.js +8 -5
  150. package/dist/excalidraw/element/binding.d.ts +28 -9
  151. package/dist/excalidraw/element/binding.js +303 -71
  152. package/dist/excalidraw/element/collision.d.ts +1 -1
  153. package/dist/excalidraw/element/collision.js +4 -1
  154. package/dist/excalidraw/element/dragElements.d.ts +2 -2
  155. package/dist/excalidraw/element/dragElements.js +13 -3
  156. package/dist/excalidraw/element/embeddable.d.ts +3 -1
  157. package/dist/excalidraw/element/heading.d.ts +11 -0
  158. package/dist/excalidraw/element/heading.js +81 -0
  159. package/dist/excalidraw/element/index.d.ts +1 -1
  160. package/dist/excalidraw/element/index.js +1 -1
  161. package/dist/excalidraw/element/linearElementEditor.d.ts +21 -13
  162. package/dist/excalidraw/element/linearElementEditor.js +133 -56
  163. package/dist/excalidraw/element/newElement.d.ts +8 -3
  164. package/dist/excalidraw/element/newElement.js +15 -2
  165. package/dist/excalidraw/element/resizeElements.d.ts +4 -3
  166. package/dist/excalidraw/element/resizeElements.js +47 -23
  167. package/dist/excalidraw/element/routing.d.ts +13 -0
  168. package/dist/excalidraw/element/routing.js +641 -0
  169. package/dist/excalidraw/element/textElement.d.ts +3 -26
  170. package/dist/excalidraw/element/textElement.js +54 -110
  171. package/dist/excalidraw/element/textWysiwyg.js +39 -47
  172. package/dist/excalidraw/element/transformHandles.js +7 -2
  173. package/dist/excalidraw/element/typeChecks.d.ts +5 -2
  174. package/dist/excalidraw/element/typeChecks.js +17 -0
  175. package/dist/excalidraw/element/types.d.ts +12 -1
  176. package/dist/excalidraw/fonts/ExcalidrawFont.d.ts +21 -0
  177. package/dist/excalidraw/fonts/ExcalidrawFont.js +112 -0
  178. package/dist/excalidraw/fonts/index.d.ts +58 -0
  179. package/dist/excalidraw/fonts/index.js +240 -0
  180. package/dist/excalidraw/fonts/metadata.d.ts +36 -0
  181. package/dist/excalidraw/fonts/metadata.js +91 -0
  182. package/dist/excalidraw/fractionalIndex.d.ts +11 -4
  183. package/dist/excalidraw/fractionalIndex.js +38 -6
  184. package/dist/excalidraw/frame.d.ts +1 -1
  185. package/dist/excalidraw/frame.js +3 -3
  186. package/dist/excalidraw/history.d.ts +4 -3
  187. package/dist/excalidraw/history.js +8 -8
  188. package/dist/excalidraw/index.d.ts +1 -1
  189. package/dist/excalidraw/index.js +3 -3
  190. package/dist/excalidraw/locales/en.json +18 -4
  191. package/dist/excalidraw/math.d.ts +43 -0
  192. package/dist/excalidraw/math.js +110 -0
  193. package/dist/excalidraw/mermaid.js +4 -3
  194. package/dist/excalidraw/renderer/interactiveScene.js +33 -17
  195. package/dist/excalidraw/renderer/renderElement.d.ts +2 -0
  196. package/dist/excalidraw/renderer/renderElement.js +74 -54
  197. package/dist/excalidraw/renderer/staticSvgScene.js +2 -1
  198. package/dist/excalidraw/scene/Scene.js +9 -3
  199. package/dist/excalidraw/scene/Shape.js +56 -5
  200. package/dist/excalidraw/scene/comparisons.d.ts +1 -0
  201. package/dist/excalidraw/scene/comparisons.js +1 -1
  202. package/dist/excalidraw/scene/export.d.ts +2 -1
  203. package/dist/excalidraw/scene/export.js +33 -35
  204. package/dist/excalidraw/scene/types.d.ts +1 -4
  205. package/dist/excalidraw/shapes.d.ts +8 -0
  206. package/dist/excalidraw/shapes.js +57 -0
  207. package/dist/excalidraw/types.d.ts +8 -3
  208. package/dist/excalidraw/utils.d.ts +11 -1
  209. package/dist/excalidraw/utils.js +22 -0
  210. package/dist/prod/CascadiaCode-Regular-TMZI7IJ5.woff2 +0 -0
  211. package/dist/prod/ComicShanns-Regular-6TOETDFT.woff2 +0 -0
  212. package/dist/prod/Excalifont-Regular-CPKEUDVM.woff2 +0 -0
  213. package/dist/prod/LiberationSans-Regular-ZQD73GJM.woff2 +0 -0
  214. package/dist/prod/Virgil-Regular-YHAB2VGJ.woff2 +0 -0
  215. package/dist/prod/{en-EB2MBPAV.json → en-YNVBSAIL.json} +18 -4
  216. package/dist/prod/index.css +1 -1
  217. package/dist/prod/index.js +49 -53
  218. package/dist/utils/export.d.ts +2 -1
  219. package/dist/utils/export.js +2 -1
  220. package/dist/utils/geometry/geometry.d.ts +2 -1
  221. package/dist/utils/geometry/geometry.js +5 -1
  222. package/dist/utils/index.d.ts +1 -0
  223. package/dist/utils/index.js +1 -0
  224. package/history.ts +9 -2
  225. package/package.json +2 -2
  226. package/dist/browser/dev/Cascadia-CYPE3OJC.woff2 +0 -0
  227. package/dist/browser/dev/Virgil-UZN6MUT6.woff2 +0 -0
  228. package/dist/browser/dev/excalidraw-assets-dev/chunk-B4UMSLQQ.js.map +0 -7
  229. package/dist/browser/dev/excalidraw-assets-dev/chunk-EM6LVGFW.js.map +0 -7
  230. package/dist/browser/dev/excalidraw-assets-dev/dist-6QVAH5JA.js.map +0 -7
  231. package/dist/browser/prod/Cascadia-CYPE3OJC.woff2 +0 -0
  232. package/dist/browser/prod/Virgil-UZN6MUT6.woff2 +0 -0
  233. package/dist/browser/prod/excalidraw-assets/chunk-EGOLGOLD.js +0 -55
  234. package/dist/browser/prod/excalidraw-assets/dist-567JAXHK.js +0 -7
  235. package/dist/browser/prod/excalidraw-assets/en-6E7MYLWO.js +0 -1
  236. package/dist/browser/prod/excalidraw-assets/image-SI7BKULC.js +0 -1
  237. package/dist/dev/Cascadia-CYPE3OJC.woff2 +0 -0
  238. package/dist/dev/Virgil-UZN6MUT6.woff2 +0 -0
  239. package/dist/excalidraw/scene/Fonts.d.ts +0 -19
  240. package/dist/excalidraw/scene/Fonts.js +0 -66
  241. package/dist/prod/Cascadia-CYPE3OJC.woff2 +0 -0
  242. package/dist/prod/Virgil-UZN6MUT6.woff2 +0 -0
  243. /package/dist/browser/dev/{Assistant-Bold-ZDZZ6JHA.woff2 → excalidraw-assets-dev/Assistant-Bold-ZDZZ6JHA.woff2} +0 -0
  244. /package/dist/browser/dev/{Assistant-Medium-DZ25RZU3.woff2 → excalidraw-assets-dev/Assistant-Medium-DZ25RZU3.woff2} +0 -0
  245. /package/dist/browser/dev/{Assistant-Regular-PLF2XOGW.woff2 → excalidraw-assets-dev/Assistant-Regular-PLF2XOGW.woff2} +0 -0
  246. /package/dist/browser/dev/{Assistant-SemiBold-CZ5MX6FK.woff2 → excalidraw-assets-dev/Assistant-SemiBold-CZ5MX6FK.woff2} +0 -0
  247. /package/dist/browser/dev/excalidraw-assets-dev/{en-AZFA5HJJ.js.map → en-XV7OZCPP.js.map} +0 -0
  248. /package/dist/browser/dev/excalidraw-assets-dev/{image-V7E6IT6R.js.map → image-77HZYGLG.js.map} +0 -0
  249. /package/dist/browser/prod/{Assistant-Bold-ZDZZ6JHA.woff2 → excalidraw-assets/Assistant-Bold-ZDZZ6JHA.woff2} +0 -0
  250. /package/dist/browser/prod/{Assistant-Medium-DZ25RZU3.woff2 → excalidraw-assets/Assistant-Medium-DZ25RZU3.woff2} +0 -0
  251. /package/dist/browser/prod/{Assistant-Regular-PLF2XOGW.woff2 → excalidraw-assets/Assistant-Regular-PLF2XOGW.woff2} +0 -0
  252. /package/dist/browser/prod/{Assistant-SemiBold-CZ5MX6FK.woff2 → excalidraw-assets/Assistant-SemiBold-CZ5MX6FK.woff2} +0 -0
@@ -1,7 +1,7 @@
1
- import type { ElementsMap, ExcalidrawElement, ExcalidrawElementType, ExcalidrawTextContainer, ExcalidrawTextElement, ExcalidrawTextElementWithContainer, FontFamilyValues, FontString, NonDeletedExcalidrawElement } from "./types";
1
+ import type { ElementsMap, ExcalidrawElement, ExcalidrawElementType, ExcalidrawTextContainer, ExcalidrawTextElement, ExcalidrawTextElementWithContainer, FontString, NonDeletedExcalidrawElement } from "./types";
2
2
  import type { MaybeTransformHandleType } from "./transformHandles";
3
3
  import type { AppState } from "../types";
4
- import type { ExtractSetType, MakeBrand } from "../utility-types";
4
+ import type { ExtractSetType } from "../utility-types";
5
5
  export declare const normalizeText: (text: string) => string;
6
6
  export declare const redrawTextBoundingBox: (textElement: ExcalidrawTextElement, container: ExcalidrawElement | null, elementsMap: ElementsMap, informMutation?: boolean) => void;
7
7
  export declare const bindTextToShapeAfterDuplication: (newElements: ExcalidrawElement[], oldElements: ExcalidrawElement[], oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>) => void;
@@ -26,12 +26,8 @@ export declare const detectLineHeight: (textElement: ExcalidrawTextElement) => n
26
26
  * aligning with the W3C spec.
27
27
  */
28
28
  export declare const getLineHeightInPx: (fontSize: ExcalidrawTextElement["fontSize"], lineHeight: ExcalidrawTextElement["lineHeight"]) => number;
29
- /**
30
- * Calculates vertical offset for a text with alphabetic baseline.
31
- */
32
- export declare const getVerticalOffset: (fontFamily: ExcalidrawTextElement["fontFamily"], fontSize: ExcalidrawTextElement["fontSize"], lineHeightPx: number) => number;
33
29
  export declare const getApproxMinLineHeight: (fontSize: ExcalidrawTextElement["fontSize"], lineHeight: ExcalidrawTextElement["lineHeight"]) => number;
34
- export declare const getTextWidth: (text: string, font: FontString) => number;
30
+ export declare const getTextWidth: (text: string, font: FontString, forceAdvanceWidth?: true) => number;
35
31
  export declare const getTextHeight: (text: string, fontSize: number, lineHeight: ExcalidrawTextElement["lineHeight"]) => number;
36
32
  export declare const parseTokens: (text: string) => string[];
37
33
  export declare const wrapText: (text: string, font: FontString, maxWidth: number) => string;
@@ -42,7 +38,6 @@ export declare const charWidth: {
42
38
  export declare const getApproxMinLineWidth: (font: FontString, lineHeight: ExcalidrawTextElement["lineHeight"]) => number;
43
39
  export declare const getMinCharWidth: (font: FontString) => number;
44
40
  export declare const getMaxCharWidth: (font: FontString) => number;
45
- export declare const getApproxCharsToFitInWidth: (font: FontString, width: number) => number;
46
41
  export declare const getBoundTextElementId: (container: ExcalidrawElement | null) => string | null;
47
42
  export declare const getBoundTextElement: (element: ExcalidrawElement | null, elementsMap: ElementsMap) => ExcalidrawTextElementWithContainer | null;
48
43
  export declare const getContainerElement: (element: ExcalidrawTextElement | null, elementsMap: ElementsMap) => ExcalidrawTextContainer | null;
@@ -69,23 +64,5 @@ export declare const computeContainerDimensionForBoundText: (dimension: number,
69
64
  export declare const getBoundTextMaxWidth: (container: ExcalidrawElement, boundTextElement: ExcalidrawTextElement | null) => number;
70
65
  export declare const getBoundTextMaxHeight: (container: ExcalidrawElement, boundTextElement: ExcalidrawTextElementWithContainer) => number;
71
66
  export declare const isMeasureTextSupported: () => boolean;
72
- /** OS/2 sTypoAscender, https://learn.microsoft.com/en-us/typography/opentype/spec/os2#stypoascender */
73
- type sTypoAscender = number & MakeBrand<"sTypoAscender">;
74
- /** OS/2 sTypoDescender, https://learn.microsoft.com/en-us/typography/opentype/spec/os2#stypodescender */
75
- type sTypoDescender = number & MakeBrand<"sTypoDescender">;
76
- /**
77
- * Hardcoded metrics for default fonts, read by https://opentype.js.org/font-inspector.html.
78
- * For custom fonts, read these metrics from OS/2 table and extend this object.
79
- *
80
- * WARN: opentype does NOT open WOFF2 correctly, make sure to convert WOFF2 to TTF first.
81
- */
82
- export declare const FONT_METRICS: Record<number, {
83
- unitsPerEm: number;
84
- ascender: sTypoAscender;
85
- descender: sTypoDescender;
86
- }>;
87
- export declare const getDefaultLineHeight: (fontFamily: FontFamilyValues) => number & {
88
- _brand: "unitlessLineHeight";
89
- };
90
67
  export declare const getMinTextElementWidth: (font: FontString, lineHeight: ExcalidrawTextElement["lineHeight"]) => number;
91
68
  export {};
@@ -1,6 +1,6 @@
1
1
  import { getFontString, arrayToMap, isTestEnv, normalizeEOL } from "../utils";
2
2
  import { mutateElement } from "./mutateElement";
3
- import { ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO, ARROW_LABEL_WIDTH_FRACTION, BOUND_TEXT_PADDING, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, FONT_FAMILY, TEXT_ALIGN, VERTICAL_ALIGN, } from "../constants";
3
+ import { ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO, ARROW_LABEL_WIDTH_FRACTION, BOUND_TEXT_PADDING, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, TEXT_ALIGN, VERTICAL_ALIGN, } from "../constants";
4
4
  import { isTextElement } from ".";
5
5
  import { isBoundToContainer, isArrowElement } from "./typeChecks";
6
6
  import { LinearElementEditor } from "./linearElementEditor";
@@ -201,41 +201,58 @@ export const detectLineHeight = (textElement) => {
201
201
  export const getLineHeightInPx = (fontSize, lineHeight) => {
202
202
  return fontSize * lineHeight;
203
203
  };
204
- /**
205
- * Calculates vertical offset for a text with alphabetic baseline.
206
- */
207
- export const getVerticalOffset = (fontFamily, fontSize, lineHeightPx) => {
208
- const { unitsPerEm, ascender, descender } = FONT_METRICS[fontFamily] || FONT_METRICS[FONT_FAMILY.Helvetica];
209
- const fontSizeEm = fontSize / unitsPerEm;
210
- const lineGap = lineHeightPx - fontSizeEm * ascender + fontSizeEm * descender;
211
- const verticalOffset = fontSizeEm * ascender + lineGap;
212
- return verticalOffset;
213
- };
214
204
  // FIXME rename to getApproxMinContainerHeight
215
205
  export const getApproxMinLineHeight = (fontSize, lineHeight) => {
216
206
  return getLineHeightInPx(fontSize, lineHeight) + BOUND_TEXT_PADDING * 2;
217
207
  };
218
208
  let canvas;
219
- const getLineWidth = (text, font) => {
209
+ /**
210
+ * @param forceAdvanceWidth use to force retrieve the "advance width" ~ `metrics.width`, instead of the actual boundind box width.
211
+ *
212
+ * > The advance width is the distance between the glyph's initial pen position and the next glyph's initial pen position.
213
+ *
214
+ * We need to use the advance width as that's the closest thing to the browser wrapping algo, hence using it for:
215
+ * - text wrapping
216
+ * - wysiwyg editor (+padding)
217
+ *
218
+ * Everything else should be based on the actual bounding box width.
219
+ *
220
+ * `Math.ceil` of the final width adds additional buffer which stabilizes slight wrapping incosistencies.
221
+ */
222
+ const getLineWidth = (text, font, forceAdvanceWidth) => {
220
223
  if (!canvas) {
221
224
  canvas = document.createElement("canvas");
222
225
  }
223
226
  const canvas2dContext = canvas.getContext("2d");
224
227
  canvas2dContext.font = font;
225
- const width = canvas2dContext.measureText(text).width;
228
+ const metrics = canvas2dContext.measureText(text);
229
+ const advanceWidth = metrics.width;
230
+ // retrieve the actual bounding box width if these metrics are available (as of now > 95% coverage)
231
+ if (!forceAdvanceWidth &&
232
+ window.TextMetrics &&
233
+ "actualBoundingBoxLeft" in window.TextMetrics.prototype &&
234
+ "actualBoundingBoxRight" in window.TextMetrics.prototype) {
235
+ // could be negative, therefore getting the absolute value
236
+ const actualWidth = Math.abs(metrics.actualBoundingBoxLeft) +
237
+ Math.abs(metrics.actualBoundingBoxRight);
238
+ // fallback to advance width if the actual width is zero, i.e. on text editing start
239
+ // or when actual width does not respect whitespace chars, i.e. spaces
240
+ // otherwise actual width should always be bigger
241
+ return Math.max(actualWidth, advanceWidth);
242
+ }
226
243
  // since in test env the canvas measureText algo
227
244
  // doesn't measure text and instead just returns number of
228
245
  // characters hence we assume that each letteris 10px
229
246
  if (isTestEnv()) {
230
- return width * 10;
247
+ return advanceWidth * 10;
231
248
  }
232
- return width;
249
+ return advanceWidth;
233
250
  };
234
- export const getTextWidth = (text, font) => {
251
+ export const getTextWidth = (text, font, forceAdvanceWidth) => {
235
252
  const lines = splitIntoLines(text);
236
253
  let width = 0;
237
254
  lines.forEach((line) => {
238
- width = Math.max(width, getLineWidth(line, font));
255
+ width = Math.max(width, getLineWidth(line, font, forceAdvanceWidth));
239
256
  });
240
257
  return width;
241
258
  };
@@ -269,7 +286,7 @@ export const wrapText = (text, font, maxWidth) => {
269
286
  }
270
287
  const lines = [];
271
288
  const originalLines = text.split("\n");
272
- const spaceWidth = getLineWidth(" ", font);
289
+ const spaceAdvanceWidth = getLineWidth(" ", font, true);
273
290
  let currentLine = "";
274
291
  let currentLineWidthTillNow = 0;
275
292
  const push = (str) => {
@@ -281,18 +298,18 @@ export const wrapText = (text, font, maxWidth) => {
281
298
  currentLine = "";
282
299
  currentLineWidthTillNow = 0;
283
300
  };
284
- originalLines.forEach((originalLine) => {
285
- const currentLineWidth = getTextWidth(originalLine, font);
301
+ for (const originalLine of originalLines) {
302
+ const currentLineWidth = getLineWidth(originalLine, font, true);
286
303
  // Push the line if its <= maxWidth
287
304
  if (currentLineWidth <= maxWidth) {
288
305
  lines.push(originalLine);
289
- return; // continue
306
+ continue;
290
307
  }
291
308
  const words = parseTokens(originalLine);
292
309
  resetParams();
293
310
  let index = 0;
294
311
  while (index < words.length) {
295
- const currentWordWidth = getLineWidth(words[index], font);
312
+ const currentWordWidth = getLineWidth(words[index], font, true);
296
313
  // This will only happen when single word takes entire width
297
314
  if (currentWordWidth === maxWidth) {
298
315
  push(words[index]);
@@ -306,20 +323,24 @@ export const wrapText = (text, font, maxWidth) => {
306
323
  resetParams();
307
324
  while (words[index].length > 0) {
308
325
  const currentChar = String.fromCodePoint(words[index].codePointAt(0));
309
- const width = charWidth.calculate(currentChar, font);
310
- currentLineWidthTillNow += width;
326
+ const line = currentLine + currentChar;
327
+ // use advance width instead of the actual width as it's closest to the browser wapping algo
328
+ // use width of the whole line instead of calculating individual chars to accomodate for kerning
329
+ const lineAdvanceWidth = getLineWidth(line, font, true);
330
+ const charAdvanceWidth = charWidth.calculate(currentChar, font);
331
+ currentLineWidthTillNow = lineAdvanceWidth;
311
332
  words[index] = words[index].slice(currentChar.length);
312
333
  if (currentLineWidthTillNow >= maxWidth) {
313
334
  push(currentLine);
314
335
  currentLine = currentChar;
315
- currentLineWidthTillNow = width;
336
+ currentLineWidthTillNow = charAdvanceWidth;
316
337
  }
317
338
  else {
318
- currentLine += currentChar;
339
+ currentLine = line;
319
340
  }
320
341
  }
321
342
  // push current line if appending space exceeds max width
322
- if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
343
+ if (currentLineWidthTillNow + spaceAdvanceWidth >= maxWidth) {
323
344
  push(currentLine);
324
345
  resetParams();
325
346
  // space needs to be appended before next word
@@ -329,7 +350,7 @@ export const wrapText = (text, font, maxWidth) => {
329
350
  }
330
351
  else if (!currentLine.endsWith("-")) {
331
352
  currentLine += " ";
332
- currentLineWidthTillNow += spaceWidth;
353
+ currentLineWidthTillNow += spaceAdvanceWidth;
333
354
  }
334
355
  index++;
335
356
  }
@@ -337,7 +358,7 @@ export const wrapText = (text, font, maxWidth) => {
337
358
  // Start appending words in a line till max width reached
338
359
  while (currentLineWidthTillNow < maxWidth && index < words.length) {
339
360
  const word = words[index];
340
- currentLineWidthTillNow = getLineWidth(currentLine + word, font);
361
+ currentLineWidthTillNow = getLineWidth(currentLine + word, font, true);
341
362
  if (currentLineWidthTillNow > maxWidth) {
342
363
  push(currentLine);
343
364
  resetParams();
@@ -352,7 +373,7 @@ export const wrapText = (text, font, maxWidth) => {
352
373
  currentLine += " ";
353
374
  }
354
375
  // Push the word if appending space exceeds max width
355
- if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
376
+ if (currentLineWidthTillNow + spaceAdvanceWidth >= maxWidth) {
356
377
  if (shouldAppendSpace) {
357
378
  lines.push(currentLine.slice(0, -1));
358
379
  }
@@ -370,7 +391,7 @@ export const wrapText = (text, font, maxWidth) => {
370
391
  currentLine = currentLine.slice(0, -1);
371
392
  push(currentLine);
372
393
  }
373
- });
394
+ }
374
395
  return lines.join("\n");
375
396
  };
376
397
  export const charWidth = (() => {
@@ -381,7 +402,7 @@ export const charWidth = (() => {
381
402
  cachedCharWidth[font] = [];
382
403
  }
383
404
  if (!cachedCharWidth[font][ascii]) {
384
- const width = getLineWidth(char, font);
405
+ const width = getLineWidth(char, font, true);
385
406
  cachedCharWidth[font][ascii] = width;
386
407
  }
387
408
  return cachedCharWidth[font][ascii];
@@ -420,32 +441,9 @@ export const getMaxCharWidth = (font) => {
420
441
  const cacheWithOutEmpty = cache.filter((val) => val !== undefined);
421
442
  return Math.max(...cacheWithOutEmpty);
422
443
  };
423
- export const getApproxCharsToFitInWidth = (font, width) => {
424
- // Generally lower case is used so converting to lower case
425
- const dummyText = DUMMY_TEXT.toLocaleLowerCase();
426
- const batchLength = 6;
427
- let index = 0;
428
- let widthTillNow = 0;
429
- let str = "";
430
- while (widthTillNow <= width) {
431
- const batch = dummyText.substr(index, index + batchLength);
432
- str += batch;
433
- widthTillNow += getLineWidth(str, font);
434
- if (index === dummyText.length - 1) {
435
- index = 0;
436
- }
437
- index = index + batchLength;
438
- }
439
- while (widthTillNow > width) {
440
- str = str.substr(0, str.length - 1);
441
- widthTillNow = getLineWidth(str, font);
442
- }
443
- return str.length;
444
- };
445
444
  export const getBoundTextElementId = (container) => {
446
445
  return container?.boundElements?.length
447
- ? container?.boundElements?.filter((ele) => ele.type === "text")[0]?.id ||
448
- null
446
+ ? container?.boundElements?.find((ele) => ele.type === "text")?.id || null
449
447
  : null;
450
448
  };
451
449
  export const getBoundTextElement = (element, elementsMap) => {
@@ -612,60 +610,6 @@ export const isMeasureTextSupported = () => {
612
610
  }));
613
611
  return width > 0;
614
612
  };
615
- /**
616
- * Unitless line height
617
- *
618
- * In previous versions we used `normal` line height, which browsers interpret
619
- * differently, and based on font-family and font-size.
620
- *
621
- * To make line heights consistent across browsers we hardcode the values for
622
- * each of our fonts based on most common average line-heights.
623
- * See https://github.com/excalidraw/excalidraw/pull/6360#issuecomment-1477635971
624
- * where the values come from.
625
- */
626
- const DEFAULT_LINE_HEIGHT = {
627
- // ~1.25 is the average for Virgil in WebKit and Blink.
628
- // Gecko (FF) uses ~1.28.
629
- [FONT_FAMILY.Virgil]: 1.25,
630
- // ~1.15 is the average for Helvetica in WebKit and Blink.
631
- [FONT_FAMILY.Helvetica]: 1.15,
632
- // ~1.2 is the average for Cascadia in WebKit and Blink, and kinda Gecko too
633
- [FONT_FAMILY.Cascadia]: 1.2,
634
- };
635
- /**
636
- * Hardcoded metrics for default fonts, read by https://opentype.js.org/font-inspector.html.
637
- * For custom fonts, read these metrics from OS/2 table and extend this object.
638
- *
639
- * WARN: opentype does NOT open WOFF2 correctly, make sure to convert WOFF2 to TTF first.
640
- */
641
- export const FONT_METRICS = {
642
- [FONT_FAMILY.Virgil]: {
643
- unitsPerEm: 1000,
644
- ascender: 886,
645
- descender: -374,
646
- },
647
- [FONT_FAMILY.Helvetica]: {
648
- unitsPerEm: 2048,
649
- ascender: 1577,
650
- descender: -471,
651
- },
652
- [FONT_FAMILY.Cascadia]: {
653
- unitsPerEm: 2048,
654
- ascender: 1977,
655
- descender: -480,
656
- },
657
- [FONT_FAMILY.Assistant]: {
658
- unitsPerEm: 1000,
659
- ascender: 1021,
660
- descender: -287,
661
- },
662
- };
663
- export const getDefaultLineHeight = (fontFamily) => {
664
- if (fontFamily in DEFAULT_LINE_HEIGHT) {
665
- return DEFAULT_LINE_HEIGHT[fontFamily];
666
- }
667
- return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
668
- };
669
613
  export const getMinTextElementWidth = (font, lineHeight) => {
670
614
  return measureText("", font, lineHeight).width + BOUND_TEXT_PADDING * 2;
671
615
  };
@@ -2,7 +2,7 @@ import { CODES, KEYS } from "../keys";
2
2
  import { isWritableElement, getFontString, getFontFamilyString, isTestEnv, } from "../utils";
3
3
  import Scene from "../scene/Scene";
4
4
  import { isArrowElement, isBoundToContainer, isTextElement, } from "./typeChecks";
5
- import { CLASSES } from "../constants";
5
+ import { CLASSES, isSafari } from "../constants";
6
6
  import { bumpVersion, mutateElement } from "./mutateElement";
7
7
  import { getBoundTextElementId, getContainerElement, getTextElementAngle, getTextWidth, normalizeText, redrawTextBoundingBox, wrapText, getBoundTextMaxHeight, getBoundTextMaxWidth, computeContainerDimensionForBoundText, computeBoundTextPosition, getBoundTextElement, } from "./textElement";
8
8
  import { actionDecreaseFontSize, actionIncreaseFontSize, } from "../actions/actionProperties";
@@ -50,10 +50,12 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
50
50
  let coordX = updatedTextElement.x;
51
51
  let coordY = updatedTextElement.y;
52
52
  const container = getContainerElement(updatedTextElement, app.scene.getNonDeletedElementsMap());
53
+ let width = updatedTextElement.width;
54
+ // set to element height by default since that's
55
+ // what is going to be used for unbounded text
56
+ let height = updatedTextElement.height;
53
57
  let maxWidth = updatedTextElement.width;
54
58
  let maxHeight = updatedTextElement.height;
55
- let textElementWidth = updatedTextElement.width;
56
- const textElementHeight = updatedTextElement.height;
57
59
  if (container && updatedTextElement.containerId) {
58
60
  if (isArrowElement(container)) {
59
61
  const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(container, updatedTextElement, elementsMap);
@@ -74,8 +76,8 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
74
76
  maxWidth = getBoundTextMaxWidth(container, updatedTextElement);
75
77
  maxHeight = getBoundTextMaxHeight(container, updatedTextElement);
76
78
  // autogrow container height if text exceeds
77
- if (!isArrowElement(container) && textElementHeight > maxHeight) {
78
- const targetContainerHeight = computeContainerDimensionForBoundText(textElementHeight, container.type);
79
+ if (!isArrowElement(container) && height > maxHeight) {
80
+ const targetContainerHeight = computeContainerDimensionForBoundText(height, container.type);
79
81
  mutateElement(container, { height: targetContainerHeight });
80
82
  return;
81
83
  }
@@ -84,8 +86,8 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
84
86
  // is reached when text is removed
85
87
  !isArrowElement(container) &&
86
88
  container.height > originalContainerData.height &&
87
- textElementHeight < maxHeight) {
88
- const targetContainerHeight = computeContainerDimensionForBoundText(textElementHeight, container.type);
89
+ height < maxHeight) {
90
+ const targetContainerHeight = computeContainerDimensionForBoundText(height, container.type);
89
91
  mutateElement(container, { height: targetContainerHeight });
90
92
  }
91
93
  else {
@@ -109,22 +111,30 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
109
111
  }
110
112
  if (!container) {
111
113
  maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
112
- textElementWidth = Math.min(textElementWidth, maxWidth);
114
+ width = Math.min(width, maxWidth);
113
115
  }
114
116
  else {
115
- textElementWidth += 0.5;
117
+ width += 0.5;
116
118
  }
119
+ // add 5% buffer otherwise it causes wysiwyg to jump
120
+ height *= 1.05;
121
+ const font = getFontString(updatedTextElement);
122
+ // adding left and right padding buffer, so that browser does not cut the glyphs (does not work in Safari)
123
+ const padding = !isSafari
124
+ ? Math.ceil(updatedTextElement.fontSize / 2)
125
+ : 0;
117
126
  // Make sure text editor height doesn't go beyond viewport
118
127
  const editorMaxHeight = (appState.height - viewportY) / appState.zoom.value;
119
128
  Object.assign(editable.style, {
120
- font: getFontString(updatedTextElement),
129
+ font,
121
130
  // must be defined *after* font ¯\_(ツ)_/¯
122
131
  lineHeight: updatedTextElement.lineHeight,
123
- width: `${textElementWidth}px`,
124
- height: `${textElementHeight}px`,
125
- left: `${viewportX}px`,
132
+ width: `${width}px`,
133
+ height: `${height}px`,
134
+ left: `${viewportX - padding}px`,
126
135
  top: `${viewportY}px`,
127
- transform: getTransform(textElementWidth, textElementHeight, getTextElementAngle(updatedTextElement, container), appState, maxWidth, editorMaxHeight),
136
+ transform: getTransform(width, height, getTextElementAngle(updatedTextElement, container), appState, maxWidth, editorMaxHeight),
137
+ padding: `0 ${padding}px`,
128
138
  textAlign,
129
139
  verticalAlign,
130
140
  color: updatedTextElement.strokeColor,
@@ -160,7 +170,6 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
160
170
  minHeight: "1em",
161
171
  backfaceVisibility: "hidden",
162
172
  margin: 0,
163
- padding: 0,
164
173
  border: 0,
165
174
  outline: 0,
166
175
  resize: "none",
@@ -194,7 +203,7 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
194
203
  if (container) {
195
204
  const boundTextElement = getBoundTextElement(container, app.scene.getNonDeletedElementsMap());
196
205
  const wrappedText = wrapText(`${editable.value}${data}`, font, getBoundTextMaxWidth(container, boundTextElement));
197
- const width = getTextWidth(wrappedText, font);
206
+ const width = getTextWidth(wrappedText, font, true);
198
207
  editable.style.width = `${width}px`;
199
208
  }
200
209
  };
@@ -320,8 +329,10 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
320
329
  .reverse();
321
330
  };
322
331
  const stopEvent = (event) => {
323
- event.preventDefault();
324
- event.stopPropagation();
332
+ if (event.target instanceof HTMLCanvasElement) {
333
+ event.preventDefault();
334
+ event.stopPropagation();
335
+ }
325
336
  };
326
337
  // using a state variable instead of passing it to the handleSubmit callback
327
338
  // so that we don't need to create separate a callback for event handlers
@@ -393,45 +404,26 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
393
404
  // Also to handle cases such as picking a color which would trigger a blur
394
405
  // in that same tick.
395
406
  const target = event?.target;
396
- const isTargetPickerTrigger = target instanceof HTMLElement &&
397
- target.classList.contains("active-color");
407
+ const isPropertiesTrigger = target instanceof HTMLElement &&
408
+ target.classList.contains("properties-trigger");
398
409
  setTimeout(() => {
399
410
  editable.onblur = handleSubmit;
400
- if (isTargetPickerTrigger) {
401
- const callback = (mutationList, observer) => {
402
- const radixIsRemoved = mutationList.find((mutation) => mutation.removedNodes.length > 0 &&
403
- mutation.removedNodes[0].dataset
404
- ?.radixPopperContentWrapper !== undefined);
405
- if (radixIsRemoved) {
406
- // should work without this in theory
407
- // and i think it does actually but radix probably somewhere,
408
- // somehow sets the focus elsewhere
409
- setTimeout(() => {
410
- editable.focus();
411
- });
412
- observer.disconnect();
413
- }
414
- };
415
- const observer = new MutationObserver(callback);
416
- observer.observe(document.querySelector(".excalidraw-container"), {
417
- childList: true,
418
- });
419
- }
420
411
  // case: clicking on the same property → no change → no update → no focus
421
- if (!isTargetPickerTrigger) {
412
+ if (!isPropertiesTrigger) {
422
413
  editable.focus();
423
414
  }
424
415
  });
425
416
  };
426
417
  // prevent blur when changing properties from the menu
427
418
  const onPointerDown = (event) => {
428
- const isTargetPickerTrigger = event.target instanceof HTMLElement &&
429
- event.target.classList.contains("active-color");
419
+ const target = event?.target;
420
+ const isPropertiesTrigger = target instanceof HTMLElement &&
421
+ target.classList.contains("properties-trigger");
430
422
  if (((event.target instanceof HTMLElement ||
431
423
  event.target instanceof SVGElement) &&
432
424
  event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) &&
433
425
  !isWritableElement(event.target)) ||
434
- isTargetPickerTrigger) {
426
+ isPropertiesTrigger) {
435
427
  editable.onblur = null;
436
428
  window.addEventListener("pointerup", bindBlurEvent);
437
429
  // handle edge-case where pointerup doesn't fire e.g. due to user
@@ -439,7 +431,7 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
439
431
  window.addEventListener("blur", handleSubmit);
440
432
  }
441
433
  else if (event.target instanceof HTMLElement &&
442
- !event.target.contains(editable) &&
434
+ event.target instanceof HTMLCanvasElement &&
443
435
  // Vitest simply ignores stopPropagation, capture-mode, or rAF
444
436
  // so without introducing crazier hacks, nothing we can do
445
437
  !isTestEnv()) {
@@ -457,8 +449,8 @@ export const textWysiwyg = ({ id, onChange, onSubmit, getViewportCoords, element
457
449
  // handle updates of textElement properties of editing element
458
450
  const unbindUpdate = Scene.getScene(element).onUpdate(() => {
459
451
  updateWysiwygStyle();
460
- const isColorPickerActive = !!document.activeElement?.closest(".color-picker-content");
461
- if (!isColorPickerActive) {
452
+ const isPopupOpened = !!document.activeElement?.closest(".properties-content");
453
+ if (!isPopupOpened) {
462
454
  editable.focus();
463
455
  }
464
456
  });
@@ -1,6 +1,6 @@
1
1
  import { getElementAbsoluteCoords } from "./bounds";
2
2
  import { rotate } from "../math";
3
- import { isFrameLikeElement, isLinearElement } from "./typeChecks";
3
+ import { isElbowArrow, isFrameLikeElement, isLinearElement, } from "./typeChecks";
4
4
  import { DEFAULT_TRANSFORM_HANDLE_SPACING, isAndroid, isIOS, } from "../constants";
5
5
  const transformHandleSizes = {
6
6
  mouse: 8,
@@ -116,7 +116,9 @@ export const getTransformHandles = (element, zoom, elementsMap, pointerType = "m
116
116
  // so that when locked element is selected (especially when you toggle lock
117
117
  // via keyboard) the locked element is visually distinct, indicating
118
118
  // you can't move/resize
119
- if (element.locked) {
119
+ if (element.locked ||
120
+ // Elbow arrows cannot be rotated
121
+ isElbowArrow(element)) {
120
122
  return {};
121
123
  }
122
124
  if (element.type === "freedraw" || isLinearElement(element)) {
@@ -159,6 +161,9 @@ export const shouldShowBoundingBox = (elements, appState) => {
159
161
  return true;
160
162
  }
161
163
  const element = elements[0];
164
+ if (isElbowArrow(element)) {
165
+ return false;
166
+ }
162
167
  if (!isLinearElement(element)) {
163
168
  return true;
164
169
  }
@@ -1,6 +1,6 @@
1
1
  import type { ElementOrToolType } from "../types";
2
2
  import type { MarkNonNullable } from "../utility-types";
3
- import type { ExcalidrawElement, ExcalidrawTextElement, ExcalidrawEmbeddableElement, ExcalidrawLinearElement, ExcalidrawBindableElement, ExcalidrawFreeDrawElement, InitializedExcalidrawImageElement, ExcalidrawImageElement, ExcalidrawTextElementWithContainer, ExcalidrawTextContainer, ExcalidrawFrameElement, RoundnessType, ExcalidrawFrameLikeElement, ExcalidrawElementType, ExcalidrawIframeElement, ExcalidrawIframeLikeElement, ExcalidrawMagicFrameElement, ExcalidrawArrowElement } from "./types";
3
+ import type { ExcalidrawElement, ExcalidrawTextElement, ExcalidrawEmbeddableElement, ExcalidrawLinearElement, ExcalidrawBindableElement, ExcalidrawFreeDrawElement, InitializedExcalidrawImageElement, ExcalidrawImageElement, ExcalidrawTextElementWithContainer, ExcalidrawTextContainer, ExcalidrawFrameElement, RoundnessType, ExcalidrawFrameLikeElement, ExcalidrawElementType, ExcalidrawIframeElement, ExcalidrawIframeLikeElement, ExcalidrawMagicFrameElement, ExcalidrawArrowElement, ExcalidrawElbowArrowElement, PointBinding, FixedPointBinding } from "./types";
4
4
  export declare const isInitializedImageElement: (element: ExcalidrawElement | null) => element is InitializedExcalidrawImageElement;
5
5
  export declare const isImageElement: (element: ExcalidrawElement | null) => element is ExcalidrawImageElement;
6
6
  export declare const isEmbeddableElement: (element: ExcalidrawElement | null | undefined) => element is ExcalidrawEmbeddableElement;
@@ -14,10 +14,12 @@ export declare const isFreeDrawElement: (element?: ExcalidrawElement | null) =>
14
14
  export declare const isFreeDrawElementType: (elementType: ExcalidrawElementType) => boolean;
15
15
  export declare const isLinearElement: (element?: ExcalidrawElement | null) => element is ExcalidrawLinearElement;
16
16
  export declare const isArrowElement: (element?: ExcalidrawElement | null) => element is ExcalidrawArrowElement;
17
+ export declare const isElbowArrow: (element?: ExcalidrawElement) => element is ExcalidrawElbowArrowElement;
17
18
  export declare const isLinearElementType: (elementType: ElementOrToolType) => boolean;
18
19
  export declare const isBindingElement: (element?: ExcalidrawElement | null, includeLocked?: boolean) => element is ExcalidrawLinearElement;
19
20
  export declare const isBindingElementType: (elementType: ElementOrToolType) => boolean;
20
- export declare const isBindableElement: (element: ExcalidrawElement | null, includeLocked?: boolean) => element is ExcalidrawBindableElement;
21
+ export declare const isBindableElement: (element: ExcalidrawElement | null | undefined, includeLocked?: boolean) => element is ExcalidrawBindableElement;
22
+ export declare const isRectanguloidElement: (element?: ExcalidrawElement | null) => element is ExcalidrawBindableElement;
21
23
  export declare const isTextBindableContainer: (element: ExcalidrawElement | null, includeLocked?: boolean) => element is ExcalidrawTextContainer;
22
24
  export declare const isExcalidrawElement: (element: any) => element is ExcalidrawElement;
23
25
  export declare const hasBoundTextElement: (element: ExcalidrawElement | null) => element is MarkNonNullable<ExcalidrawBindableElement, "boundElements">;
@@ -30,3 +32,4 @@ export declare const getDefaultRoundnessTypeForElement: (element: ExcalidrawElem
30
32
  } | {
31
33
  type: 3;
32
34
  } | null;
35
+ export declare const isFixedPointBinding: (binding: PointBinding) => binding is FixedPointBinding;
@@ -40,6 +40,9 @@ export const isLinearElement = (element) => {
40
40
  export const isArrowElement = (element) => {
41
41
  return element != null && element.type === "arrow";
42
42
  };
43
+ export const isElbowArrow = (element) => {
44
+ return isArrowElement(element) && element.elbowed;
45
+ };
43
46
  export const isLinearElementType = (elementType) => {
44
47
  return (elementType === "arrow" || elementType === "line" // || elementType === "freedraw"
45
48
  );
@@ -65,6 +68,17 @@ export const isBindableElement = (element, includeLocked = true) => {
65
68
  element.type === "magicframe" ||
66
69
  (element.type === "text" && !element.containerId)));
67
70
  };
71
+ export const isRectanguloidElement = (element) => {
72
+ return (element != null &&
73
+ (element.type === "rectangle" ||
74
+ element.type === "diamond" ||
75
+ element.type === "image" ||
76
+ element.type === "iframe" ||
77
+ element.type === "embeddable" ||
78
+ element.type === "frame" ||
79
+ element.type === "magicframe" ||
80
+ (element.type === "text" && !element.containerId)));
81
+ };
68
82
  export const isTextBindableContainer = (element, includeLocked = true) => {
69
83
  return (element != null &&
70
84
  (!element.locked || includeLocked === true) &&
@@ -142,3 +156,6 @@ export const getDefaultRoundnessTypeForElement = (element) => {
142
156
  }
143
157
  return null;
144
158
  };
159
+ export const isFixedPointBinding = (binding) => {
160
+ return binding.fixedPoint != null;
161
+ };
@@ -1,6 +1,6 @@
1
1
  import type { Point } from "../types";
2
2
  import type { FONT_FAMILY, ROUNDNESS, TEXT_ALIGN, THEME, VERTICAL_ALIGN } from "../constants";
3
- import type { MakeBrand, MarkNonNullable, ValueOf } from "../utility-types";
3
+ import type { MakeBrand, MarkNonNullable, Merge, ValueOf } from "../utility-types";
4
4
  import type { MagicCacheData } from "../data/magic";
5
5
  export type ChartType = "bar" | "line";
6
6
  export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
@@ -174,11 +174,16 @@ export type ExcalidrawTextContainer = ExcalidrawRectangleElement | ExcalidrawDia
174
174
  export type ExcalidrawTextElementWithContainer = {
175
175
  containerId: ExcalidrawTextContainer["id"];
176
176
  } & ExcalidrawTextElement;
177
+ export type FixedPoint = [number, number];
177
178
  export type PointBinding = {
178
179
  elementId: ExcalidrawBindableElement["id"];
179
180
  focus: number;
180
181
  gap: number;
182
+ fixedPoint: FixedPoint | null;
181
183
  };
184
+ export type FixedPointBinding = Merge<PointBinding, {
185
+ fixedPoint: FixedPoint;
186
+ }>;
182
187
  export type Arrowhead = "arrow" | "bar" | "dot" | "circle" | "circle_outline" | "triangle" | "triangle_outline" | "diamond" | "diamond_outline";
183
188
  export type ExcalidrawLinearElement = _ExcalidrawElementBase & Readonly<{
184
189
  type: "line" | "arrow";
@@ -191,6 +196,12 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase & Readonly<{
191
196
  }>;
192
197
  export type ExcalidrawArrowElement = ExcalidrawLinearElement & Readonly<{
193
198
  type: "arrow";
199
+ elbowed: boolean;
200
+ }>;
201
+ export type ExcalidrawElbowArrowElement = Merge<ExcalidrawArrowElement, {
202
+ elbowed: true;
203
+ startBinding: FixedPointBinding | null;
204
+ endBinding: FixedPointBinding | null;
194
205
  }>;
195
206
  export type ExcalidrawFreeDrawElement = _ExcalidrawElementBase & Readonly<{
196
207
  type: "freedraw";