@dxos/plugin-tldraw 0.10.0

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 (401) hide show
  1. package/LICENSE +105 -0
  2. package/PLUGIN.mdl +412 -0
  3. package/README.md +3 -0
  4. package/dist/assets/embed-icons/codepen.png +0 -0
  5. package/dist/assets/embed-icons/codesandbox.png +0 -0
  6. package/dist/assets/embed-icons/desmos.png +0 -0
  7. package/dist/assets/embed-icons/excalidraw.png +0 -0
  8. package/dist/assets/embed-icons/felt.png +0 -0
  9. package/dist/assets/embed-icons/figma.png +0 -0
  10. package/dist/assets/embed-icons/github_gist.png +0 -0
  11. package/dist/assets/embed-icons/google_calendar.png +0 -0
  12. package/dist/assets/embed-icons/google_maps.png +0 -0
  13. package/dist/assets/embed-icons/google_slides.png +0 -0
  14. package/dist/assets/embed-icons/observable.png +0 -0
  15. package/dist/assets/embed-icons/replit.png +0 -0
  16. package/dist/assets/embed-icons/scratch.png +0 -0
  17. package/dist/assets/embed-icons/spotify.png +0 -0
  18. package/dist/assets/embed-icons/tldraw.png +0 -0
  19. package/dist/assets/embed-icons/val_town.png +0 -0
  20. package/dist/assets/embed-icons/vimeo.png +0 -0
  21. package/dist/assets/embed-icons/youtube.png +0 -0
  22. package/dist/assets/fonts/IBMPlexMono-Medium.woff2 +0 -0
  23. package/dist/assets/fonts/IBMPlexSans-Medium.woff2 +0 -0
  24. package/dist/assets/fonts/IBMPlexSerif-Medium.woff2 +0 -0
  25. package/dist/assets/fonts/Montserrat-Regular.woff2 +0 -0
  26. package/dist/assets/fonts/Shantell_Sans-Tldrawish.woff2 +0 -0
  27. package/dist/assets/icons/icon/0_merged.svg +1 -0
  28. package/dist/assets/icons/icon/align-bottom.svg +1 -0
  29. package/dist/assets/icons/icon/align-center-horizontal.svg +1 -0
  30. package/dist/assets/icons/icon/align-center-vertical.svg +1 -0
  31. package/dist/assets/icons/icon/align-left.svg +1 -0
  32. package/dist/assets/icons/icon/align-right.svg +1 -0
  33. package/dist/assets/icons/icon/align-top.svg +1 -0
  34. package/dist/assets/icons/icon/arrow-left.svg +1 -0
  35. package/dist/assets/icons/icon/arrowhead-arrow.svg +1 -0
  36. package/dist/assets/icons/icon/arrowhead-bar.svg +1 -0
  37. package/dist/assets/icons/icon/arrowhead-diamond.svg +1 -0
  38. package/dist/assets/icons/icon/arrowhead-dot.svg +1 -0
  39. package/dist/assets/icons/icon/arrowhead-none.svg +1 -0
  40. package/dist/assets/icons/icon/arrowhead-square.svg +1 -0
  41. package/dist/assets/icons/icon/arrowhead-triangle-inverted.svg +1 -0
  42. package/dist/assets/icons/icon/arrowhead-triangle.svg +1 -0
  43. package/dist/assets/icons/icon/blob.svg +1 -0
  44. package/dist/assets/icons/icon/bring-forward.svg +1 -0
  45. package/dist/assets/icons/icon/bring-to-front.svg +1 -0
  46. package/dist/assets/icons/icon/broken.svg +1 -0
  47. package/dist/assets/icons/icon/chat-teardrop-text.svg +1 -0
  48. package/dist/assets/icons/icon/check-circle.svg +1 -0
  49. package/dist/assets/icons/icon/check.svg +1 -0
  50. package/dist/assets/icons/icon/chevron-down.svg +1 -0
  51. package/dist/assets/icons/icon/chevron-left.svg +1 -0
  52. package/dist/assets/icons/icon/chevron-right.svg +1 -0
  53. package/dist/assets/icons/icon/chevron-up.svg +1 -0
  54. package/dist/assets/icons/icon/chevrons-ne.svg +1 -0
  55. package/dist/assets/icons/icon/chevrons-sw.svg +1 -0
  56. package/dist/assets/icons/icon/clipboard-copied.svg +1 -0
  57. package/dist/assets/icons/icon/clipboard-copy.svg +1 -0
  58. package/dist/assets/icons/icon/color.svg +1 -0
  59. package/dist/assets/icons/icon/cross-2.svg +1 -0
  60. package/dist/assets/icons/icon/cross-circle.svg +1 -0
  61. package/dist/assets/icons/icon/dash-dashed.svg +1 -0
  62. package/dist/assets/icons/icon/dash-dotted.svg +1 -0
  63. package/dist/assets/icons/icon/dash-draw.svg +1 -0
  64. package/dist/assets/icons/icon/dash-solid.svg +1 -0
  65. package/dist/assets/icons/icon/disconnected.svg +1 -0
  66. package/dist/assets/icons/icon/discord.svg +1 -0
  67. package/dist/assets/icons/icon/distribute-horizontal.svg +1 -0
  68. package/dist/assets/icons/icon/distribute-vertical.svg +1 -0
  69. package/dist/assets/icons/icon/dot.svg +1 -0
  70. package/dist/assets/icons/icon/dots-horizontal.svg +1 -0
  71. package/dist/assets/icons/icon/dots-vertical.svg +1 -0
  72. package/dist/assets/icons/icon/drag-handle-dots.svg +1 -0
  73. package/dist/assets/icons/icon/duplicate.svg +1 -0
  74. package/dist/assets/icons/icon/edit.svg +1 -0
  75. package/dist/assets/icons/icon/external-link.svg +1 -0
  76. package/dist/assets/icons/icon/fill-fill.svg +1 -0
  77. package/dist/assets/icons/icon/fill-none.svg +1 -0
  78. package/dist/assets/icons/icon/fill-pattern.svg +1 -0
  79. package/dist/assets/icons/icon/fill-semi.svg +1 -0
  80. package/dist/assets/icons/icon/fill-solid.svg +1 -0
  81. package/dist/assets/icons/icon/follow.svg +1 -0
  82. package/dist/assets/icons/icon/following.svg +1 -0
  83. package/dist/assets/icons/icon/font-draw.svg +8 -0
  84. package/dist/assets/icons/icon/font-mono.svg +1 -0
  85. package/dist/assets/icons/icon/font-sans.svg +1 -0
  86. package/dist/assets/icons/icon/font-serif.svg +1 -0
  87. package/dist/assets/icons/icon/geo-arrow-down.svg +1 -0
  88. package/dist/assets/icons/icon/geo-arrow-left.svg +1 -0
  89. package/dist/assets/icons/icon/geo-arrow-right.svg +1 -0
  90. package/dist/assets/icons/icon/geo-arrow-up.svg +1 -0
  91. package/dist/assets/icons/icon/geo-check-box.svg +1 -0
  92. package/dist/assets/icons/icon/geo-cloud.svg +1 -0
  93. package/dist/assets/icons/icon/geo-diamond.svg +1 -0
  94. package/dist/assets/icons/icon/geo-ellipse.svg +1 -0
  95. package/dist/assets/icons/icon/geo-heart.svg +1 -0
  96. package/dist/assets/icons/icon/geo-hexagon.svg +1 -0
  97. package/dist/assets/icons/icon/geo-octagon.svg +1 -0
  98. package/dist/assets/icons/icon/geo-oval.svg +1 -0
  99. package/dist/assets/icons/icon/geo-pentagon.svg +1 -0
  100. package/dist/assets/icons/icon/geo-rectangle.svg +1 -0
  101. package/dist/assets/icons/icon/geo-rhombus-2.svg +1 -0
  102. package/dist/assets/icons/icon/geo-rhombus.svg +1 -0
  103. package/dist/assets/icons/icon/geo-star.svg +1 -0
  104. package/dist/assets/icons/icon/geo-trapezoid.svg +1 -0
  105. package/dist/assets/icons/icon/geo-triangle.svg +1 -0
  106. package/dist/assets/icons/icon/geo-x-box.svg +1 -0
  107. package/dist/assets/icons/icon/github.svg +1 -0
  108. package/dist/assets/icons/icon/group.svg +1 -0
  109. package/dist/assets/icons/icon/horizontal-align-end.svg +1 -0
  110. package/dist/assets/icons/icon/horizontal-align-middle.svg +1 -0
  111. package/dist/assets/icons/icon/horizontal-align-start.svg +1 -0
  112. package/dist/assets/icons/icon/icon-names.json +143 -0
  113. package/dist/assets/icons/icon/info-circle.svg +1 -0
  114. package/dist/assets/icons/icon/leading.svg +1 -0
  115. package/dist/assets/icons/icon/link.svg +1 -0
  116. package/dist/assets/icons/icon/lock.svg +1 -0
  117. package/dist/assets/icons/icon/menu.svg +1 -0
  118. package/dist/assets/icons/icon/minus.svg +1 -0
  119. package/dist/assets/icons/icon/mixed.svg +1 -0
  120. package/dist/assets/icons/icon/pack.svg +1 -0
  121. package/dist/assets/icons/icon/plus.svg +1 -0
  122. package/dist/assets/icons/icon/question-mark-circle.svg +1 -0
  123. package/dist/assets/icons/icon/question-mark.svg +1 -0
  124. package/dist/assets/icons/icon/redo.svg +1 -0
  125. package/dist/assets/icons/icon/reset-zoom.svg +1 -0
  126. package/dist/assets/icons/icon/rotate-ccw.svg +1 -0
  127. package/dist/assets/icons/icon/rotate-cw.svg +1 -0
  128. package/dist/assets/icons/icon/send-backward.svg +1 -0
  129. package/dist/assets/icons/icon/send-to-back.svg +1 -0
  130. package/dist/assets/icons/icon/share-1.svg +1 -0
  131. package/dist/assets/icons/icon/size-extra-large.svg +1 -0
  132. package/dist/assets/icons/icon/size-large.svg +1 -0
  133. package/dist/assets/icons/icon/size-medium.svg +1 -0
  134. package/dist/assets/icons/icon/size-small.svg +1 -0
  135. package/dist/assets/icons/icon/spline-cubic.svg +1 -0
  136. package/dist/assets/icons/icon/spline-line.svg +1 -0
  137. package/dist/assets/icons/icon/stack-horizontal.svg +1 -0
  138. package/dist/assets/icons/icon/stack-vertical.svg +1 -0
  139. package/dist/assets/icons/icon/status-offline.svg +1 -0
  140. package/dist/assets/icons/icon/stretch-horizontal.svg +1 -0
  141. package/dist/assets/icons/icon/stretch-vertical.svg +1 -0
  142. package/dist/assets/icons/icon/text-align-center.svg +1 -0
  143. package/dist/assets/icons/icon/text-align-left.svg +1 -0
  144. package/dist/assets/icons/icon/text-align-right.svg +1 -0
  145. package/dist/assets/icons/icon/toggle-off.svg +1 -0
  146. package/dist/assets/icons/icon/toggle-on.svg +1 -0
  147. package/dist/assets/icons/icon/tool-arrow.svg +1 -0
  148. package/dist/assets/icons/icon/tool-eraser.svg +1 -0
  149. package/dist/assets/icons/icon/tool-frame.svg +1 -0
  150. package/dist/assets/icons/icon/tool-hand.svg +1 -0
  151. package/dist/assets/icons/icon/tool-highlight.svg +1 -0
  152. package/dist/assets/icons/icon/tool-laser.svg +1 -0
  153. package/dist/assets/icons/icon/tool-line.svg +1 -0
  154. package/dist/assets/icons/icon/tool-media.svg +1 -0
  155. package/dist/assets/icons/icon/tool-note.svg +1 -0
  156. package/dist/assets/icons/icon/tool-pencil.svg +1 -0
  157. package/dist/assets/icons/icon/tool-pointer.svg +1 -0
  158. package/dist/assets/icons/icon/tool-screenshot.svg +1 -0
  159. package/dist/assets/icons/icon/tool-text.svg +1 -0
  160. package/dist/assets/icons/icon/trash.svg +1 -0
  161. package/dist/assets/icons/icon/twitter.svg +1 -0
  162. package/dist/assets/icons/icon/undo.svg +1 -0
  163. package/dist/assets/icons/icon/ungroup.svg +1 -0
  164. package/dist/assets/icons/icon/unlock.svg +1 -0
  165. package/dist/assets/icons/icon/vertical-align-end.svg +1 -0
  166. package/dist/assets/icons/icon/vertical-align-middle.svg +1 -0
  167. package/dist/assets/icons/icon/vertical-align-start.svg +1 -0
  168. package/dist/assets/icons/icon/warning-triangle.svg +1 -0
  169. package/dist/assets/icons/icon/zoom-in.svg +1 -0
  170. package/dist/assets/icons/icon/zoom-out.svg +1 -0
  171. package/dist/assets/translations/ar.json +295 -0
  172. package/dist/assets/translations/ca.json +295 -0
  173. package/dist/assets/translations/cs.json +352 -0
  174. package/dist/assets/translations/da.json +160 -0
  175. package/dist/assets/translations/de.json +310 -0
  176. package/dist/assets/translations/en.json +1 -0
  177. package/dist/assets/translations/es.json +299 -0
  178. package/dist/assets/translations/fa.json +384 -0
  179. package/dist/assets/translations/fi.json +312 -0
  180. package/dist/assets/translations/fr.json +372 -0
  181. package/dist/assets/translations/gl.json +349 -0
  182. package/dist/assets/translations/he.json +86 -0
  183. package/dist/assets/translations/hi-in.json +290 -0
  184. package/dist/assets/translations/hr.json +358 -0
  185. package/dist/assets/translations/hu.json +365 -0
  186. package/dist/assets/translations/id.json +373 -0
  187. package/dist/assets/translations/it.json +330 -0
  188. package/dist/assets/translations/ja.json +351 -0
  189. package/dist/assets/translations/ko-kr.json +396 -0
  190. package/dist/assets/translations/ku.json +93 -0
  191. package/dist/assets/translations/languages.json +150 -0
  192. package/dist/assets/translations/main.json +396 -0
  193. package/dist/assets/translations/my.json +112 -0
  194. package/dist/assets/translations/ne.json +295 -0
  195. package/dist/assets/translations/no.json +62 -0
  196. package/dist/assets/translations/pl.json +113 -0
  197. package/dist/assets/translations/pt-br.json +333 -0
  198. package/dist/assets/translations/pt-pt.json +86 -0
  199. package/dist/assets/translations/ro.json +371 -0
  200. package/dist/assets/translations/ru.json +375 -0
  201. package/dist/assets/translations/sl.json +373 -0
  202. package/dist/assets/translations/sv.json +96 -0
  203. package/dist/assets/translations/te.json +112 -0
  204. package/dist/assets/translations/th.json +295 -0
  205. package/dist/assets/translations/tr.json +355 -0
  206. package/dist/assets/translations/uk.json +375 -0
  207. package/dist/assets/translations/vi.json +295 -0
  208. package/dist/assets/translations/zh-cn.json +386 -0
  209. package/dist/assets/translations/zh-tw.json +340 -0
  210. package/dist/lib/TldrawPlugin.mjs +22 -0
  211. package/dist/lib/TldrawPlugin.mjs.map +1 -0
  212. package/dist/lib/assets/plugin-tldraw-BDrn_mrV.css +24 -0
  213. package/dist/lib/capabilities.mjs +8 -0
  214. package/dist/lib/capabilities.mjs.map +1 -0
  215. package/dist/lib/chunk-TldrawArticle.mjs +60 -0
  216. package/dist/lib/chunk-TldrawArticle.mjs.map +1 -0
  217. package/dist/lib/chunk-TldrawCard.mjs +35 -0
  218. package/dist/lib/chunk-TldrawCard.mjs.map +1 -0
  219. package/dist/lib/chunk-meta.mjs +40 -0
  220. package/dist/lib/chunk-meta.mjs.map +1 -0
  221. package/dist/lib/chunk-settings.mjs +23 -0
  222. package/dist/lib/chunk-settings.mjs.map +1 -0
  223. package/dist/lib/chunk-sketch-variant.mjs +23 -0
  224. package/dist/lib/chunk-sketch-variant.mjs.map +1 -0
  225. package/dist/lib/chunk-types.mjs +73 -0
  226. package/dist/lib/chunk-types.mjs.map +1 -0
  227. package/dist/lib/components.mjs +443 -0
  228. package/dist/lib/components.mjs.map +1 -0
  229. package/dist/lib/containers.mjs +8 -0
  230. package/dist/lib/containers.mjs.map +1 -0
  231. package/dist/lib/hooks.mjs +116 -0
  232. package/dist/lib/hooks.mjs.map +1 -0
  233. package/dist/lib/index.mjs +4 -0
  234. package/dist/lib/meta.mjs +2 -0
  235. package/dist/lib/model.mjs +905 -0
  236. package/dist/lib/model.mjs.map +1 -0
  237. package/dist/lib/plugin.mjs +8 -0
  238. package/dist/lib/plugin.mjs.map +1 -0
  239. package/dist/lib/testing.mjs +246 -0
  240. package/dist/lib/testing.mjs.map +1 -0
  241. package/dist/lib/translations.mjs +10 -0
  242. package/dist/lib/translations.mjs.map +1 -0
  243. package/dist/lib/types.mjs +2 -0
  244. package/dist/types/dx.config.d.ts +28 -0
  245. package/dist/types/dx.config.d.ts.map +1 -0
  246. package/dist/types/src/TldrawPlugin.d.ts +4 -0
  247. package/dist/types/src/TldrawPlugin.d.ts.map +1 -0
  248. package/dist/types/src/capabilities/index.d.ts +4 -0
  249. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  250. package/dist/types/src/capabilities/settings.d.ts +5 -0
  251. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  252. package/dist/types/src/capabilities/sketch-variant.d.ts +5 -0
  253. package/dist/types/src/capabilities/sketch-variant.d.ts.map +1 -0
  254. package/dist/types/src/components/Canvas/Canvas.d.ts +17 -0
  255. package/dist/types/src/components/Canvas/Canvas.d.ts.map +1 -0
  256. package/dist/types/src/components/Canvas/Canvas.stories.d.ts +15 -0
  257. package/dist/types/src/components/Canvas/Canvas.stories.d.ts.map +1 -0
  258. package/dist/types/src/components/Canvas/index.d.ts +4 -0
  259. package/dist/types/src/components/Canvas/index.d.ts.map +1 -0
  260. package/dist/types/src/components/actions.d.ts +6 -0
  261. package/dist/types/src/components/actions.d.ts.map +1 -0
  262. package/dist/types/src/components/custom/CustomGrid.d.ts +12 -0
  263. package/dist/types/src/components/custom/CustomGrid.d.ts.map +1 -0
  264. package/dist/types/src/components/custom/CustomMenu.d.ts +4 -0
  265. package/dist/types/src/components/custom/CustomMenu.d.ts.map +1 -0
  266. package/dist/types/src/components/custom/CustomStylePanel.d.ts +4 -0
  267. package/dist/types/src/components/custom/CustomStylePanel.d.ts.map +1 -0
  268. package/dist/types/src/components/custom/CustomToolbar.d.ts +10 -0
  269. package/dist/types/src/components/custom/CustomToolbar.d.ts.map +1 -0
  270. package/dist/types/src/components/custom/DottedGrid.d.ts +4 -0
  271. package/dist/types/src/components/custom/DottedGrid.d.ts.map +1 -0
  272. package/dist/types/src/components/custom/MeshGrid.d.ts +4 -0
  273. package/dist/types/src/components/custom/MeshGrid.d.ts.map +1 -0
  274. package/dist/types/src/components/custom/index.d.ts +6 -0
  275. package/dist/types/src/components/custom/index.d.ts.map +1 -0
  276. package/dist/types/src/components/index.d.ts +2 -0
  277. package/dist/types/src/components/index.d.ts.map +1 -0
  278. package/dist/types/src/containers/TldrawArticle/TldrawArticle.d.ts +8 -0
  279. package/dist/types/src/containers/TldrawArticle/TldrawArticle.d.ts.map +1 -0
  280. package/dist/types/src/containers/TldrawArticle/index.d.ts +2 -0
  281. package/dist/types/src/containers/TldrawArticle/index.d.ts.map +1 -0
  282. package/dist/types/src/containers/TldrawCard/TldrawCard.d.ts +9 -0
  283. package/dist/types/src/containers/TldrawCard/TldrawCard.d.ts.map +1 -0
  284. package/dist/types/src/containers/TldrawCard/TldrawCard.stories.d.ts +15 -0
  285. package/dist/types/src/containers/TldrawCard/TldrawCard.stories.d.ts.map +1 -0
  286. package/dist/types/src/containers/TldrawCard/index.d.ts +2 -0
  287. package/dist/types/src/containers/TldrawCard/index.d.ts.map +1 -0
  288. package/dist/types/src/containers/index.d.ts +4 -0
  289. package/dist/types/src/containers/index.d.ts.map +1 -0
  290. package/dist/types/src/hooks/adapter.d.ts +15 -0
  291. package/dist/types/src/hooks/adapter.d.ts.map +1 -0
  292. package/dist/types/src/hooks/adapter.test.d.ts +2 -0
  293. package/dist/types/src/hooks/adapter.test.d.ts.map +1 -0
  294. package/dist/types/src/hooks/index.d.ts +3 -0
  295. package/dist/types/src/hooks/index.d.ts.map +1 -0
  296. package/dist/types/src/hooks/useStoreAdapter.d.ts +11 -0
  297. package/dist/types/src/hooks/useStoreAdapter.d.ts.map +1 -0
  298. package/dist/types/src/index.d.ts +4 -0
  299. package/dist/types/src/index.d.ts.map +1 -0
  300. package/dist/types/src/meta.d.ts +33 -0
  301. package/dist/types/src/meta.d.ts.map +1 -0
  302. package/dist/types/src/migrations/index.d.ts +3 -0
  303. package/dist/types/src/migrations/index.d.ts.map +1 -0
  304. package/dist/types/src/migrations/migrate.d.ts +9 -0
  305. package/dist/types/src/migrations/migrate.d.ts.map +1 -0
  306. package/dist/types/src/migrations/schema.d.ts +7 -0
  307. package/dist/types/src/migrations/schema.d.ts.map +1 -0
  308. package/dist/types/src/model/TldrawBuilder.d.ts +110 -0
  309. package/dist/types/src/model/TldrawBuilder.d.ts.map +1 -0
  310. package/dist/types/src/model/TldrawBuilder.test.d.ts +2 -0
  311. package/dist/types/src/model/TldrawBuilder.test.d.ts.map +1 -0
  312. package/dist/types/src/model/apply.d.ts +5 -0
  313. package/dist/types/src/model/apply.d.ts.map +1 -0
  314. package/dist/types/src/model/builder.d.ts +3 -0
  315. package/dist/types/src/model/builder.d.ts.map +1 -0
  316. package/dist/types/src/model/index.d.ts +6 -0
  317. package/dist/types/src/model/index.d.ts.map +1 -0
  318. package/dist/types/src/model/read.d.ts +8 -0
  319. package/dist/types/src/model/read.d.ts.map +1 -0
  320. package/dist/types/src/model/render.d.ts +27 -0
  321. package/dist/types/src/model/render.d.ts.map +1 -0
  322. package/dist/types/src/model/scene.test.d.ts +2 -0
  323. package/dist/types/src/model/scene.test.d.ts.map +1 -0
  324. package/dist/types/src/plugin.d.ts +3 -0
  325. package/dist/types/src/plugin.d.ts.map +1 -0
  326. package/dist/types/src/testing/data.d.ts +242 -0
  327. package/dist/types/src/testing/data.d.ts.map +1 -0
  328. package/dist/types/src/testing/index.d.ts +2 -0
  329. package/dist/types/src/testing/index.d.ts.map +1 -0
  330. package/dist/types/src/testing/schema.d.ts +7 -0
  331. package/dist/types/src/testing/schema.d.ts.map +1 -0
  332. package/dist/types/src/translations.d.ts +9 -0
  333. package/dist/types/src/translations.d.ts.map +1 -0
  334. package/dist/types/src/types/Settings.d.ts +10 -0
  335. package/dist/types/src/types/Settings.d.ts.map +1 -0
  336. package/dist/types/src/types/Tldraw.d.ts +25 -0
  337. package/dist/types/src/types/Tldraw.d.ts.map +1 -0
  338. package/dist/types/src/types/TldrawCapabilities.d.ts +4 -0
  339. package/dist/types/src/types/TldrawCapabilities.d.ts.map +1 -0
  340. package/dist/types/src/types/index.d.ts +4 -0
  341. package/dist/types/src/types/index.d.ts.map +1 -0
  342. package/dist/types/src/types/types.d.ts +6 -0
  343. package/dist/types/src/types/types.d.ts.map +1 -0
  344. package/dist/types/src/variant.test.d.ts +2 -0
  345. package/dist/types/src/variant.test.d.ts.map +1 -0
  346. package/dist/types/tsconfig.tsbuildinfo +1 -0
  347. package/dx.config.ts +35 -0
  348. package/package.json +157 -0
  349. package/src/TldrawPlugin.tsx +31 -0
  350. package/src/capabilities/index.ts +8 -0
  351. package/src/capabilities/settings.ts +31 -0
  352. package/src/capabilities/sketch-variant.ts +29 -0
  353. package/src/components/Canvas/Canvas.stories.tsx +101 -0
  354. package/src/components/Canvas/Canvas.tsx +284 -0
  355. package/src/components/Canvas/index.ts +9 -0
  356. package/src/components/Canvas/theme.css +23 -0
  357. package/src/components/actions.ts +42 -0
  358. package/src/components/custom/CustomGrid.tsx +61 -0
  359. package/src/components/custom/CustomMenu.tsx +31 -0
  360. package/src/components/custom/CustomStylePanel.tsx +38 -0
  361. package/src/components/custom/CustomToolbar.tsx +88 -0
  362. package/src/components/custom/DottedGrid.tsx +53 -0
  363. package/src/components/custom/MeshGrid.tsx +47 -0
  364. package/src/components/custom/index.ts +9 -0
  365. package/src/components/index.ts +5 -0
  366. package/src/containers/TldrawArticle/TldrawArticle.tsx +78 -0
  367. package/src/containers/TldrawArticle/index.ts +5 -0
  368. package/src/containers/TldrawCard/TldrawCard.stories.tsx +53 -0
  369. package/src/containers/TldrawCard/TldrawCard.tsx +32 -0
  370. package/src/containers/TldrawCard/index.ts +5 -0
  371. package/src/containers/index.ts +8 -0
  372. package/src/hooks/adapter.test.ts +20 -0
  373. package/src/hooks/adapter.ts +83 -0
  374. package/src/hooks/index.ts +6 -0
  375. package/src/hooks/useStoreAdapter.ts +55 -0
  376. package/src/index.ts +7 -0
  377. package/src/meta.ts +9 -0
  378. package/src/migrations/index.ts +6 -0
  379. package/src/migrations/migrate.ts +49 -0
  380. package/src/migrations/schema.ts +98 -0
  381. package/src/model/TldrawBuilder.test.ts +72 -0
  382. package/src/model/TldrawBuilder.ts +443 -0
  383. package/src/model/apply.ts +132 -0
  384. package/src/model/builder.ts +31 -0
  385. package/src/model/index.ts +9 -0
  386. package/src/model/read.ts +203 -0
  387. package/src/model/render.ts +191 -0
  388. package/src/model/scene.test.ts +222 -0
  389. package/src/plugin.ts +9 -0
  390. package/src/testing/data.ts +238 -0
  391. package/src/testing/index.ts +5 -0
  392. package/src/testing/schema.ts +98 -0
  393. package/src/translations.ts +18 -0
  394. package/src/types/Settings.ts +32 -0
  395. package/src/types/Tldraw.ts +38 -0
  396. package/src/types/TldrawCapabilities.ts +17 -0
  397. package/src/types/index.ts +8 -0
  398. package/src/types/types.ts +11 -0
  399. package/src/typings.d.ts +8 -0
  400. package/src/variant.test.ts +142 -0
  401. package/src/vite-env.d.ts +10 -0
