@panphora/clayjs 1.16.0 → 1.18.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 (511) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +72519 -59839
  11. package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
  12. package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
  13. package/dist/m/c-2GRUM77Q.js +30 -0
  14. package/dist/m/c-2KIEGNC4.js +1 -0
  15. package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
  16. package/dist/m/c-2YYDMLNZ.js +1 -0
  17. package/dist/m/c-2ZZBC45O.js +1 -0
  18. package/dist/m/c-372N6H3B.js +1 -0
  19. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  20. package/dist/m/c-3LDGIBMN.js +1 -0
  21. package/dist/m/c-3O7OBDCM.js +1 -0
  22. package/dist/m/c-4ASQPRQL.js +1 -0
  23. package/dist/m/c-4TXNLX4K.js +6 -0
  24. package/dist/m/c-4YU3BR4D.js +6 -0
  25. package/dist/m/c-5734MF2P.js +1 -0
  26. package/dist/m/c-57XDY6HC.js +1 -0
  27. package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
  28. package/dist/m/c-5ONTYIUI.js +1 -0
  29. package/dist/m/c-5OOVFCJM.js +1 -0
  30. package/dist/m/c-5T6NZHNM.js +1 -0
  31. package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
  32. package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
  33. package/dist/m/c-6DNRCPNM.js +1 -0
  34. package/dist/m/c-6UGWFMQC.js +2 -0
  35. package/dist/m/c-7K4SCQB7.js +1 -0
  36. package/dist/m/c-7MG5RU63.js +1 -0
  37. package/dist/m/c-7PRJCA3E.js +1 -0
  38. package/dist/m/c-A6L3FMUS.js +1 -0
  39. package/dist/m/c-AMU3BPPR.js +4 -0
  40. package/dist/m/c-AMYEFA2E.js +1 -0
  41. package/dist/m/c-AQYCG7QW.js +1 -0
  42. package/dist/m/c-ATKAOH23.js +1 -0
  43. package/dist/m/c-B3JM5ZXC.js +1 -0
  44. package/dist/m/c-B5CS55WR.js +1 -0
  45. package/dist/m/c-B5UJQT7W.js +1 -0
  46. package/dist/m/c-B7J4K2AE.js +3 -0
  47. package/dist/m/c-BHZYSHW4.js +1 -0
  48. package/dist/m/c-BIJALSRD.js +1 -0
  49. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  50. package/dist/m/c-BPF2NSPV.js +1 -0
  51. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  52. package/dist/m/c-BUAYFEV5.js +1 -0
  53. package/dist/m/c-C6IPRG34.js +1 -0
  54. package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
  55. package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
  56. package/dist/m/c-CJFDLD43.js +1 -0
  57. package/dist/m/c-COPTL5WB.js +1 -0
  58. package/dist/m/c-D3E4WJ4M.js +166 -0
  59. package/dist/m/c-D5IKEKXW.js +7 -0
  60. package/dist/m/c-DHBRA6DL.js +1 -0
  61. package/dist/m/c-DKLS74XB.js +72 -0
  62. package/dist/m/c-DVYGFTSK.js +3 -0
  63. package/dist/m/c-E3ZXARWA.js +1 -0
  64. package/dist/m/c-EK63G3CG.js +1 -0
  65. package/dist/m/c-ERQSNR6K.js +13 -0
  66. package/dist/m/c-EVYAISP3.js +1 -0
  67. package/dist/m/c-EXKHPOVC.js +3 -0
  68. package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
  69. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  70. package/dist/m/c-GNDL3TKB.js +1 -0
  71. package/dist/m/c-GRWMD6O5.js +1 -0
  72. package/dist/m/c-GRXHHFPO.js +1 -0
  73. package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
  74. package/dist/m/c-HAWFNBYG.js +1 -0
  75. package/dist/m/c-HG2C77BM.js +4 -0
  76. package/dist/m/c-HHGJBU4D.js +1 -0
  77. package/dist/m/c-HMV6P4RX.js +1 -0
  78. package/dist/m/c-HMYWIHFG.js +1 -0
  79. package/dist/m/c-HT2DKBV2.js +1 -0
  80. package/dist/m/c-I2SCCUEW.js +1 -0
  81. package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
  82. package/dist/m/c-IMZCWYHR.js +7 -0
  83. package/dist/m/c-IVMD3FEM.js +1 -0
  84. package/dist/m/c-IVTRVENZ.js +1 -0
  85. package/dist/m/c-IW6GGOU2.js +1 -0
  86. package/dist/m/c-JKZJIUIP.js +1 -0
  87. package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
  88. package/dist/m/c-JRKY44DK.js +1 -0
  89. package/dist/m/c-K3ADYNLU.js +1 -0
  90. package/dist/m/c-KBS3UV6B.js +1 -0
  91. package/dist/m/c-KGHVFRR3.js +1 -0
  92. package/dist/m/c-KJJPV77R.js +1 -0
  93. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  94. package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
  95. package/dist/m/c-KQ7E3PYY.js +1 -0
  96. package/dist/m/c-L3F5ZLIV.js +1 -0
  97. package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
  98. package/dist/m/c-L5VFATT6.js +1 -0
  99. package/dist/m/c-L7JL5TL3.js +3 -0
  100. package/dist/m/c-LKSL4AUJ.js +1 -0
  101. package/dist/m/c-M6FNQCTQ.js +1 -0
  102. package/dist/m/c-MCOOHCSG.js +1 -0
  103. package/dist/m/c-MHI3GYKF.js +1 -0
  104. package/dist/m/c-MWXFLW4J.js +1 -0
  105. package/dist/m/c-NEL7HQW3.js +1 -0
  106. package/dist/m/c-NQJRVJ56.js +1 -0
  107. package/dist/m/c-NWX3MFT4.js +1 -0
  108. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  109. package/dist/m/c-OKXI52N7.js +1 -0
  110. package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
  111. package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
  112. package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
  113. package/dist/m/c-P3OFYUMA.js +1 -0
  114. package/dist/m/c-PLN76GF3.js +6 -0
  115. package/dist/m/c-PLP4JKFC.js +1 -0
  116. package/dist/m/c-Q26MME6N.js +1 -0
  117. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  118. package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
  119. package/dist/m/c-QIXT3Q7C.js +28 -0
  120. package/dist/m/c-QO4HNGCQ.js +1 -0
  121. package/dist/m/c-R27DWQKX.js +1 -0
  122. package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
  123. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  124. package/dist/m/c-RND3G2S2.js +1 -0
  125. package/dist/m/c-S2U6NJU4.js +1 -0
  126. package/dist/m/c-SPZPGD3R.js +1 -0
  127. package/dist/m/c-SW4YEG2D.js +8 -0
  128. package/dist/m/c-SZGYW5HT.js +1 -0
  129. package/dist/m/c-T637T7SN.js +1 -0
  130. package/dist/m/c-TJAXGIRV.js +1 -0
  131. package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
  132. package/dist/m/c-TTB5EK4S.js +32 -0
  133. package/dist/m/c-U7SHNWDH.js +1 -0
  134. package/dist/m/c-UDHIVLYH.js +20 -0
  135. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  136. package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
  137. package/dist/m/c-UPD33ZPE.js +1 -0
  138. package/dist/m/c-URSMSFCB.js +1 -0
  139. package/dist/m/c-UY4QX42B.js +1 -0
  140. package/dist/m/c-V44KWI2L.js +4 -0
  141. package/dist/m/c-VG5XIN4T.js +1 -0
  142. package/dist/m/c-VILENZTD.js +1 -0
  143. package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
  144. package/dist/m/c-VR2BJ4AN.js +1 -0
  145. package/dist/m/c-W4JCVLAB.js +1 -0
  146. package/dist/m/c-W6DQSHXI.js +135 -0
  147. package/dist/m/c-WBGWGOM7.js +1 -0
  148. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  149. package/dist/m/c-WGW764WX.js +1 -0
  150. package/dist/m/c-WMRJKQ2V.js +5 -0
  151. package/dist/m/c-WNTYKCDG.js +1 -0
  152. package/dist/m/c-WQWIRWO3.js +72 -0
  153. package/dist/m/c-X5LNHET5.js +1 -0
  154. package/dist/m/c-X5TSXZYJ.js +2 -0
  155. package/dist/m/c-XGH4GS2T.js +1 -0
  156. package/dist/m/c-Y6F2FORL.js +1 -0
  157. package/dist/m/c-YBKHUAK7.js +10 -0
  158. package/dist/m/c-YBSS55KU.js +20 -0
  159. package/dist/m/c-YCLU4UNT.js +2 -0
  160. package/dist/m/c-YVZ2L7CE.js +1 -0
  161. package/dist/m/c-ZK4GIZH2.js +1 -0
  162. package/dist/m/c-ZRXJD4G6.js +1 -0
  163. package/dist/m/c-ZWBI4HYK.js +1 -0
  164. package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
  165. package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
  166. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  167. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  168. package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
  169. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  170. package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
  171. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  172. package/dist/m/core/save-LYY7K6U7.js +1 -0
  173. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  174. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  175. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  176. package/dist/m/events/index-RN2JNGBV.js +1 -0
  177. package/dist/m/internals/index-TGLQINN3.js +1 -0
  178. package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
  179. package/dist/m/loader-3BBNZZGM.js +1 -0
  180. package/dist/m/manifest-F449FF98.js +1 -0
  181. package/dist/m/manifest-edit-4396A009.js +1 -0
  182. package/dist/m/manifest.json +2509 -1474
  183. package/dist/m/options/options-EJMS3K24.js +1 -0
  184. package/dist/m/page/editor-6LRB3GE4.js +304 -0
  185. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  186. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  187. package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
  188. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  189. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  190. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  191. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  192. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  193. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  194. package/dist/m/plugins/people-JCHVPF7P.js +1 -0
  195. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  196. package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
  197. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  198. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  199. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  200. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  201. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  202. package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
  203. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  204. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  205. package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
  206. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  207. package/dist/m/route/route-62CHXL5B.js +1 -0
  208. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  209. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  210. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  211. package/dist/offline-manifest.json +1560 -889
  212. package/dist/page/themes/light-1.css +33 -8
  213. package/dist/server/core.cjs +1554 -311
  214. package/dist/server/offline-runtime.cjs +63 -63
  215. package/dist/server/offline-runtime.mjs +63 -63
  216. package/dist/server/page-compaction-worker.cjs +3710 -0
  217. package/dist/server/rooms.mjs +5 -0
  218. package/docs/guide.md +653 -101
  219. package/entries/sap.js +5 -5
  220. package/package.json +1 -1
  221. package/src/core/admin-contenteditable.js +5 -2
  222. package/src/core/admin-inputs.js +1 -0
  223. package/src/core/admin-onclick.js +2 -1
  224. package/src/core/boot-phase.js +2 -1
  225. package/src/core/conflict-download.js +3 -29
  226. package/src/core/conflict-notice-ui.js +4 -7
  227. package/src/core/download.js +32 -0
  228. package/src/core/host-meta.js +56 -0
  229. package/src/core/is-edit-mode.js +17 -1
  230. package/src/core/mode-key.js +125 -19
  231. package/src/core/mode-support.js +5 -0
  232. package/src/core/mode-transition.js +20 -6
  233. package/src/core/preserve-authored.js +4 -4
  234. package/src/core/recovery.js +121 -33
  235. package/src/core/save-accepted.js +33 -0
  236. package/src/core/save-core.js +111 -32
  237. package/src/core/save-job.js +108 -0
  238. package/src/core/save-prepare.js +53 -0
  239. package/src/core/save-size-notice.js +3 -1
  240. package/src/core/save-slices.js +15 -0
  241. package/src/core/save.js +557 -127
  242. package/src/core/serialize-steps.js +109 -0
  243. package/src/core/snapshot.js +209 -40
  244. package/src/core/source-map.js +83 -38
  245. package/src/core/stale-host-notice.js +1 -0
  246. package/src/core/upgrade-run.js +59 -8
  247. package/src/dom/active-element.js +5 -0
  248. package/src/lib/assets.js +1 -0
  249. package/src/lib/autosave-scheduler.js +3 -4
  250. package/src/lib/slice.js +65 -0
  251. package/src/lib/uimotion.js +774 -0
  252. package/src/lib/unsaved-state.js +2 -2
  253. package/src/live/fast-lane.js +10 -3
  254. package/src/live/local-lane.js +202 -11
  255. package/src/live/ops.js +16 -0
  256. package/src/live/page-compaction.js +162 -0
  257. package/src/live/server-lane.js +1 -0
  258. package/src/live/store.js +11 -3
  259. package/src/loader-logic.js +3 -1
  260. package/src/loader.js +14 -4
  261. package/src/page/adopt-boot.js +2 -1
  262. package/src/page/awareness.js +33 -2
  263. package/src/page/blocks.js +122 -27
  264. package/src/page/board-views.js +2 -16
  265. package/src/page/boot-pack.js +1 -1
  266. package/src/page/chart-tables.js +29 -0
  267. package/src/page/chart.js +411 -72
  268. package/src/page/checklist.js +10 -135
  269. package/src/page/checkpoint-proof.js +295 -0
  270. package/src/page/chrome.js +257 -40
  271. package/src/page/clipboard-shape.js +126 -0
  272. package/src/page/document-title-dom.js +12 -0
  273. package/src/page/document-title-runtime.js +15 -1
  274. package/src/page/document-title.js +4 -1
  275. package/src/page/drag.js +33 -3
  276. package/src/page/editor.js +207 -31
  277. package/src/page/emoji-data.js +162 -0
  278. package/src/page/export-assets.js +357 -0
  279. package/src/page/export-html.js +209 -0
  280. package/src/page/export-markdown.js +361 -0
  281. package/src/page/features/board.js +12 -3
  282. package/src/page/features/chart.js +11 -0
  283. package/src/page/features/checklist.js +428 -0
  284. package/src/page/features/dates.js +180 -0
  285. package/src/page/features/emoji.js +187 -0
  286. package/src/page/features/export-html.js +189 -0
  287. package/src/page/features/export-markdown.js +169 -0
  288. package/src/page/features/import-markdown.js +349 -0
  289. package/src/page/features/media.js +61 -5
  290. package/src/page/features/mentions.js +188 -0
  291. package/src/page/features/registry.js +24 -1
  292. package/src/page/features/table.js +375 -85
  293. package/src/page/features/title-cover.js +331 -0
  294. package/src/page/features/title-icon.js +213 -0
  295. package/src/page/features/toc.js +87 -0
  296. package/src/page/format-command.js +33 -0
  297. package/src/page/format-gate.js +68 -18
  298. package/src/page/format-host.js +71 -0
  299. package/src/page/format-native-commands.js +158 -0
  300. package/src/page/format-native.js +42 -0
  301. package/src/page/image-actions.js +64 -0
  302. package/src/page/import-markdown.js +262 -0
  303. package/src/page/inline.js +32 -8
  304. package/src/page/items.js +141 -11
  305. package/src/page/markdown.js +30 -2
  306. package/src/page/media.js +35 -119
  307. package/src/page/mention-picker.js +99 -0
  308. package/src/page/merge.js +1 -1
  309. package/src/page/placeholder.js +14 -1
  310. package/src/page/projection.js +156 -25
  311. package/src/page/recover.js +10 -12
  312. package/src/page/recovery-ui.js +464 -0
  313. package/src/page/rich/chart.js +63 -7
  314. package/src/page/save-projection.js +67 -0
  315. package/src/page/save-states.js +14 -4
  316. package/src/page/schema.js +52 -3
  317. package/src/page/table-paste.js +3 -1
  318. package/src/page/table-style.js +4 -4
  319. package/src/page/table.js +44 -2
  320. package/src/page/themes/content.css +25 -1
  321. package/src/page/themes/light-1.css +33 -8
  322. package/src/page/themes/light-1.source.css +8 -7
  323. package/src/page/toc-link.js +31 -0
  324. package/src/page/toc.js +5 -0
  325. package/src/page/view-hooks.js +58 -0
  326. package/src/page/view.js +13 -0
  327. package/src/page/ybind/binding.js +116 -6
  328. package/src/page/ybind/bridge.js +187 -20
  329. package/src/page/ybind/containment.js +162 -0
  330. package/src/page/ybind/doc.js +17 -9
  331. package/src/page/ybind/file-debt.js +63 -0
  332. package/src/page/ybind/layout.js +7 -2
  333. package/src/page/ybind/marks.js +2 -1
  334. package/src/page/ybind/moves.js +5 -4
  335. package/src/page/ybind/origins.js +106 -4
  336. package/src/page/ybind/parts.js +8 -1
  337. package/src/page/ybind/provider.js +125 -18
  338. package/src/page/ybind/recovery-proof.js +32 -9
  339. package/src/page/ybind/recovery-replay.js +905 -4
  340. package/src/page/ybind/recovery.js +174 -45
  341. package/src/page/ybind/table.js +67 -11
  342. package/src/page/ybind/walk-delta.js +258 -0
  343. package/src/page/ybind/walk.js +54 -29
  344. package/src/page/ybind/writer.js +1 -1
  345. package/src/plugins/ai-edit.js +13 -9
  346. package/src/plugins/indicator-chip.js +91 -0
  347. package/src/plugins/indicator.js +13 -80
  348. package/src/plugins/live.js +126 -15
  349. package/src/plugins/menu/actions.js +196 -0
  350. package/src/plugins/menu/people.js +279 -0
  351. package/src/plugins/menu/placement.js +121 -0
  352. package/src/plugins/menu/preferences.js +9 -0
  353. package/src/plugins/menu/seo.js +316 -397
  354. package/src/plugins/menu/style.js +171 -0
  355. package/src/plugins/menu/variants.js +296 -0
  356. package/src/plugins/menu.js +186 -183
  357. package/src/plugins/page-view.js +3 -2
  358. package/src/plugins/page.js +362 -43
  359. package/src/plugins/sortable.js +8 -1
  360. package/src/plugins/source.js +6 -0
  361. package/src/plugins/upgrade.js +51 -17
  362. package/src/plugins/upload-ui.js +12 -17
  363. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  364. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  365. package/src/plugins/visitor-drafts/intro.js +7 -4
  366. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  367. package/src/plugins/visitor-drafts/prompt.js +11 -9
  368. package/src/plugins/visitor-drafts/source.js +4 -3
  369. package/src/plugins/visitor-drafts/store.js +138 -9
  370. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  371. package/src/plugins/visitor-drafts-owner.js +4 -6
  372. package/src/plugins/visitor-drafts.js +5 -3
  373. package/src/recovery/store.js +1 -1
  374. package/src/route/route.js +4 -0
  375. package/src/server/checkpoint/index.js +2 -2
  376. package/src/server/rooms/index.js +5 -1
  377. package/src/server/rooms/live-data-rooms.js +302 -50
  378. package/src/server/rooms/page-compaction-worker.js +12 -0
  379. package/src/server/rooms/page-compactor.js +92 -0
  380. package/src/server/rooms/page-room-conformance.js +461 -0
  381. package/src/source/parse-steps.js +94 -0
  382. package/src/source/session.js +233 -57
  383. package/src/sync/editing-sender.js +12 -0
  384. package/src/sync/live-sync-edit.js +22 -2
  385. package/src/sync/live-sync.js +150 -23
  386. package/src/sync/page-relay.js +52 -0
  387. package/src/sync/presence-store.js +44 -4
  388. package/src/sync/presence.js +6 -3
  389. package/src/sync/saved-stream.js +28 -14
  390. package/src/ui/bevel-controls.js +1 -0
  391. package/src/ui/bevel-dialog.js +5 -2
  392. package/src/ui/editor-ui.js +77 -0
  393. package/src/ui/page-colour.js +281 -0
  394. package/src/ui/toast.js +16 -7
  395. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  396. package/src/vendor/hyper-undo.vendor.js +1 -1
  397. package/src/vendor/hypercms.vendor.js +9 -9
  398. package/src/vendor/parse5.vendor.js +2 -2
  399. package/src/vendor/sap-core.vendor.js +5 -5
  400. package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
  401. package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
  402. package/dist/m/c-2M73252O.js +0 -1
  403. package/dist/m/c-2SPVQSHC.js +0 -1
  404. package/dist/m/c-35NBGMZB.js +0 -1
  405. package/dist/m/c-3IQRECYS.js +0 -1
  406. package/dist/m/c-4B6H4G5V.js +0 -1
  407. package/dist/m/c-4FFHZM32.js +0 -1
  408. package/dist/m/c-4YSCRICO.js +0 -1
  409. package/dist/m/c-54RCDCBU.js +0 -1
  410. package/dist/m/c-5M4YEKCX.js +0 -4
  411. package/dist/m/c-5V3MOVJT.js +0 -1
  412. package/dist/m/c-66O32MV3.js +0 -28
  413. package/dist/m/c-6HMB24EV.js +0 -1
  414. package/dist/m/c-7O6AFR65.js +0 -1
  415. package/dist/m/c-ANQTJ3B5.js +0 -2
  416. package/dist/m/c-AYVOREBE.js +0 -1
  417. package/dist/m/c-B2ZXPOS3.js +0 -3
  418. package/dist/m/c-BMIZKMPV.js +0 -1
  419. package/dist/m/c-BTDI4Y23.js +0 -1
  420. package/dist/m/c-CJZSLMVQ.js +0 -1
  421. package/dist/m/c-DBE6ORSI.js +0 -1
  422. package/dist/m/c-EDHUR4MX.js +0 -1
  423. package/dist/m/c-EY6UOM54.js +0 -1
  424. package/dist/m/c-F3CKJTJS.js +0 -1
  425. package/dist/m/c-F7Y6FLPY.js +0 -1
  426. package/dist/m/c-FA4DZVTC.js +0 -1
  427. package/dist/m/c-G2BUZITR.js +0 -1
  428. package/dist/m/c-GB3JTV6E.js +0 -1
  429. package/dist/m/c-GBGWXTX4.js +0 -1
  430. package/dist/m/c-GHBCCVG4.js +0 -1
  431. package/dist/m/c-GJAEGGXY.js +0 -1
  432. package/dist/m/c-GOWBDM6J.js +0 -1
  433. package/dist/m/c-IB52R237.js +0 -1
  434. package/dist/m/c-IKWNE4R4.js +0 -1
  435. package/dist/m/c-IU3IYEU6.js +0 -1
  436. package/dist/m/c-IZ2EBRAJ.js +0 -1
  437. package/dist/m/c-KR4UW4KW.js +0 -1
  438. package/dist/m/c-KY3VJGUA.js +0 -8
  439. package/dist/m/c-LFPHCXY3.js +0 -1
  440. package/dist/m/c-LRPUO2MX.js +0 -1
  441. package/dist/m/c-LXENEBV7.js +0 -1
  442. package/dist/m/c-NMJRXKPC.js +0 -8
  443. package/dist/m/c-OIBUTDXI.js +0 -1
  444. package/dist/m/c-PPOSCAQD.js +0 -1
  445. package/dist/m/c-QIX5BJYN.js +0 -1
  446. package/dist/m/c-QQHEX6HA.js +0 -1
  447. package/dist/m/c-R7RMANJI.js +0 -9
  448. package/dist/m/c-RI4NT75Y.js +0 -1
  449. package/dist/m/c-RM7J7BV2.js +0 -1
  450. package/dist/m/c-RUJKSUQW.js +0 -1
  451. package/dist/m/c-SGCCC4FW.js +0 -25
  452. package/dist/m/c-T4VO4OCB.js +0 -1
  453. package/dist/m/c-TQEMVJCS.js +0 -1
  454. package/dist/m/c-TR6RMDQ7.js +0 -3
  455. package/dist/m/c-UKHTLWUW.js +0 -1
  456. package/dist/m/c-UNN7LWCT.js +0 -1
  457. package/dist/m/c-UXBMRDZB.js +0 -235
  458. package/dist/m/c-V7PZZRG7.js +0 -1
  459. package/dist/m/c-VL52ADDR.js +0 -1
  460. package/dist/m/c-WNYBLIOZ.js +0 -1
  461. package/dist/m/c-WWDBYXLK.js +0 -1
  462. package/dist/m/c-XNNW2CWK.js +0 -1
  463. package/dist/m/c-Y6USGTS5.js +0 -5
  464. package/dist/m/c-YG2NJYS6.js +0 -135
  465. package/dist/m/c-Z4VRC7GU.js +0 -7
  466. package/dist/m/c-Z6K52OLP.js +0 -19
  467. package/dist/m/c-ZDY2MDAA.js +0 -21
  468. package/dist/m/c-ZUCOKOHT.js +0 -1
  469. package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
  470. package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
  471. package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
  472. package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
  473. package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
  474. package/dist/m/core/recovery-TBBSICE2.js +0 -1
  475. package/dist/m/core/save-WUCHTQBE.js +0 -1
  476. package/dist/m/core/save-core-FJVHT5M3.js +0 -1
  477. package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
  478. package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
  479. package/dist/m/events/index-23DPTUT4.js +0 -1
  480. package/dist/m/internals/index-WOVAP3RU.js +0 -1
  481. package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
  482. package/dist/m/loader-HXX6YNH5.js +0 -1
  483. package/dist/m/manifest-F22DEDAC.js +0 -1
  484. package/dist/m/manifest-edit-C3C7376B.js +0 -1
  485. package/dist/m/options/options-2LH76JQ3.js +0 -1
  486. package/dist/m/page/editor-XUTB2HB2.js +0 -189
  487. package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
  488. package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
  489. package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
  490. package/dist/m/plugins/live-NUTTXSBS.js +0 -1
  491. package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
  492. package/dist/m/plugins/page-KSUH7G6J.js +0 -2
  493. package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
  494. package/dist/m/plugins/people-AJJ32YCG.js +0 -1
  495. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  496. package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
  497. package/dist/m/plugins/source-KHK5UUSK.js +0 -1
  498. package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
  499. package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
  500. package/dist/m/plugins/upload-XSGNILUY.js +0 -1
  501. package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
  502. package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
  503. package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
  504. package/dist/m/reader-core-ILWLXVMP.js +0 -68
  505. package/dist/m/route/route-KOCZSEGN.js +0 -1
  506. package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
  507. package/dist/m/ui/index-EVHKMKBL.js +0 -1
  508. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  509. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  510. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  511. /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
package/src/page/chart.js CHANGED
@@ -1,13 +1,22 @@
1
- import { state } from "../vendor/prosemirror.vendor.js";
2
1
  import { schema } from "./schema.js";
3
2
  import { blockJson, keyBetween, newId, orderedIds } from "./model.js";
3
+ import { chartTables, tableName } from "./chart-tables.js";
4
4
  import { chartModel, renderChartSvg } from "./rich/chart.js";
5
5
  import { openPageMenu } from "./chrome.js";
6
6
  import { project } from "./projection.js";
7
7
  import { columnKinds } from "./table-kinds.js";
8
8
  import { cellText } from "./rich/cell.js";
9
+ import { registerUnsavedState } from "../lib/unsaved-state.js";
10
+ import toast from "../ui/toast-lazy.js";
11
+ import { asStep } from "./blocksel.js";
12
+ import { state } from "../vendor/prosemirror.vendor.js";
13
+
14
+ const { TextSelection } = state;
9
15
 
10
16
  const views = new WeakMap();
17
+ // A chart inserted by the slash command or a table action opens its inspector as soon
18
+ // as its node view exists; one that arrives any other way stays closed.
19
+ const openOnMount = new Set();
11
20
 
12
21
  function sourceTable(doc, id) {
13
22
  let table = null;
@@ -25,6 +34,7 @@ function chartAttrs(table, pos, parent) {
25
34
  if (!colIds.length) return null;
26
35
  const kinds = columnKinds(table);
27
36
  const series = colIds.filter((id) => id !== colIds[0] && kinds[id] === "number");
37
+ if (!series.length) return null;
28
38
  return {
29
39
  id: newId(),
30
40
  pos,
@@ -32,7 +42,7 @@ function chartAttrs(table, pos, parent) {
32
42
  source: table.attrs.id,
33
43
  kind: "bar",
34
44
  x: colIds[0],
35
- series: series.length ? series : colIds[1] ? [colIds[1]] : [],
45
+ series,
36
46
  title: "",
37
47
  highlight: null,
38
48
  };
@@ -45,16 +55,16 @@ export function insertChartAfterTable(view, tablePos) {
45
55
  const after = tablePos + table.nodeSize;
46
56
  const next = doc.maybeChild(doc.resolve(after).index(0));
47
57
  const attrs = chartAttrs(table, keyBetween(table.attrs.pos, next?.attrs.pos ?? null), table.attrs.parent);
48
- if (!attrs) return false;
58
+ if (!attrs) {
59
+ toast("This table has no number column to chart yet.");
60
+ return true;
61
+ }
62
+ openOnMount.add(attrs.id);
49
63
  view.dispatch(view.state.tr.insert(after, schema.nodes.chart.create(attrs)));
50
64
  return true;
51
65
  }
52
66
 
53
- export function chartTables(doc) {
54
- const tables = [];
55
- doc.forEach((node) => { if (node.type === schema.nodes.table && orderedIds(node.attrs.cols).length) tables.push(node); });
56
- return tables;
57
- }
67
+ export { chartTables };
58
68
 
59
69
  // /chart: an empty line becomes the chart, otherwise it goes below the line.
60
70
  // One table is charted at once; several open a picker named by their columns.
@@ -69,9 +79,14 @@ function placeChart(view, index, table, remove) {
69
79
  const next = tr.doc.maybeChild(index + 1);
70
80
  const pos = replace ? node.attrs.pos : keyBetween(node.attrs.pos, next?.attrs.pos ?? null);
71
81
  const attrs = chartAttrs(table, pos, node.attrs.parent ?? null);
72
- if (!attrs) return false;
82
+ if (!attrs) {
83
+ if (remove) view.dispatch(view.state.tr.delete(remove.from, remove.to));
84
+ toast("This table has no number column to chart yet.");
85
+ return true;
86
+ }
73
87
  const chart = schema.nodes.chart.create(attrs);
74
88
  tr = replace ? tr.replaceWith(at, at + node.nodeSize, chart) : tr.insert(at + node.nodeSize, chart);
89
+ openOnMount.add(attrs.id);
75
90
  view.dispatch(tr.scrollIntoView());
76
91
  return true;
77
92
  }
@@ -80,49 +95,151 @@ export const tableLabel = (table) => orderedIds(table.attrs.cols).map((id) => ta
80
95
 
81
96
  export function chartFromSlash(view, index, remove = null) {
82
97
  const tables = chartTables(view.state.doc);
83
- if (!tables.length) return false;
98
+ if (!tables.length) {
99
+ if (remove) view.dispatch(view.state.tr.delete(remove.from, remove.to));
100
+ toast("Add a table first: a chart plots the numbers in a table on this page.");
101
+ return true;
102
+ }
84
103
  if (tables.length === 1) return placeChart(view, index, tables[0], remove);
85
- const anchor = view.nodeDOM(view.state.doc.resolve(view.state.selection.head).before(1)) ?? view.dom;
86
- return openPageMenu(view, anchor, tables.map((table, i) => ({
87
- label: `Table ${i + 1}`,
88
- description: tableLabel(table),
104
+ const doc = view.state.doc;
105
+ const anchor = view.nodeDOM(doc.resolve(view.state.selection.head).before(1)) ?? view.dom;
106
+ return openPageMenu(view, anchor, tables.map((table) => ({
107
+ label: tableName(doc, table.attrs.id) ?? "",
108
+ description: `${table.childCount} ${table.childCount === 1 ? "row" : "rows"} · ${tableLabel(table)}`,
89
109
  icon: "▦",
90
110
  run: () => placeChart(view, index, table, remove),
91
111
  })));
92
112
  }
93
113
 
114
+ const ICON = (body) => `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
115
+ const TABLE_ICON = ICON('<rect x="2.5" y="3" width="11" height="10" rx="1.5"/><path d="M2.5 6.5h11M6 6.5V13"/>');
116
+ const GEAR_ICON = ICON('<g transform="scale(0.6667)" stroke-width="2.25"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></g>');
117
+ const TRASH_ICON = ICON('<path d="M2.75 4.5h10.5M6.25 4.5V2.75h3.5V4.5M4.25 4.5l.6 8.75h6.3l.6-8.75"/>');
118
+ const CHART_TYPES = [["bar", "Bar"], ["line", "Line"], ["pie", "Pie"]];
119
+ const UNTITLED = "Untitled chart";
120
+
121
+ function tableOffset(doc, id) {
122
+ let pos = null;
123
+ doc.forEach((node, offset) => { if (pos === null && node.type === schema.nodes.table && node.attrs.id === id) pos = offset; });
124
+ return pos;
125
+ }
126
+
127
+ function documentTables(doc) {
128
+ const tables = [];
129
+ doc.forEach((node) => { if (node.type === schema.nodes.table && orderedIds(node.attrs.cols).length > 1) tables.push(node); });
130
+ return tables.map((node) => ({ id: node.attrs.id, name: tableName(doc, node.attrs.id), rows: node.childCount }));
131
+ }
132
+
133
+ function defaultColumns(table) {
134
+ const colIds = orderedIds(table.attrs.cols);
135
+ if (!colIds.length) return null;
136
+ const kinds = columnKinds(table);
137
+ const x = colIds.find((id) => kinds[id] !== "number") ?? colIds[0];
138
+ const series = colIds.filter((id) => id !== x && kinds[id] === "number");
139
+ return { x, series: series.length ? series : colIds.filter((id) => id !== x).slice(0, 1) };
140
+ }
141
+
142
+ function element(doc, tag, className, text = null) {
143
+ const node = doc.createElement(tag);
144
+ if (className) node.className = className;
145
+ if (text !== null) node.textContent = text;
146
+ return node;
147
+ }
148
+
149
+ function option(doc, value, label, selected = false) {
150
+ const node = element(doc, "option", "", label);
151
+ node.value = value;
152
+ node.selected = selected;
153
+ return node;
154
+ }
155
+
156
+ function select(doc, field, label) {
157
+ const node = element(doc, "select", "page-chart-select");
158
+ node.dataset.field = field;
159
+ node.setAttribute("aria-label", label);
160
+ return node;
161
+ }
162
+
94
163
  export class ChartView {
95
164
  constructor(node, view, getPos) {
96
165
  this.node = node;
97
166
  this.view = view;
98
167
  this.getPos = getPos;
99
168
  this.source = undefined;
169
+ this.tables = null;
100
170
  this.frame = null;
171
+ this.unsaved = null;
172
+ this.titleInput = null;
101
173
  const doc = view.dom.ownerDocument;
174
+ this.doc = doc;
102
175
  this.dom = doc.createElement("figure");
103
176
  this.dom.className = "page-chart";
104
177
  this.dom.dataset.id = node.attrs.id ?? "";
105
178
  this.dom.dataset.type = "chart";
106
- this.plot = doc.createElement("div");
107
- this.plot.className = "page-chart-plot";
108
- this.caption = doc.createElement("figcaption");
109
- this.settings = doc.createElement("button");
110
- this.settings.type = "button";
111
- this.settings.className = "page-chart-settings";
112
- this.settings.textContent = "Chart settings";
113
- this.settings.setAttribute("aria-label", "Chart settings");
114
- this.settings.contentEditable = "false";
115
- this.settings.addEventListener("click", () => this.openSettings());
116
- this.dom.append(this.plot, this.caption, this.settings);
179
+ this.head = element(doc, "div", "page-chart-head");
180
+ this.head.contentEditable = "false";
181
+ this.title = element(doc, "span", "page-chart-title");
182
+ this.chip = element(doc, "button", "page-chart-source");
183
+ this.chip.type = "button";
184
+ this.chip.contentEditable = "false";
185
+ this.chip.setAttribute("aria-label", "Go to source table");
186
+ this.chip.innerHTML = TABLE_ICON;
187
+ this.chipName = element(doc, "span", "page-chart-source-name");
188
+ this.chip.append(this.chipName);
189
+ this.chip.addEventListener("click", () => this.goToSource());
190
+ this.edit = element(doc, "button", "page-chart-edit");
191
+ this.edit.type = "button";
192
+ this.edit.contentEditable = "false";
193
+ this.edit.setAttribute("aria-haspopup", "dialog");
194
+ this.edit.setAttribute("aria-expanded", "false");
195
+ this.edit.innerHTML = GEAR_ICON;
196
+ this.edit.append("Edit chart");
197
+ this.edit.addEventListener("click", () => (this.panel.isConnected ? this.closePanel(true) : this.openPanel()));
198
+ this.head.append(this.title, this.chip, this.edit);
199
+ this.plot = element(doc, "div", "page-chart-plot");
200
+ this.broken = element(doc, "div", "page-chart-broken");
201
+ this.broken.hidden = true;
202
+ this.broken.contentEditable = "false";
203
+ this.brokenKey = null;
204
+ this.brokenSelect = null;
205
+ this.broken.addEventListener("click", (event) => this.brokenClick(event));
206
+ this.panel = element(doc, "div", "page-chart-panel");
207
+ this.panel.tabIndex = -1;
208
+ this.panel.contentEditable = "false";
209
+ this.panel.setAttribute("role", "dialog");
210
+ this.panel.setAttribute("aria-label", "Chart settings");
211
+ this.panel.addEventListener("click", (event) => this.panelClick(event));
212
+ this.panel.addEventListener("change", (event) => this.panelChange(event));
213
+ this.panel.addEventListener("mousedown", (event) => { if (event.target.closest?.("button")) event.preventDefault(); });
214
+ this.outside = (event) => {
215
+ if (!this.panel.isConnected || this.dom.contains(event.target)) return;
216
+ this.commitTitle();
217
+ this.closePanel();
218
+ };
219
+ this.escape = (event) => {
220
+ if (!this.panel.isConnected || event.key !== "Escape") return;
221
+ event.preventDefault();
222
+ event.stopPropagation();
223
+ this.closePanel(true);
224
+ };
225
+ doc.addEventListener("mousedown", this.outside, true);
226
+ doc.addEventListener("keydown", this.escape, true);
227
+ this.dom.append(this.head, this.plot, this.broken);
117
228
  if (!views.has(view)) views.set(view, new Set());
118
229
  views.get(view).add(this);
119
230
  this.refresh();
231
+ if (openOnMount.delete(node.attrs.id)) this.openSoon();
120
232
  }
121
233
 
122
234
  refresh() {
123
- const source = sourceTable(this.view.state.doc, this.node.attrs.source);
124
- if (source === this.source) return;
235
+ this.renderHead();
236
+ if (this.panel.isConnected && this.panelKey() !== this.renderedKey) this.redrawPanel();
237
+ const doc = this.view.state.doc;
238
+ const source = sourceTable(doc, this.node.attrs.source);
239
+ const tables = documentTables(doc).map((table) => `${table.id}:${table.name}:${table.rows}`).join("|");
240
+ if (source === this.source && tables === this.tables) return;
125
241
  this.source = source;
242
+ this.tables = tables;
126
243
  this.schedule();
127
244
  }
128
245
 
@@ -137,7 +254,70 @@ export class ChartView {
137
254
  const attrs = this.node.attrs;
138
255
  const chart = chartModel({ id: attrs.id, type: attrs.kind, x: attrs.x, series: attrs.series, title: attrs.title, highlight: attrs.highlight }, tableData(this.source));
139
256
  this.plot.innerHTML = renderChartSvg(chart, { width: 640, height: 320, narrow: this.dom.getBoundingClientRect().width < 480 });
140
- this.caption.textContent = attrs.title;
257
+ this.renderBroken(chart);
258
+ }
259
+
260
+ renderBroken(chart) {
261
+ const broken = chart.state === "missing" && chart.reason === "table";
262
+ this.plot.hidden = broken;
263
+ this.broken.hidden = !broken;
264
+ if (!broken) {
265
+ if (this.brokenKey !== null) {
266
+ this.broken.textContent = "";
267
+ this.brokenKey = null;
268
+ this.brokenSelect = null;
269
+ }
270
+ return;
271
+ }
272
+ const tables = documentTables(this.view.state.doc);
273
+ const key = tables.map((table) => `${table.id}:${table.name}:${table.rows}`).join("|");
274
+ if (this.brokenKey === key) return;
275
+ this.brokenKey = key;
276
+ this.brokenSelect = null;
277
+ const doc = this.doc;
278
+ this.broken.textContent = "";
279
+ this.broken.append(
280
+ element(doc, "div", "page-chart-broken-head", "This chart's table was deleted"),
281
+ element(doc, "p", "page-chart-broken-text", "Pick another table in this document, or delete the chart. Undo brings the table back."),
282
+ );
283
+ const actions = element(doc, "div", "page-chart-broken-actions");
284
+ if (tables.length) {
285
+ const sources = select(doc, "rescue", "Source table");
286
+ for (const table of tables) sources.append(option(doc, table.id, `${table.name} (${table.rows} ${table.rows === 1 ? "row" : "rows"})`));
287
+ this.brokenSelect = sources;
288
+ const use = element(doc, "button", "page-chart-use", "Use this table");
289
+ use.type = "button";
290
+ use.dataset.action = "use";
291
+ actions.append(sources, use);
292
+ }
293
+ const remove = element(doc, "button", "page-chart-delete", "Delete chart");
294
+ remove.type = "button";
295
+ remove.dataset.action = "delete";
296
+ actions.append(remove);
297
+ this.broken.append(actions);
298
+ }
299
+
300
+ brokenClick(event) {
301
+ const button = event.target.closest?.("button[data-action]");
302
+ if (!button) return;
303
+ if (button.dataset.action === "delete") this.removeChart();
304
+ else if (button.dataset.action === "use") this.switchSource(this.brokenSelect?.value ?? "");
305
+ }
306
+
307
+ openSoon() {
308
+ const win = this.dom.ownerDocument.defaultView;
309
+ const raf = win.requestAnimationFrame?.bind(win) ?? ((fn) => win.setTimeout(fn, 0));
310
+ raf(() => { if (!this.view.isDestroyed && this.dom.isConnected) this.openPanel(); });
311
+ }
312
+
313
+ renderHead() {
314
+ const attrs = this.node.attrs;
315
+ const name = tableName(this.view.state.doc, attrs.source);
316
+ const broken = !sourceTable(this.view.state.doc, attrs.source);
317
+ this.title.textContent = attrs.title || UNTITLED;
318
+ this.title.classList.toggle("is-empty", !attrs.title);
319
+ this.chipName.textContent = broken ? "No table" : name ?? "";
320
+ this.chip.disabled = broken;
141
321
  }
142
322
 
143
323
  setAttrs(changes) {
@@ -148,48 +328,199 @@ export class ChartView {
148
328
  this.view.dispatch(this.view.state.tr.setNodeMarkup(pos, null, { ...node.attrs, ...changes }));
149
329
  }
150
330
 
151
- submenu(items) {
152
- openPageMenu(this.view, this.settings, items);
331
+ row(label, ...children) {
332
+ const row = element(this.doc, "div", "page-chart-row");
333
+ if (label) row.append(element(this.doc, "span", "page-chart-label", label));
334
+ row.append(...children);
335
+ return row;
336
+ }
337
+
338
+ panelKey() {
339
+ const doc = this.view.state.doc;
340
+ const tables = documentTables(doc).map((table) => `${table.id}:${table.name}:${table.rows}`).join("|");
341
+ return JSON.stringify([this.node.attrs, tables, tableData(sourceTable(doc, this.node.attrs.source))]);
342
+ }
343
+
344
+ redrawPanel() {
345
+ const active = this.doc.activeElement;
346
+ const focus = active && this.panel.contains(active) ? active.dataset ?? {} : null;
347
+ const input = this.titleInput;
348
+ const draft = input && input.value !== this.renderedTitle ? { value: input.value, start: input.selectionStart, end: input.selectionEnd } : null;
349
+ this.renderPanel();
350
+ if (draft && this.titleInput) this.titleInput.value = draft.value;
351
+ if (!focus) return;
352
+ const match = [...this.panel.querySelectorAll("button, input, select")].find((control) =>
353
+ ["field", "kind", "col", "action"].every((key) => control.dataset[key] === focus[key]));
354
+ if (!match) return this.panel.focus();
355
+ match.focus();
356
+ if (draft && match === this.titleInput) match.setSelectionRange?.(draft.start, draft.end);
153
357
  }
154
358
 
155
- openSettings() {
359
+ renderPanel() {
360
+ const doc = this.doc;
361
+ this.renderedKey = this.panelKey();
362
+ this.renderedTitle = this.node.attrs.title ?? "";
156
363
  const attrs = this.node.attrs;
157
- const table = tableData(sourceTable(this.view.state.doc, attrs.source));
158
- const cols = table?.cols ?? [];
159
- const rows = table?.rows ?? [];
160
- this.submenu([
161
- { label: "Line", icon: attrs.kind === "line" ? "✓" : "", run: () => this.setAttrs({ kind: "line" }) },
162
- { label: "Bar", icon: attrs.kind === "bar" ? "✓" : "", run: () => this.setAttrs({ kind: "bar" }) },
163
- { label: "X column ▸", focus: false, run: () => this.submenu(cols.map((col) => ({ label: col.name || col.id, icon: attrs.x === col.id ? "✓" : "", run: () => this.setAttrs({ x: col.id, series: attrs.series.filter((id) => id !== col.id) }) }))) },
164
- { label: "Series ▸", focus: false, run: () => this.submenu(cols.filter((col) => col.id !== attrs.x).map((col) => ({ label: col.name || col.id, icon: attrs.series.includes(col.id) ? "✓" : "", checked: attrs.series.includes(col.id), run: () => this.setAttrs({ series: attrs.series.includes(col.id) ? attrs.series.filter((id) => id !== col.id) : [...attrs.series, col.id] }) }))) },
165
- { label: "Highlight row ▸", focus: false, run: () => this.submenu([{ label: "None", icon: !attrs.highlight ? "✓" : "", run: () => this.setAttrs({ highlight: null }) }, ...rows.map((row) => ({ label: cellText(row.cells[attrs.x]) || row.id, icon: attrs.highlight === row.id ? "✓" : "", run: () => this.setAttrs({ highlight: row.id }) }))]) },
166
- { label: "Edit title", focus: false, run: () => this.editTitle() },
167
- ]);
168
- }
169
-
170
- editTitle() {
171
- const input = this.dom.ownerDocument.createElement("input");
172
- input.type = "text";
173
- input.className = "page-chart-title-input";
174
- input.setAttribute("aria-label", "Chart title");
175
- input.value = this.node.attrs.title ?? "";
176
- const finish = (save) => {
177
- input.remove();
178
- this.caption.hidden = false;
179
- if (save) this.setAttrs({ title: input.value });
180
- else this.settings.focus();
181
- };
182
- input.addEventListener("keydown", (event) => {
183
- if (event.key === "Enter" || event.key === "Escape") {
184
- event.preventDefault();
185
- event.stopPropagation();
186
- finish(event.key === "Enter");
187
- }
364
+ const source = tableData(this.source);
365
+ const cols = source?.cols ?? [];
366
+ const rows = source?.rows ?? [];
367
+ const pie = attrs.kind === "pie";
368
+ this.unsaved?.dispose();
369
+ this.unsaved = null;
370
+ this.titleInput = null;
371
+ this.panel.textContent = "";
372
+
373
+ const types = element(doc, "div", "page-chart-types");
374
+ for (const [kind, label] of CHART_TYPES) {
375
+ const button = element(doc, "button", "page-chart-type", label);
376
+ button.type = "button";
377
+ button.dataset.kind = kind;
378
+ button.setAttribute("aria-pressed", String(attrs.kind === kind));
379
+ types.append(button);
380
+ }
381
+ this.panel.append(this.row("Chart type", types));
382
+
383
+ const sources = select(doc, "source", "Source table");
384
+ for (const table of documentTables(this.view.state.doc)) {
385
+ sources.append(option(doc, table.id, `${table.name} (${table.rows} ${table.rows === 1 ? "row" : "rows"})`, table.id === attrs.source));
386
+ }
387
+ this.panel.append(this.row("Source table", sources));
388
+
389
+ const labels = select(doc, "labels", "Labels column");
390
+ for (const col of cols) labels.append(option(doc, col.id, col.name || col.id, col.id === attrs.x));
391
+ this.panel.append(this.row("Labels column", labels));
392
+
393
+ const values = element(doc, "div", "page-chart-values");
394
+ cols.filter((col) => col.id !== attrs.x).forEach((col, index) => {
395
+ const line = element(doc, "label", "page-chart-value");
396
+ const check = element(doc, "input");
397
+ check.type = "checkbox";
398
+ check.dataset.field = "series";
399
+ check.dataset.col = col.id;
400
+ check.checked = attrs.series.includes(col.id);
401
+ const swatch = element(doc, "span", "page-chart-swatch");
402
+ swatch.style.background = `var(--chart-${index % 6 + 1})`;
403
+ swatch.setAttribute("aria-hidden", "true");
404
+ line.append(check, swatch, element(doc, "span", "page-chart-value-name", col.name || col.id));
405
+ values.append(line);
406
+ });
407
+ const valueRow = this.row(pie ? "Value column" : "Value columns", values);
408
+ if (pie) valueRow.append(element(doc, "span", "page-chart-hint", "Pie charts plot the first value column across rows."));
409
+ this.panel.append(valueRow);
410
+
411
+ const highlight = select(doc, "highlight", "Highlight row");
412
+ highlight.append(option(doc, "", "None", !attrs.highlight));
413
+ for (const row of rows) highlight.append(option(doc, row.id, cellText(row.cells?.[attrs.x]) || row.id, row.id === attrs.highlight));
414
+ this.panel.append(this.row("Highlight row", highlight));
415
+
416
+ const title = element(doc, "input", "page-chart-title-input");
417
+ title.type = "text";
418
+ title.dataset.field = "title";
419
+ title.setAttribute("aria-label", "Chart title");
420
+ title.value = attrs.title ?? "";
421
+ title.addEventListener("keydown", (event) => {
422
+ if (event.isComposing || event.keyCode === 229) return;
423
+ if (event.key !== "Enter") return;
424
+ event.preventDefault();
425
+ event.stopPropagation();
426
+ this.commitTitle();
188
427
  });
189
- this.caption.hidden = true;
190
- this.caption.after(input);
191
- input.focus();
192
- input.select();
428
+ title.addEventListener("change", () => this.commitTitle());
429
+ title.addEventListener("blur", () => this.commitTitle());
430
+ this.titleInput = title;
431
+ this.panel.append(this.row("Chart title", title));
432
+
433
+ const foot = element(doc, "div", "page-chart-foot");
434
+ const remove = element(doc, "button", "page-chart-remove");
435
+ remove.type = "button";
436
+ remove.dataset.action = "remove";
437
+ remove.innerHTML = TRASH_ICON;
438
+ remove.append("Remove");
439
+ const done = element(doc, "button", "page-chart-done", "Done");
440
+ done.type = "button";
441
+ done.dataset.action = "done";
442
+ foot.append(remove, done);
443
+ this.panel.append(foot);
444
+
445
+ this.unsaved = registerUnsavedState({
446
+ id: "chart-title",
447
+ isPending: () => title.isConnected && title.value !== (this.node.attrs.title ?? ""),
448
+ });
449
+ }
450
+
451
+ openPanel() {
452
+ if (this.panel.isConnected) return;
453
+ this.renderPanel();
454
+ this.dom.append(this.panel);
455
+ this.edit.setAttribute("aria-expanded", "true");
456
+ this.panel.focus();
457
+ }
458
+
459
+ closePanel(refocus = false) {
460
+ if (!this.panel.isConnected) return;
461
+ this.unsaved?.dispose();
462
+ this.unsaved = null;
463
+ this.titleInput = null;
464
+ this.panel.remove();
465
+ this.edit.setAttribute("aria-expanded", "false");
466
+ if (refocus) this.edit.focus();
467
+ }
468
+
469
+ panelClick(event) {
470
+ const button = event.target.closest?.("button");
471
+ if (!button) return;
472
+ if (button.dataset.action === "done") {
473
+ this.commitTitle();
474
+ this.closePanel(true);
475
+ }
476
+ else if (button.dataset.action === "remove") this.removeChart();
477
+ else if (button.dataset.kind) this.setAttrs({ kind: button.dataset.kind });
478
+ }
479
+
480
+ panelChange(event) {
481
+ const control = event.target;
482
+ if (!control?.dataset) return;
483
+ if (control.dataset.field === "source") this.switchSource(control.value);
484
+ else if (control.dataset.field === "labels") this.setAttrs({ x: control.value, series: this.node.attrs.series.filter((id) => id !== control.value) });
485
+ else if (control.dataset.field === "highlight") this.setAttrs({ highlight: control.value || null });
486
+ else if (control.dataset.field === "series") {
487
+ const series = this.node.attrs.series.filter((id) => id !== control.dataset.col);
488
+ this.setAttrs({ series: control.checked ? [...series, control.dataset.col] : series });
489
+ }
490
+ }
491
+
492
+ switchSource(id) {
493
+ const table = sourceTable(this.view.state.doc, id);
494
+ const defaults = table ? defaultColumns(table) : null;
495
+ if (!defaults) return;
496
+ this.setAttrs({ source: id, x: defaults.x, series: defaults.series });
497
+ }
498
+
499
+ commitTitle() {
500
+ const input = this.titleInput;
501
+ if (!input || input.value === (this.node.attrs.title ?? "")) return;
502
+ this.setAttrs({ title: input.value });
503
+ }
504
+
505
+ goToSource() {
506
+ const doc = this.view.state.doc;
507
+ const pos = tableOffset(doc, this.node.attrs.source);
508
+ const table = pos === null ? null : doc.nodeAt(pos);
509
+ if (!table?.childCount || !table.child(0).childCount) return;
510
+ const at = pos + 3;
511
+ this.view.dispatch(this.view.state.tr.setSelection(TextSelection.create(doc, at)).scrollIntoView());
512
+ this.view.focus?.();
513
+ }
514
+
515
+ removeChart() {
516
+ const pos = this.getPos();
517
+ if (pos == null) return;
518
+ const node = this.view.state.doc.nodeAt(pos);
519
+ if (node?.type !== schema.nodes.chart) return;
520
+ this.closePanel();
521
+ const tr = this.view.state.tr.delete(pos, pos + node.nodeSize);
522
+ tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(pos, tr.doc.content.size)), -1));
523
+ this.view.dispatch(asStep(tr).scrollIntoView());
193
524
  }
194
525
 
195
526
  update(node) {
@@ -197,13 +528,21 @@ export class ChartView {
197
528
  const changed = !node.sameMarkup(this.node);
198
529
  this.node = node;
199
530
  this.dom.dataset.id = node.attrs.id ?? "";
200
- if (changed) { this.source = sourceTable(this.view.state.doc, node.attrs.source); this.schedule(); }
531
+ if (changed) {
532
+ this.source = sourceTable(this.view.state.doc, node.attrs.source);
533
+ this.renderHead();
534
+ this.schedule();
535
+ }
201
536
  return true;
202
537
  }
203
538
 
204
- stopEvent(event) { return !!event.target.closest?.(".page-chart-settings, .page-chart-title-input"); }
539
+ stopEvent(event) { return !!event.target.closest?.(".page-chart-head, .page-chart-panel"); }
205
540
 
206
541
  destroy() {
542
+ this.unsaved?.dispose();
543
+ this.unsaved = null;
544
+ this.doc.removeEventListener("mousedown", this.outside, true);
545
+ this.doc.removeEventListener("keydown", this.escape, true);
207
546
  views.get(this.view)?.delete(this);
208
547
  if (this.frame !== null) {
209
548
  const win = this.dom.ownerDocument.defaultView;
@@ -213,8 +552,8 @@ export class ChartView {
213
552
  }
214
553
  }
215
554
 
216
- export function chartPlugin() {
217
- return new state.Plugin({
218
- view: () => ({ update(view) { for (const chart of views.get(view) ?? []) chart.refresh(); } }),
219
- });
555
+ // The hook the chart view part registers for its editor view: every change can
556
+ // have moved a chart's source table.
557
+ export function refreshCharts(view) {
558
+ for (const chart of views.get(view) ?? []) chart.refresh();
220
559
  }