@@ -0,0 +1,203 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ //
6
+ // Derives the neutral scene from tldraw records. Geometry is read from the live records —
7
+ // not from a stored copy — so the agent's mental model stays coherent after users drag or
8
+ // resize shapes in the UI. An object's `origin` is the top-left of its bounding box.
9
+ //
10
+
11
+ import { type ReadResult, type ReadWorldObject, type Scene } from '@dxos/plugin-illustrator/model';
12
+
13
+ import { type ExternalBox } from './render';
14
+ import { type CanvasContent } from './TldrawBuilder';
15
+
16
+ type ShapeRecord = Record<string, any>;
17
+
18
+ /** Read the derived scene from canvas content. */
19
+ export const readScene = (content: CanvasContent): ReadResult => {
20
+ const groups = new Map<string, ShapeRecord[]>();
21
+ let unmanaged = 0;
22
+ for (const record of Object.values(content ?? {})) {
23
+ if (record?.typeName !== 'shape') {
24
+ continue;
25
+ }
26
+ const objectId = record.meta?.object;
27
+ if (typeof objectId !== 'string' || typeof record.meta?.element !== 'string') {
28
+ unmanaged++;
29
+ continue;
30
+ }
31
+ groups.set(objectId, [...(groups.get(objectId) ?? []), record]);
32
+ }
33
+
34
+ const objects = [...groups.entries()].map(([objectId, records]) => readObject(objectId, records, content));
35
+ return { scene: { objects }, unmanaged };
36
+ };
37
+
38
+ /** Canvas-px bounding boxes of all managed elements, keyed by `objectId/elementId`. */
39
+ export const elementBoxes = (content: CanvasContent): Record<string, ExternalBox> => {
40
+ const boxes: Record<string, ExternalBox> = {};
41
+ for (const record of Object.values(content ?? {})) {
42
+ if (record?.typeName === 'shape' && record.meta?.object && record.meta?.element) {
43
+ boxes[`${record.meta.object}/${record.meta.element}`] = bounds(record);
44
+ }
45
+ }
46
+ return boxes;
47
+ };
48
+
49
+ const readObject = (objectId: string, records: ShapeRecord[], content: CanvasContent): ReadWorldObject => {
50
+ const scale = records.find((record) => typeof record.meta?.scale === 'number')?.meta.scale ?? 1;
51
+ const origin = {
52
+ x: Math.min(...records.map((record) => bounds(record).x)),
53
+ y: Math.min(...records.map((record) => bounds(record).y)),
54
+ };
55
+ const local = (abs: { x: number; y: number }): Scene.Point => ({
56
+ x: round((abs.x - origin.x) / scale),
57
+ y: round((abs.y - origin.y) / scale),
58
+ });
59
+ const length = (px: number) => round(px / scale);
60
+
61
+ const elements = records
62
+ .map((record) => readElement(record, { local, length, objectId, content }))
63
+ .filter((element): element is Scene.Element => element !== undefined);
64
+
65
+ return { id: objectId, origin, scale, elements };
66
+ };
67
+
68
+ type ReadContext = {
69
+ local: (abs: { x: number; y: number }) => Scene.Point;
70
+ length: (px: number) => number;
71
+ objectId: string;
72
+ content: CanvasContent;
73
+ };
74
+
75
+ const readElement = (record: ShapeRecord, ctx: ReadContext): Scene.Element | undefined => {
76
+ const id = record.meta.element;
77
+ const kind = record.meta.kind;
78
+ switch (record.type) {
79
+ case 'geo': {
80
+ const boxKind = kind === 'rect' || kind === 'diamond' || kind === 'triangle' ? kind : 'ellipse';
81
+ return {
82
+ kind: boxKind,
83
+ id,
84
+ ...ctx.local({ x: record.x, y: record.y }),
85
+ w: ctx.length(record.props.w),
86
+ h: ctx.length(record.props.h),
87
+ ...(record.rotation ? { rotation: round((record.rotation * 180) / Math.PI) } : {}),
88
+ ...(record.props.text ? { text: record.props.text } : {}),
89
+ ...readStyle(record),
90
+ };
91
+ }
92
+ case 'text': {
93
+ return {
94
+ kind: 'text',
95
+ id,
96
+ ...ctx.local({ x: record.x, y: record.y }),
97
+ ...(record.props.autoSize ? {} : { w: ctx.length(record.props.w) }),
98
+ text: record.props.text ?? '',
99
+ ...readStyle(record),
100
+ };
101
+ }
102
+ case 'line': {
103
+ const points = linePoints(record).map((point) => ctx.local({ x: record.x + point.x, y: record.y + point.y }));
104
+ if (kind === 'curve') {
105
+ return { kind: 'curve', id, points, ...readStyle(record) };
106
+ }
107
+ const closed =
108
+ points.length > 2 && points[0].x === points[points.length - 1].x && points[0].y === points[points.length - 1].y;
109
+ return {
110
+ kind: 'line',
111
+ id,
112
+ points: closed ? points.slice(0, -1) : points,
113
+ ...(closed ? { closed: true } : {}),
114
+ ...readStyle(record),
115
+ };
116
+ }
117
+ case 'arrow': {
118
+ const { from, to } = arrowRefs(record, ctx);
119
+ return {
120
+ kind: 'arrow',
121
+ id,
122
+ ...(from
123
+ ? { from }
124
+ : { start: ctx.local({ x: record.x + record.props.start.x, y: record.y + record.props.start.y }) }),
125
+ ...(to ? { to } : { end: ctx.local({ x: record.x + record.props.end.x, y: record.y + record.props.end.y }) }),
126
+ ...(record.props.text ? { text: record.props.text } : {}),
127
+ ...readStyle(record),
128
+ };
129
+ }
130
+ default:
131
+ return undefined;
132
+ }
133
+ };
134
+
135
+ /** Resolve arrow terminal bindings back to element refs (same-object refs are shortened). */
136
+ const arrowRefs = (record: ShapeRecord, ctx: ReadContext): { from?: string; to?: string } => {
137
+ const refs: { from?: string; to?: string } = {};
138
+ for (const binding of Object.values(ctx.content ?? {})) {
139
+ if (binding?.typeName !== 'binding' || binding.fromId !== record.id) {
140
+ continue;
141
+ }
142
+ const target = ctx.content[binding.toId];
143
+ if (!target?.meta?.object || !target?.meta?.element) {
144
+ continue;
145
+ }
146
+ const ref =
147
+ target.meta.object === ctx.objectId ? target.meta.element : `${target.meta.object}/${target.meta.element}`;
148
+ if (binding.props?.terminal === 'start') {
149
+ refs.from = ref;
150
+ } else {
151
+ refs.to = ref;
152
+ }
153
+ }
154
+ return refs;
155
+ };
156
+
157
+ /** Report only non-default styles to keep the read output compact. */
158
+ const readStyle = (record: ShapeRecord): Partial<Pick<Scene.Box, 'color' | 'fill' | 'stroke' | 'weight'>> => {
159
+ const { color, fill, dash, size } = record.props;
160
+ return {
161
+ ...(color && color !== 'black' ? { color } : {}),
162
+ ...(fill && fill !== 'none' ? { fill: fill === 'pattern' ? ('pattern' as const) : ('solid' as const) } : {}),
163
+ ...(dash && dash !== 'draw' ? { stroke: dash as Scene.Stroke } : {}),
164
+ ...(size && size !== 'm' ? { weight: size as Scene.Weight } : {}),
165
+ };
166
+ };
167
+
168
+ const linePoints = (record: ShapeRecord): { x: number; y: number }[] =>
169
+ Object.values(record.props.points ?? {})
170
+ // Indexes are `a<n>` fractional keys; numeric compare so `a10` sorts after `a2`.
171
+ .sort((a: any, b: any) => Number(String(a.index).slice(1)) - Number(String(b.index).slice(1)))
172
+ .map((point: any) => ({ x: point.x, y: point.y }));
173
+
174
+ /** Bounding box of a shape record in canvas px. */
175
+ const bounds = (record: ShapeRecord): ExternalBox => {
176
+ switch (record.type) {
177
+ case 'geo':
178
+ return { x: record.x, y: record.y, w: record.props.w, h: record.props.h };
179
+ case 'text':
180
+ return { x: record.x, y: record.y, w: record.props.w ?? 0, h: 0 };
181
+ case 'line': {
182
+ const points = linePoints(record);
183
+ const xs = points.map((point) => record.x + point.x);
184
+ const ys = points.map((point) => record.y + point.y);
185
+ return boxFrom(xs, ys);
186
+ }
187
+ case 'arrow': {
188
+ const xs = [record.x + record.props.start.x, record.x + record.props.end.x];
189
+ const ys = [record.y + record.props.start.y, record.y + record.props.end.y];
190
+ return boxFrom(xs, ys);
191
+ }
192
+ default:
193
+ return { x: record.x ?? 0, y: record.y ?? 0, w: record.props?.w ?? 0, h: record.props?.h ?? 0 };
194
+ }
195
+ };
196
+
197
+ const boxFrom = (xs: number[], ys: number[]): ExternalBox => {
198
+ const x = Math.min(...xs);
199
+ const y = Math.min(...ys);
200
+ return { x, y, w: Math.max(...xs) - x, h: Math.max(...ys) - y };
201
+ };
202
+
203
+ const round = (value: number) => Math.round(value * 100) / 100;
@@ -0,0 +1,191 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ //
6
+ // tldraw backend for the scene DSL: compiles a `WorldObject` into tldraw records.
7
+ // Element identity is stamped into each record's `meta` (`{ object, element, kind, scale }`)
8
+ // so `read.ts` can reconstruct the scene and edits can address records by id.
9
+ //
10
+
11
+ import { Scene } from '@dxos/plugin-illustrator/model';
12
+
13
+ import {
14
+ type CanvasContent,
15
+ type ConnectorProps,
16
+ type Dash,
17
+ type Fill,
18
+ type PathProps,
19
+ type Point,
20
+ type ShapeProps,
21
+ TldrawBuilder,
22
+ } from './TldrawBuilder';
23
+
24
+ export const DEFAULT_SCALE = 1;
25
+
26
+ /** Canvas-px bounding box of an existing element another object's arrow may bind to. */
27
+ export type ExternalBox = { x: number; y: number; w: number; h: number };
28
+
29
+ export type RenderOptions = {
30
+ origin: Scene.Point;
31
+ scale: number;
32
+ /** Fractional-index seed past the existing canvas content. */
33
+ indexStart: number;
34
+ /** Bounding boxes of existing elements, keyed by `objectId/elementId` (canvas px). */
35
+ external?: Record<string, ExternalBox>;
36
+ dialect?: string;
37
+ };
38
+
39
+ /** Stable record id for an element: `shape:<objectId>/<elementId>`. */
40
+ export const shapeId = (objectId: string, elementId: string) => `shape:${objectId}/${elementId}`;
41
+
42
+ const strokeToDash: Record<Scene.Stroke, Dash> = {
43
+ sketchy: 'draw',
44
+ solid: 'solid',
45
+ dashed: 'dashed',
46
+ dotted: 'dotted',
47
+ };
48
+
49
+ const fillToFill: Record<Scene.Fill, Fill> = {
50
+ none: 'none',
51
+ solid: 'solid',
52
+ pattern: 'pattern',
53
+ };
54
+
55
+ const boxKindToGeo = {
56
+ rect: 'rectangle',
57
+ ellipse: 'ellipse',
58
+ diamond: 'diamond',
59
+ triangle: 'triangle',
60
+ } as const;
61
+
62
+ const style = (element: Scene.Element): Pick<ShapeProps, 'color' | 'fill' | 'dash' | 'size'> => ({
63
+ color: element.color,
64
+ fill: 'fill' in element && element.fill ? fillToFill[element.fill] : undefined,
65
+ dash: element.stroke ? strokeToDash[element.stroke] : undefined,
66
+ size: element.weight,
67
+ });
68
+
69
+ /**
70
+ * Compile one world object into tldraw records (canvas-px coordinates).
71
+ * Local coordinates are mapped via `canvas = origin + local * scale`.
72
+ */
73
+ export const renderObject = (object: Scene.WorldObject, options: RenderOptions): CanvasContent => {
74
+ const { origin, scale, indexStart, external = {}, dialect = 'scene' } = options;
75
+ const point = (local: Scene.Point | { x: number; y: number }): Point => ({
76
+ x: origin.x + local.x * scale,
77
+ y: origin.y + local.y * scale,
78
+ });
79
+ const length = (units: number) => units * scale;
80
+
81
+ const kinds = new Map<string, string>();
82
+ const builder = new TldrawBuilder({
83
+ indexStart,
84
+ metaFor: (handle) => {
85
+ const [objectId, elementId] = splitHandle(handle);
86
+ return { object: objectId, element: elementId, kind: kinds.get(handle), scale, dialect };
87
+ },
88
+ });
89
+
90
+ // Register existing canvas elements so arrows can bind across objects.
91
+ for (const [handle, box] of Object.entries(external)) {
92
+ builder.external(handle, box);
93
+ }
94
+
95
+ // Refs are element ids within this object, or `objectId/elementId` across objects.
96
+ const ref = (value: string) => (value.includes('/') ? value : `${object.id}/${value}`);
97
+
98
+ for (const element of object.elements) {
99
+ const handle = `${object.id}/${element.id}`;
100
+ kinds.set(handle, element.kind);
101
+ switch (element.kind) {
102
+ case 'rect':
103
+ case 'ellipse':
104
+ case 'diamond':
105
+ case 'triangle': {
106
+ builder.geo(boxKindToGeo[element.kind], {
107
+ id: handle,
108
+ ...point(element),
109
+ w: length(element.w),
110
+ h: length(element.h),
111
+ rotation: element.rotation !== undefined ? (element.rotation * Math.PI) / 180 : undefined,
112
+ text: element.text,
113
+ ...style(element),
114
+ });
115
+ break;
116
+ }
117
+ case 'circle': {
118
+ kinds.set(handle, 'ellipse');
119
+ builder.circle({
120
+ id: handle,
121
+ ...point({ x: element.cx - element.r, y: element.cy - element.r }),
122
+ w: length(element.r * 2),
123
+ text: element.text,
124
+ ...style(element),
125
+ });
126
+ break;
127
+ }
128
+ case 'line': {
129
+ const props: PathProps = {
130
+ id: handle,
131
+ points: element.points.map(point),
132
+ closed: element.closed,
133
+ ...style(element),
134
+ };
135
+ builder.path(props);
136
+ break;
137
+ }
138
+ case 'curve': {
139
+ builder.path({ id: handle, points: element.points.map(point), smooth: true, ...style(element) });
140
+ break;
141
+ }
142
+ case 'arc': {
143
+ kinds.set(handle, 'curve');
144
+ builder.path({ id: handle, points: sampleArc(element).map(point), smooth: true, ...style(element) });
145
+ break;
146
+ }
147
+ case 'text': {
148
+ builder.text({
149
+ id: handle,
150
+ ...point(element),
151
+ w: element.w !== undefined ? length(element.w) : undefined,
152
+ text: element.text,
153
+ color: element.color,
154
+ size: element.weight,
155
+ });
156
+ break;
157
+ }
158
+ case 'arrow': {
159
+ const props: ConnectorProps = {
160
+ id: handle,
161
+ from: element.from ? ref(element.from) : undefined,
162
+ to: element.to ? ref(element.to) : undefined,
163
+ start: element.start ? point(element.start) : undefined,
164
+ end: element.end ? point(element.end) : undefined,
165
+ text: element.text,
166
+ ...style(element),
167
+ };
168
+ builder.arrow(props);
169
+ break;
170
+ }
171
+ }
172
+ }
173
+
174
+ return builder.records();
175
+ };
176
+
177
+ /** `objectId/elementId` — element ids may themselves not contain `/`. */
178
+ const splitHandle = (handle: string): [string, string] => {
179
+ const index = handle.indexOf('/');
180
+ return [handle.slice(0, index), handle.slice(index + 1)];
181
+ };
182
+
183
+ /** Sample an arc (degrees, clockwise from +x, y-down screen coords) into spline points. */
184
+ const sampleArc = ({ cx, cy, r, startAngle, endAngle }: Scene.Arc): Scene.Point[] => {
185
+ const sweep = endAngle - startAngle;
186
+ const steps = Math.max(4, Math.ceil(Math.abs(sweep) / 22.5));
187
+ return Array.from({ length: steps + 1 }, (_, i) => {
188
+ const angle = ((startAngle + (sweep * i) / steps) * Math.PI) / 180;
189
+ return { x: cx + r * Math.cos(angle), y: cy + r * Math.sin(angle) };
190
+ });
191
+ };
@@ -0,0 +1,222 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { createTLSchema } from '@tldraw/tlschema';
6
+ import { describe, test } from 'vitest';
7
+
8
+ import { type Scene } from '@dxos/plugin-illustrator/model';
9
+
10
+ import { applyCommands } from './apply';
11
+ import { readScene } from './read';
12
+
13
+ /** A face made of every element category: boxes, circles, arc sugar, polyline, text. */
14
+ const face: Scene.WorldObject = {
15
+ id: 'face',
16
+ origin: { x: 100, y: 100 },
17
+ scale: 2,
18
+ elements: [
19
+ { kind: 'circle', id: 'head', cx: 50, cy: 50, r: 50, color: 'yellow', fill: 'solid' },
20
+ { kind: 'circle', id: 'left-eye', cx: 30, cy: 35, r: 6, fill: 'solid' },
21
+ { kind: 'circle', id: 'right-eye', cx: 70, cy: 35, r: 6, fill: 'solid' },
22
+ {
23
+ kind: 'line',
24
+ id: 'nose',
25
+ points: [
26
+ { x: 50, y: 45 },
27
+ { x: 50, y: 60 },
28
+ ],
29
+ },
30
+ { kind: 'arc', id: 'smile', cx: 50, cy: 55, r: 25, startAngle: 30, endAngle: 150, color: 'red' },
31
+ ],
32
+ };
33
+
34
+ const hat: Scene.WorldObject = {
35
+ id: 'hat',
36
+ origin: { x: 120, y: 40 },
37
+ scale: 2,
38
+ elements: [
39
+ { kind: 'rect', id: 'brim', x: 0, y: 25, w: 80, h: 6, color: 'blue', fill: 'solid' },
40
+ { kind: 'rect', id: 'crown', x: 20, y: 0, w: 40, h: 25, color: 'blue', fill: 'solid' },
41
+ ],
42
+ };
43
+
44
+ const upsert = (object: Scene.WorldObject): Scene.Command => ({ op: 'upsert-object', object });
45
+
46
+ describe('scene DSL', () => {
47
+ test('renders objects and reads back a coherent scene', ({ expect }) => {
48
+ const content = {};
49
+ const result = applyCommands(content, [upsert(face)]);
50
+ expect(result.upserted).to.deep.eq(['face']);
51
+
52
+ const { scene, unmanaged } = readScene(content);
53
+ expect(unmanaged).to.eq(0);
54
+ expect(scene.objects).to.have.length(1);
55
+
56
+ const read = scene.objects[0];
57
+ expect(read.id).to.eq('face');
58
+ // Origin is derived from the bounding box: head top-left = origin + (0,0) * scale.
59
+ expect(read.origin).to.deep.eq({ x: 100, y: 100 });
60
+ expect(read.scale).to.eq(2);
61
+ expect(read.elements.map((element) => element.id)).to.deep.eq(['head', 'left-eye', 'right-eye', 'nose', 'smile']);
62
+
63
+ // Circle sugar reads back as an ellipse in local units.
64
+ const head = read.elements.find((element) => element.id === 'head');
65
+ expect(head).to.include({ kind: 'ellipse', x: 0, y: 0, w: 100, h: 100 });
66
+ // Arc sugar reads back as a curve.
67
+ const smile = read.elements.find((element) => element.id === 'smile');
68
+ expect(smile?.kind).to.eq('curve');
69
+ });
70
+
71
+ test('rendered records pass tldraw schema validation', ({ expect }) => {
72
+ const content = {};
73
+ applyCommands(content, [
74
+ upsert(face),
75
+ upsert(hat),
76
+ upsert({
77
+ id: 'labels',
78
+ origin: { x: 0, y: 400 },
79
+ elements: [
80
+ { kind: 'text', id: 'caption', x: 0, y: 0, text: 'portrait' },
81
+ { kind: 'arrow', id: 'points-at', from: 'caption', to: 'face/head', text: 'head' },
82
+ {
83
+ kind: 'line',
84
+ id: 'zigzag',
85
+ points: [
86
+ { x: 0, y: 20 },
87
+ { x: 10, y: 30 },
88
+ { x: 20, y: 20 },
89
+ ],
90
+ closed: true,
91
+ },
92
+ { kind: 'diamond', id: 'gem', x: 40, y: 20, w: 20, h: 20, rotation: 45, stroke: 'dashed', weight: 'l' },
93
+ ],
94
+ }),
95
+ ]);
96
+
97
+ const schema = createTLSchema();
98
+ // tldraw types its record map as a fixed-key object without an index signature;
99
+ // validating an arbitrary record requires a string-keyed view.
100
+ const validators = schema.types as Record<string, { validate: (record: unknown) => unknown }>;
101
+ for (const record of Object.values(content) as any[]) {
102
+ expect(() => validators[record.typeName].validate(record), record.id).to.not.throw();
103
+ }
104
+ });
105
+
106
+ test('cross-object arrows bind to their targets', ({ expect }) => {
107
+ const content: Record<string, any> = {};
108
+ applyCommands(content, [
109
+ upsert(face),
110
+ upsert({
111
+ id: 'hat',
112
+ origin: { x: 120, y: 40 },
113
+ elements: [...hat.elements, { kind: 'arrow', id: 'sits-on', from: 'brim', to: 'face/head' }],
114
+ }),
115
+ ]);
116
+
117
+ const bindings = Object.values(content).filter((record) => record.typeName === 'binding');
118
+ expect(bindings.map((binding) => binding.toId).sort()).to.deep.eq(['shape:face/head', 'shape:hat/brim']);
119
+
120
+ const { scene } = readScene(content);
121
+ const arrow = scene.objects
122
+ .find((object) => object.id === 'hat')
123
+ ?.elements.find((element) => element.id === 'sits-on');
124
+ expect(arrow).to.include({ kind: 'arrow', from: 'brim', to: 'face/head' });
125
+ });
126
+
127
+ test('upsert-elements replaces by id and keeps the rest', ({ expect }) => {
128
+ const content = {};
129
+ applyCommands(content, [upsert(face)]);
130
+ const before = readScene(content).scene.objects[0];
131
+
132
+ // Make the smile a frown (upper arc), leave everything else untouched.
133
+ applyCommands(content, [
134
+ {
135
+ op: 'upsert-elements',
136
+ objectId: 'face',
137
+ elements: [{ kind: 'arc', id: 'smile', cx: 50, cy: 70, r: 20, startAngle: 210, endAngle: 330 }],
138
+ },
139
+ ]);
140
+
141
+ const after = readScene(content).scene.objects[0];
142
+ expect(after.elements).to.have.length(before.elements.length);
143
+ expect(after.elements.find((element) => element.id === 'head')).to.deep.eq(
144
+ before.elements.find((element) => element.id === 'head'),
145
+ );
146
+ expect(after.elements.find((element) => element.id === 'smile')).to.not.deep.eq(
147
+ before.elements.find((element) => element.id === 'smile'),
148
+ );
149
+ });
150
+
151
+ test('remove and move commands', ({ expect }) => {
152
+ const content: Record<string, any> = {};
153
+ applyCommands(content, [upsert(face), upsert(hat)]);
154
+
155
+ const moved = applyCommands(content, [{ op: 'move-object', objectId: 'hat', origin: { x: 220, y: 140 } }]);
156
+ expect(moved.upserted).to.deep.eq(['hat']);
157
+ const hatRead = readScene(content).scene.objects.find((object) => object.id === 'hat');
158
+ expect(hatRead?.origin).to.deep.eq({ x: 220, y: 140 });
159
+ // Local geometry is unchanged by a move.
160
+ expect(hatRead?.elements.find((element) => element.id === 'brim')).to.include({ x: 0, y: 25, w: 80, h: 6 });
161
+
162
+ const removedElements = applyCommands(content, [
163
+ { op: 'remove-elements', objectId: 'face', elementIds: ['left-eye', 'right-eye'] },
164
+ ]);
165
+ expect(removedElements.removed).to.eq(2);
166
+
167
+ const removedObject = applyCommands(content, [{ op: 'remove-object', objectId: 'hat' }]);
168
+ expect(removedObject.removed).to.eq(2);
169
+ expect(readScene(content).scene.objects.map((object) => object.id)).to.deep.eq(['face']);
170
+ });
171
+
172
+ test('removing an arrow target also drops its bindings', ({ expect }) => {
173
+ const content: Record<string, any> = {};
174
+ applyCommands(content, [
175
+ upsert({
176
+ id: 'graph',
177
+ origin: { x: 0, y: 0 },
178
+ elements: [
179
+ { kind: 'rect', id: 'a', x: 0, y: 0, w: 40, h: 20 },
180
+ { kind: 'rect', id: 'b', x: 100, y: 0, w: 40, h: 20 },
181
+ { kind: 'arrow', id: 'a-to-b', from: 'a', to: 'b' },
182
+ ],
183
+ }),
184
+ ]);
185
+ expect(Object.values(content).filter((record) => record.typeName === 'binding')).to.have.length(2);
186
+
187
+ applyCommands(content, [{ op: 'remove-elements', objectId: 'graph', elementIds: ['b'] }]);
188
+ const bindings = Object.values(content).filter((record) => record.typeName === 'binding');
189
+ // Only the binding to the surviving shape remains; nothing dangles.
190
+ expect(bindings).to.have.length(1);
191
+ expect(bindings[0].toId).to.eq('shape:graph/a');
192
+ });
193
+
194
+ test('unmanaged shapes are counted but not read as scene objects', ({ expect }) => {
195
+ const content: Record<string, any> = {};
196
+ applyCommands(content, [upsert(hat)]);
197
+ content['shape:user'] = {
198
+ id: 'shape:user',
199
+ typeName: 'shape',
200
+ type: 'geo',
201
+ x: 0,
202
+ y: 0,
203
+ rotation: 0,
204
+ index: 'a99',
205
+ parentId: 'page:page',
206
+ isLocked: false,
207
+ opacity: 1,
208
+ meta: {},
209
+ props: { geo: 'rectangle', w: 10, h: 10 },
210
+ };
211
+
212
+ const { scene, unmanaged } = readScene(content);
213
+ expect(unmanaged).to.eq(1);
214
+ expect(scene.objects.map((object) => object.id)).to.deep.eq(['hat']);
215
+ });
216
+
217
+ test('upsert-elements on an unknown object fails with guidance', ({ expect }) => {
218
+ expect(() => applyCommands({}, [{ op: 'upsert-elements', objectId: 'ghost', elements: [] }])).to.throw(
219
+ /unknown object/,
220
+ );
221
+ });
222
+ });
package/src/plugin.ts ADDED
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+
7
+ import { meta } from './meta';
8
+
9
+ export const TldrawPlugin = Plugin.lazy(meta, () => import('#plugin'));