@panphora/clayjs 1.17.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 (467) 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 +72474 -62090
  11. package/dist/m/attrs/{save-freeze-MYNVXREN.js → save-freeze-LWGF6XUN.js} +1 -1
  12. package/dist/m/c-2GRUM77Q.js +30 -0
  13. package/dist/m/c-2KIEGNC4.js +1 -0
  14. package/dist/m/{c-AMSNPOCX.js → c-2KL3BD52.js} +1 -1
  15. package/dist/m/c-2ZZBC45O.js +1 -0
  16. package/dist/m/c-372N6H3B.js +1 -0
  17. package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
  18. package/dist/m/c-3LDGIBMN.js +1 -0
  19. package/dist/m/c-3O7OBDCM.js +1 -0
  20. package/dist/m/c-4ASQPRQL.js +1 -0
  21. package/dist/m/c-4TXNLX4K.js +6 -0
  22. package/dist/m/c-4YU3BR4D.js +6 -0
  23. package/dist/m/c-5734MF2P.js +1 -0
  24. package/dist/m/c-57XDY6HC.js +1 -0
  25. package/dist/m/{c-KAU6E43D.js → c-5B4YBZXC.js} +1 -1
  26. package/dist/m/c-5ONTYIUI.js +1 -0
  27. package/dist/m/c-5OOVFCJM.js +1 -0
  28. package/dist/m/{c-3XIRD4NU.js → c-5T6NZHNM.js} +1 -1
  29. package/dist/m/{c-74MGYWZF.js → c-5ZQV7KC4.js} +1 -1
  30. package/dist/m/{c-7OLLJPWP.js → c-6DB2OLRJ.js} +1 -1
  31. package/dist/m/c-6DNRCPNM.js +1 -0
  32. package/dist/m/c-6UGWFMQC.js +2 -0
  33. package/dist/m/c-7K4SCQB7.js +1 -0
  34. package/dist/m/c-7MG5RU63.js +1 -0
  35. package/dist/m/c-7PRJCA3E.js +1 -0
  36. package/dist/m/c-A6L3FMUS.js +1 -0
  37. package/dist/m/c-AMU3BPPR.js +4 -0
  38. package/dist/m/{c-Y5HBBIQ4.js → c-AMYEFA2E.js} +1 -1
  39. package/dist/m/c-AQYCG7QW.js +1 -0
  40. package/dist/m/c-ATKAOH23.js +1 -0
  41. package/dist/m/c-B5CS55WR.js +1 -0
  42. package/dist/m/c-B5UJQT7W.js +1 -0
  43. package/dist/m/c-B7J4K2AE.js +3 -0
  44. package/dist/m/c-BHZYSHW4.js +1 -0
  45. package/dist/m/{c-VHM5XQHK.js → c-BIJALSRD.js} +1 -1
  46. package/dist/m/c-BJ7YQ5QZ.js +1 -0
  47. package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
  48. package/dist/m/{c-VE6ZMYMC.js → c-BUAYFEV5.js} +1 -1
  49. package/dist/m/c-C6IPRG34.js +1 -0
  50. package/dist/m/{c-KU3ZFADW.js → c-CAS3NND4.js} +1 -1
  51. package/dist/m/{c-PS5ARVWR.js → c-CCQ25GFY.js} +1 -1
  52. package/dist/m/c-CJFDLD43.js +1 -0
  53. package/dist/m/c-COPTL5WB.js +1 -0
  54. package/dist/m/c-D3E4WJ4M.js +166 -0
  55. package/dist/m/c-D5IKEKXW.js +7 -0
  56. package/dist/m/c-DHBRA6DL.js +1 -0
  57. package/dist/m/c-DKLS74XB.js +72 -0
  58. package/dist/m/c-DVYGFTSK.js +3 -0
  59. package/dist/m/c-E3ZXARWA.js +1 -0
  60. package/dist/m/c-EK63G3CG.js +1 -0
  61. package/dist/m/c-ERQSNR6K.js +13 -0
  62. package/dist/m/c-EVYAISP3.js +1 -0
  63. package/dist/m/c-EXKHPOVC.js +3 -0
  64. package/dist/m/{c-QJ4OM3G6.js → c-FWKHM4E7.js} +1 -1
  65. package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
  66. package/dist/m/c-GNDL3TKB.js +1 -0
  67. package/dist/m/c-GRWMD6O5.js +1 -0
  68. package/dist/m/c-GRXHHFPO.js +1 -0
  69. package/dist/m/c-HG2C77BM.js +4 -0
  70. package/dist/m/c-HHGJBU4D.js +1 -0
  71. package/dist/m/c-HMV6P4RX.js +1 -0
  72. package/dist/m/c-HMYWIHFG.js +1 -0
  73. package/dist/m/c-HT2DKBV2.js +1 -0
  74. package/dist/m/c-I2SCCUEW.js +1 -0
  75. package/dist/m/{c-TJ6WOJL3.js → c-ICJYHABF.js} +2 -2
  76. package/dist/m/c-IMZCWYHR.js +7 -0
  77. package/dist/m/c-IVMD3FEM.js +1 -0
  78. package/dist/m/{c-WDC5GVKU.js → c-IVTRVENZ.js} +1 -1
  79. package/dist/m/c-JKZJIUIP.js +1 -0
  80. package/dist/m/{c-UDUUJPFZ.js → c-JNLX3RJB.js} +1 -1
  81. package/dist/m/c-K3ADYNLU.js +1 -0
  82. package/dist/m/c-KGHVFRR3.js +1 -0
  83. package/dist/m/{c-5QWQ7A5L.js → c-KJJPV77R.js} +1 -1
  84. package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
  85. package/dist/m/{c-EZU56NQV.js → c-KP64LT2A.js} +2 -2
  86. package/dist/m/c-KQ7E3PYY.js +1 -0
  87. package/dist/m/c-L3F5ZLIV.js +1 -0
  88. package/dist/m/{c-3RUU4TPX.js → c-L42OLXWM.js} +3 -2
  89. package/dist/m/c-L5VFATT6.js +1 -0
  90. package/dist/m/c-L7JL5TL3.js +3 -0
  91. package/dist/m/c-LKSL4AUJ.js +1 -0
  92. package/dist/m/c-M6FNQCTQ.js +1 -0
  93. package/dist/m/c-MCOOHCSG.js +1 -0
  94. package/dist/m/c-MHI3GYKF.js +1 -0
  95. package/dist/m/c-MWXFLW4J.js +1 -0
  96. package/dist/m/{c-5RJFTLSF.js → c-NEL7HQW3.js} +1 -1
  97. package/dist/m/{c-DCXOXBF3.js → c-NQJRVJ56.js} +1 -1
  98. package/dist/m/c-NWX3MFT4.js +1 -0
  99. package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
  100. package/dist/m/c-OKXI52N7.js +1 -0
  101. package/dist/m/{c-R3L7ZJGB.js → c-OPLUZR3A.js} +1 -1
  102. package/dist/m/{c-GSFIGKOP.js → c-OTMKN2OO.js} +1 -1
  103. package/dist/m/{c-6NC5I2TV.js → c-OYRZ7XG2.js} +1 -1
  104. package/dist/m/{c-EVCQ3BKA.js → c-P3OFYUMA.js} +1 -1
  105. package/dist/m/c-PLN76GF3.js +6 -0
  106. package/dist/m/c-PLP4JKFC.js +1 -0
  107. package/dist/m/c-Q26MME6N.js +1 -0
  108. package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
  109. package/dist/m/{c-FRLHSY7E.js → c-QIKT6SKM.js} +1 -1
  110. package/dist/m/c-QIXT3Q7C.js +28 -0
  111. package/dist/m/c-QO4HNGCQ.js +1 -0
  112. package/dist/m/{c-JU3NK25H.js → c-R27DWQKX.js} +1 -1
  113. package/dist/m/{c-RSMOODDU.js → c-R2XUODVY.js} +1 -1
  114. package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
  115. package/dist/m/c-RND3G2S2.js +1 -0
  116. package/dist/m/{c-Q622NS2Y.js → c-S2U6NJU4.js} +1 -1
  117. package/dist/m/c-SPZPGD3R.js +1 -0
  118. package/dist/m/c-SW4YEG2D.js +8 -0
  119. package/dist/m/c-T637T7SN.js +1 -0
  120. package/dist/m/c-TJAXGIRV.js +1 -0
  121. package/dist/m/{c-RYUXUUBX.js → c-TNEM3RNM.js} +1 -1
  122. package/dist/m/c-TTB5EK4S.js +32 -0
  123. package/dist/m/c-U7SHNWDH.js +1 -0
  124. package/dist/m/c-UDHIVLYH.js +20 -0
  125. package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
  126. package/dist/m/{c-QICP2YR5.js → c-UORXLSD3.js} +1 -1
  127. package/dist/m/c-UPD33ZPE.js +1 -0
  128. package/dist/m/c-URSMSFCB.js +1 -0
  129. package/dist/m/c-UY4QX42B.js +1 -0
  130. package/dist/m/c-V44KWI2L.js +4 -0
  131. package/dist/m/c-VILENZTD.js +1 -0
  132. package/dist/m/{c-HDDEEL77.js → c-VJYEVRYK.js} +1 -1
  133. package/dist/m/{c-SMBZG5X4.js → c-VR2BJ4AN.js} +1 -1
  134. package/dist/m/c-W4JCVLAB.js +1 -0
  135. package/dist/m/c-W6DQSHXI.js +135 -0
  136. package/dist/m/c-WBGWGOM7.js +1 -0
  137. package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
  138. package/dist/m/c-WGW764WX.js +1 -0
  139. package/dist/m/c-WMRJKQ2V.js +5 -0
  140. package/dist/m/c-WNTYKCDG.js +1 -0
  141. package/dist/m/c-WQWIRWO3.js +72 -0
  142. package/dist/m/c-X5LNHET5.js +1 -0
  143. package/dist/m/{c-I2ZATQCD.js → c-X5TSXZYJ.js} +2 -2
  144. package/dist/m/c-XGH4GS2T.js +1 -0
  145. package/dist/m/c-Y6F2FORL.js +1 -0
  146. package/dist/m/c-YBKHUAK7.js +10 -0
  147. package/dist/m/c-YBSS55KU.js +20 -0
  148. package/dist/m/c-YCLU4UNT.js +2 -0
  149. package/dist/m/c-YVZ2L7CE.js +1 -0
  150. package/dist/m/c-ZK4GIZH2.js +1 -0
  151. package/dist/m/c-ZRXJD4G6.js +1 -0
  152. package/dist/m/c-ZWBI4HYK.js +1 -0
  153. package/dist/m/core/{admin-attrs-QHOKPJ7W.js → admin-attrs-YY7T5FGU.js} +1 -1
  154. package/dist/m/core/autosave-S6OMNDCK.js +1 -0
  155. package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
  156. package/dist/m/core/{host-permission-FNXVEFNE.js → host-permission-6EVJGNPL.js} +1 -1
  157. package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
  158. package/dist/m/core/{persist-JFYRHMRJ.js → persist-UKXV5KLA.js} +1 -1
  159. package/dist/m/core/recovery-DRFZRJED.js +1 -0
  160. package/dist/m/core/save-LYY7K6U7.js +1 -0
  161. package/dist/m/core/save-core-MKRPSEEO.js +1 -0
  162. package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
  163. package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
  164. package/dist/m/internals/{index-PVXGPUDA.js → index-TGLQINN3.js} +1 -1
  165. package/dist/m/loader-3BBNZZGM.js +1 -0
  166. package/dist/m/manifest-F449FF98.js +1 -0
  167. package/dist/m/manifest-edit-4396A009.js +1 -0
  168. package/dist/m/manifest.json +2111 -1573
  169. package/dist/m/page/{editor-NNJA4OKD.js → editor-6LRB3GE4.js} +123 -21
  170. package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
  171. package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
  172. package/dist/m/plugins/{demo-RM5IR6AO.js → demo-OYO5GCAO.js} +1 -1
  173. package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
  174. package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
  175. package/dist/m/plugins/live-AD4H6XSL.js +1 -0
  176. package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
  177. package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
  178. package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
  179. package/dist/m/plugins/{people-SGPJKANK.js → people-JCHVPF7P.js} +1 -1
  180. package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
  181. package/dist/m/plugins/{richclay-NRQDSWX6.js → richclay-KLGETK77.js} +2 -2
  182. package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
  183. package/dist/m/plugins/source-TJXOIOYY.js +1 -0
  184. package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
  185. package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
  186. package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
  187. package/dist/m/plugins/{upload-WLYKK6TL.js → upload-NHGJXZK2.js} +1 -1
  188. package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
  189. package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
  190. package/dist/m/plugins/{wire-4N6GYAF6.js → wire-VBWXEXU2.js} +1 -1
  191. package/dist/m/reader-core-4W2KYEWQ.js +77 -0
  192. package/dist/m/route/route-62CHXL5B.js +1 -0
  193. package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
  194. package/dist/m/ui/index-IVWN2QC4.js +1 -0
  195. package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
  196. package/dist/offline-manifest.json +1367 -930
  197. package/dist/page/themes/light-1.css +8 -7
  198. package/dist/server/core.cjs +1440 -288
  199. package/dist/server/offline-runtime.cjs +63 -63
  200. package/dist/server/offline-runtime.mjs +63 -63
  201. package/dist/server/page-compaction-worker.cjs +3710 -0
  202. package/dist/server/rooms.mjs +5 -0
  203. package/docs/guide.md +653 -101
  204. package/entries/sap.js +5 -5
  205. package/package.json +1 -1
  206. package/src/core/admin-contenteditable.js +5 -2
  207. package/src/core/admin-inputs.js +1 -0
  208. package/src/core/admin-onclick.js +2 -1
  209. package/src/core/conflict-notice-ui.js +4 -7
  210. package/src/core/download.js +2 -2
  211. package/src/core/host-meta.js +38 -0
  212. package/src/core/is-edit-mode.js +17 -1
  213. package/src/core/mode-key.js +125 -19
  214. package/src/core/mode-support.js +5 -0
  215. package/src/core/mode-transition.js +20 -6
  216. package/src/core/preserve-authored.js +4 -4
  217. package/src/core/recovery.js +121 -33
  218. package/src/core/save-accepted.js +33 -0
  219. package/src/core/save-core.js +106 -31
  220. package/src/core/save-job.js +108 -0
  221. package/src/core/save-prepare.js +53 -0
  222. package/src/core/save-size-notice.js +3 -1
  223. package/src/core/save-slices.js +15 -0
  224. package/src/core/save.js +557 -127
  225. package/src/core/serialize-steps.js +109 -0
  226. package/src/core/snapshot.js +170 -39
  227. package/src/core/source-map.js +83 -38
  228. package/src/core/stale-host-notice.js +1 -0
  229. package/src/core/upgrade-run.js +59 -8
  230. package/src/dom/active-element.js +5 -0
  231. package/src/lib/assets.js +1 -0
  232. package/src/lib/slice.js +65 -0
  233. package/src/lib/uimotion.js +774 -0
  234. package/src/lib/unsaved-state.js +2 -2
  235. package/src/live/fast-lane.js +10 -3
  236. package/src/live/local-lane.js +202 -11
  237. package/src/live/ops.js +16 -0
  238. package/src/live/page-compaction.js +162 -0
  239. package/src/live/server-lane.js +1 -0
  240. package/src/live/store.js +11 -3
  241. package/src/loader-logic.js +3 -1
  242. package/src/loader.js +14 -4
  243. package/src/page/adopt-boot.js +2 -1
  244. package/src/page/awareness.js +33 -2
  245. package/src/page/blocks.js +89 -27
  246. package/src/page/boot-pack.js +1 -1
  247. package/src/page/chart-tables.js +21 -3
  248. package/src/page/chart.js +406 -63
  249. package/src/page/checklist.js +10 -135
  250. package/src/page/checkpoint-proof.js +7 -1
  251. package/src/page/chrome.js +250 -51
  252. package/src/page/clipboard-shape.js +126 -0
  253. package/src/page/drag.js +33 -3
  254. package/src/page/editor.js +136 -25
  255. package/src/page/export-assets.js +357 -0
  256. package/src/page/export-html.js +209 -0
  257. package/src/page/export-markdown.js +361 -0
  258. package/src/page/features/checklist.js +428 -0
  259. package/src/page/features/export-html.js +189 -0
  260. package/src/page/features/export-markdown.js +169 -0
  261. package/src/page/features/import-markdown.js +349 -0
  262. package/src/page/features/media.js +61 -5
  263. package/src/page/features/registry.js +18 -0
  264. package/src/page/features/table.js +373 -83
  265. package/src/page/features/title-cover.js +331 -0
  266. package/src/page/features/title-icon.js +213 -0
  267. package/src/page/features/toc.js +87 -0
  268. package/src/page/format-command.js +25 -1
  269. package/src/page/format-gate.js +68 -18
  270. package/src/page/format-host.js +17 -0
  271. package/src/page/format-native-commands.js +21 -5
  272. package/src/page/image-actions.js +64 -0
  273. package/src/page/import-markdown.js +262 -0
  274. package/src/page/items.js +135 -17
  275. package/src/page/markdown.js +6 -2
  276. package/src/page/media.js +35 -119
  277. package/src/page/placeholder.js +14 -1
  278. package/src/page/projection.js +98 -10
  279. package/src/page/recover.js +10 -12
  280. package/src/page/recovery-ui.js +464 -0
  281. package/src/page/rich/chart.js +63 -7
  282. package/src/page/save-projection.js +28 -0
  283. package/src/page/save-states.js +13 -3
  284. package/src/page/schema.js +19 -3
  285. package/src/page/table-paste.js +3 -1
  286. package/src/page/table-style.js +4 -4
  287. package/src/page/table.js +44 -2
  288. package/src/page/themes/light-1.css +8 -7
  289. package/src/page/themes/light-1.source.css +8 -7
  290. package/src/page/toc-link.js +31 -0
  291. package/src/page/toc.js +5 -0
  292. package/src/page/view-hooks.js +38 -12
  293. package/src/page/view.js +13 -0
  294. package/src/page/ybind/binding.js +88 -5
  295. package/src/page/ybind/bridge.js +187 -20
  296. package/src/page/ybind/containment.js +162 -0
  297. package/src/page/ybind/doc.js +17 -9
  298. package/src/page/ybind/file-debt.js +63 -0
  299. package/src/page/ybind/layout.js +7 -2
  300. package/src/page/ybind/marks.js +1 -1
  301. package/src/page/ybind/moves.js +5 -4
  302. package/src/page/ybind/origins.js +106 -4
  303. package/src/page/ybind/parts.js +8 -1
  304. package/src/page/ybind/provider.js +113 -17
  305. package/src/page/ybind/recovery-proof.js +32 -9
  306. package/src/page/ybind/recovery-replay.js +558 -15
  307. package/src/page/ybind/recovery.js +174 -45
  308. package/src/page/ybind/table.js +67 -11
  309. package/src/page/ybind/walk-delta.js +258 -0
  310. package/src/page/ybind/walk.js +54 -29
  311. package/src/page/ybind/writer.js +1 -1
  312. package/src/plugins/ai-edit.js +13 -9
  313. package/src/plugins/indicator-chip.js +91 -0
  314. package/src/plugins/indicator.js +13 -80
  315. package/src/plugins/live.js +89 -14
  316. package/src/plugins/menu/actions.js +196 -0
  317. package/src/plugins/menu/people.js +279 -0
  318. package/src/plugins/menu/placement.js +121 -0
  319. package/src/plugins/menu/preferences.js +9 -0
  320. package/src/plugins/menu/seo.js +316 -397
  321. package/src/plugins/menu/style.js +171 -0
  322. package/src/plugins/menu/variants.js +296 -0
  323. package/src/plugins/menu.js +186 -183
  324. package/src/plugins/page-view.js +3 -2
  325. package/src/plugins/page.js +249 -44
  326. package/src/plugins/sortable.js +8 -1
  327. package/src/plugins/source.js +6 -0
  328. package/src/plugins/upgrade.js +51 -17
  329. package/src/plugins/upload-ui.js +12 -17
  330. package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
  331. package/src/plugins/visitor-drafts/intro-style.js +7 -2
  332. package/src/plugins/visitor-drafts/intro.js +5 -3
  333. package/src/plugins/visitor-drafts/owner-chip.js +8 -12
  334. package/src/plugins/visitor-drafts/prompt.js +11 -9
  335. package/src/plugins/visitor-drafts/source.js +4 -3
  336. package/src/plugins/visitor-drafts/store.js +138 -9
  337. package/src/plugins/visitor-drafts/ui-memory.js +2 -2
  338. package/src/plugins/visitor-drafts-owner.js +4 -6
  339. package/src/plugins/visitor-drafts.js +5 -3
  340. package/src/recovery/store.js +1 -1
  341. package/src/route/route.js +4 -0
  342. package/src/server/rooms/index.js +5 -1
  343. package/src/server/rooms/live-data-rooms.js +302 -50
  344. package/src/server/rooms/page-compaction-worker.js +12 -0
  345. package/src/server/rooms/page-compactor.js +92 -0
  346. package/src/server/rooms/page-room-conformance.js +461 -0
  347. package/src/source/parse-steps.js +94 -0
  348. package/src/source/session.js +233 -57
  349. package/src/sync/editing-sender.js +12 -0
  350. package/src/sync/live-sync-edit.js +22 -2
  351. package/src/sync/live-sync.js +150 -23
  352. package/src/sync/page-relay.js +52 -0
  353. package/src/sync/presence-store.js +44 -4
  354. package/src/sync/presence.js +6 -3
  355. package/src/sync/saved-stream.js +11 -6
  356. package/src/ui/bevel-controls.js +1 -0
  357. package/src/ui/bevel-dialog.js +5 -2
  358. package/src/ui/editor-ui.js +77 -0
  359. package/src/ui/page-colour.js +281 -0
  360. package/src/ui/toast.js +16 -7
  361. package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
  362. package/src/vendor/hyper-undo.vendor.js +1 -1
  363. package/src/vendor/hypercms.vendor.js +9 -9
  364. package/src/vendor/parse5.vendor.js +2 -2
  365. package/src/vendor/sap-core.vendor.js +5 -5
  366. package/dist/m/c-2SPVQSHC.js +0 -1
  367. package/dist/m/c-336SGU5W.js +0 -1
  368. package/dist/m/c-3H6BZDNJ.js +0 -6
  369. package/dist/m/c-4DFEJ2PO.js +0 -3
  370. package/dist/m/c-56QRZTZH.js +0 -1
  371. package/dist/m/c-5J4E7O4Y.js +0 -4
  372. package/dist/m/c-5LKDWQTL.js +0 -19
  373. package/dist/m/c-5V3MOVJT.js +0 -1
  374. package/dist/m/c-6AZOGAPE.js +0 -1
  375. package/dist/m/c-6CRUUXOX.js +0 -5
  376. package/dist/m/c-6HMB24EV.js +0 -1
  377. package/dist/m/c-6KEVEQ7L.js +0 -1
  378. package/dist/m/c-A3E3NIMC.js +0 -1
  379. package/dist/m/c-BQ6LOBEE.js +0 -1
  380. package/dist/m/c-BTDI4Y23.js +0 -1
  381. package/dist/m/c-CJ3A7ZKE.js +0 -1
  382. package/dist/m/c-CQHPGKX2.js +0 -1
  383. package/dist/m/c-DFS4XZJV.js +0 -2
  384. package/dist/m/c-DNENWY4O.js +0 -1
  385. package/dist/m/c-E4LIR5X5.js +0 -1
  386. package/dist/m/c-FDNHHZY5.js +0 -1
  387. package/dist/m/c-FROJUZMV.js +0 -1
  388. package/dist/m/c-G2JPKCTP.js +0 -1
  389. package/dist/m/c-GB6TU7WV.js +0 -1
  390. package/dist/m/c-GFJJEBTU.js +0 -1
  391. package/dist/m/c-GJAEGGXY.js +0 -1
  392. package/dist/m/c-GOWBDM6J.js +0 -1
  393. package/dist/m/c-HFKUVACH.js +0 -1
  394. package/dist/m/c-HHDFCNHV.js +0 -1
  395. package/dist/m/c-IEM4NTCS.js +0 -1
  396. package/dist/m/c-JDCX26UP.js +0 -25
  397. package/dist/m/c-KGVGROZT.js +0 -1
  398. package/dist/m/c-KQ6TYJAP.js +0 -135
  399. package/dist/m/c-LFPHCXY3.js +0 -1
  400. package/dist/m/c-LRPUO2MX.js +0 -1
  401. package/dist/m/c-LWLBHLVC.js +0 -3
  402. package/dist/m/c-M2L47S2X.js +0 -7
  403. package/dist/m/c-MFP2ZWD4.js +0 -1
  404. package/dist/m/c-MKSQ4Y57.js +0 -1
  405. package/dist/m/c-NUFFJVVJ.js +0 -1
  406. package/dist/m/c-NUHSPT5P.js +0 -1
  407. package/dist/m/c-O72HVYSA.js +0 -1
  408. package/dist/m/c-OIBUTDXI.js +0 -1
  409. package/dist/m/c-OW5QIGG3.js +0 -1
  410. package/dist/m/c-PGRCK73F.js +0 -1
  411. package/dist/m/c-Q7NQD65V.js +0 -1
  412. package/dist/m/c-QH63LQOS.js +0 -1
  413. package/dist/m/c-R7IWVINQ.js +0 -1
  414. package/dist/m/c-RM7J7BV2.js +0 -1
  415. package/dist/m/c-S4ZEH46B.js +0 -9
  416. package/dist/m/c-SDAXDM2D.js +0 -1
  417. package/dist/m/c-SMGU7RT3.js +0 -235
  418. package/dist/m/c-SPIORRYS.js +0 -1
  419. package/dist/m/c-SWLRT66R.js +0 -1
  420. package/dist/m/c-TCJTVAFN.js +0 -1
  421. package/dist/m/c-TQEMVJCS.js +0 -1
  422. package/dist/m/c-TYLXX4NF.js +0 -21
  423. package/dist/m/c-U62OB4BT.js +0 -1
  424. package/dist/m/c-U6YWUAR3.js +0 -8
  425. package/dist/m/c-UDCSU5G5.js +0 -2
  426. package/dist/m/c-VI5RMSVY.js +0 -1
  427. package/dist/m/c-VRZN56XY.js +0 -1
  428. package/dist/m/c-W5MCHEGA.js +0 -1
  429. package/dist/m/c-WNYBLIOZ.js +0 -1
  430. package/dist/m/c-WWXIDH4O.js +0 -28
  431. package/dist/m/c-XCNTK76U.js +0 -1
  432. package/dist/m/c-XMS6DPBR.js +0 -1
  433. package/dist/m/c-ZUCOKOHT.js +0 -1
  434. package/dist/m/c-ZVPVXTJF.js +0 -4
  435. package/dist/m/c-ZVRKXKM7.js +0 -1
  436. package/dist/m/core/autosave-HRYGUWV6.js +0 -1
  437. package/dist/m/core/conflict-notice-6MROJN2L.js +0 -1
  438. package/dist/m/core/is-edit-mode-KTWA3O5N.js +0 -1
  439. package/dist/m/core/recovery-FLTCC2OC.js +0 -1
  440. package/dist/m/core/save-HQZ4S667.js +0 -1
  441. package/dist/m/core/save-core-KGQWFUNH.js +0 -1
  442. package/dist/m/core/snapshot-EGQZNT45.js +0 -1
  443. package/dist/m/core/unsaved-warning-IDJLD66H.js +0 -1
  444. package/dist/m/loader-S4I7GKXR.js +0 -1
  445. package/dist/m/manifest-4C419D67.js +0 -1
  446. package/dist/m/manifest-edit-B077F288.js +0 -1
  447. package/dist/m/plugins/ai-edit-QN4PMMNL.js +0 -6
  448. package/dist/m/plugins/indicator-URDBBOQO.js +0 -1
  449. package/dist/m/plugins/live-HFWD4OIH.js +0 -1
  450. package/dist/m/plugins/menu-3HIEQSI6.js +0 -138
  451. package/dist/m/plugins/page-JOKB6XPR.js +0 -2
  452. package/dist/m/plugins/page-view-NXOJHNW6.js +0 -1
  453. package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
  454. package/dist/m/plugins/sortable-GWT4PWDV.js +0 -1
  455. package/dist/m/plugins/source-4ZRSAIYM.js +0 -1
  456. package/dist/m/plugins/undo-VDA5P7FH.js +0 -1
  457. package/dist/m/plugins/upgrade-W7FG5VFG.js +0 -1
  458. package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +0 -21
  459. package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +0 -1
  460. package/dist/m/reader-core-DXYCURFF.js +0 -68
  461. package/dist/m/route/route-KOCZSEGN.js +0 -1
  462. package/dist/m/sync/live-sync-J7VMYTKS.js +0 -1
  463. package/dist/m/ui/index-3JLVI27O.js +0 -1
  464. package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
  465. /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
  466. /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
  467. /package/dist/m/plugins/{richclay-NRQDSWX6.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
package/src/page/chart.js CHANGED
@@ -1,13 +1,22 @@
1
1
  import { schema } from "./schema.js";
2
2
  import { blockJson, keyBetween, newId, orderedIds } from "./model.js";
3
- import { chartTables } from "./chart-tables.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,7 +55,11 @@ 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
  }
@@ -65,9 +79,14 @@ function placeChart(view, index, table, remove) {
65
79
  const next = tr.doc.maybeChild(index + 1);
66
80
  const pos = replace ? node.attrs.pos : keyBetween(node.attrs.pos, next?.attrs.pos ?? null);
67
81
  const attrs = chartAttrs(table, pos, node.attrs.parent ?? null);
68
- 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
+ }
69
87
  const chart = schema.nodes.chart.create(attrs);
70
88
  tr = replace ? tr.replaceWith(at, at + node.nodeSize, chart) : tr.insert(at + node.nodeSize, chart);
89
+ openOnMount.add(attrs.id);
71
90
  view.dispatch(tr.scrollIntoView());
72
91
  return true;
73
92
  }
@@ -76,49 +95,151 @@ export const tableLabel = (table) => orderedIds(table.attrs.cols).map((id) => ta
76
95
 
77
96
  export function chartFromSlash(view, index, remove = null) {
78
97
  const tables = chartTables(view.state.doc);
79
- 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
+ }
80
103
  if (tables.length === 1) return placeChart(view, index, tables[0], remove);
81
- const anchor = view.nodeDOM(view.state.doc.resolve(view.state.selection.head).before(1)) ?? view.dom;
82
- return openPageMenu(view, anchor, tables.map((table, i) => ({
83
- label: `Table ${i + 1}`,
84
- 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)}`,
85
109
  icon: "▦",
86
110
  run: () => placeChart(view, index, table, remove),
87
111
  })));
88
112
  }
89
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
+
90
163
  export class ChartView {
91
164
  constructor(node, view, getPos) {
92
165
  this.node = node;
93
166
  this.view = view;
94
167
  this.getPos = getPos;
95
168
  this.source = undefined;
169
+ this.tables = null;
96
170
  this.frame = null;
171
+ this.unsaved = null;
172
+ this.titleInput = null;
97
173
  const doc = view.dom.ownerDocument;
174
+ this.doc = doc;
98
175
  this.dom = doc.createElement("figure");
99
176
  this.dom.className = "page-chart";
100
177
  this.dom.dataset.id = node.attrs.id ?? "";
101
178
  this.dom.dataset.type = "chart";
102
- this.plot = doc.createElement("div");
103
- this.plot.className = "page-chart-plot";
104
- this.caption = doc.createElement("figcaption");
105
- this.settings = doc.createElement("button");
106
- this.settings.type = "button";
107
- this.settings.className = "page-chart-settings";
108
- this.settings.textContent = "Chart settings";
109
- this.settings.setAttribute("aria-label", "Chart settings");
110
- this.settings.contentEditable = "false";
111
- this.settings.addEventListener("click", () => this.openSettings());
112
- 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);
113
228
  if (!views.has(view)) views.set(view, new Set());
114
229
  views.get(view).add(this);
115
230
  this.refresh();
231
+ if (openOnMount.delete(node.attrs.id)) this.openSoon();
116
232
  }
117
233
 
118
234
  refresh() {
119
- const source = sourceTable(this.view.state.doc, this.node.attrs.source);
120
- 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;
121
241
  this.source = source;
242
+ this.tables = tables;
122
243
  this.schedule();
123
244
  }
124
245
 
@@ -133,7 +254,70 @@ export class ChartView {
133
254
  const attrs = this.node.attrs;
134
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));
135
256
  this.plot.innerHTML = renderChartSvg(chart, { width: 640, height: 320, narrow: this.dom.getBoundingClientRect().width < 480 });
136
- 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;
137
321
  }
138
322
 
139
323
  setAttrs(changes) {
@@ -144,48 +328,199 @@ export class ChartView {
144
328
  this.view.dispatch(this.view.state.tr.setNodeMarkup(pos, null, { ...node.attrs, ...changes }));
145
329
  }
146
330
 
147
- submenu(items) {
148
- 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;
149
336
  }
150
337
 
151
- openSettings() {
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);
357
+ }
358
+
359
+ renderPanel() {
360
+ const doc = this.doc;
361
+ this.renderedKey = this.panelKey();
362
+ this.renderedTitle = this.node.attrs.title ?? "";
152
363
  const attrs = this.node.attrs;
153
- const table = tableData(sourceTable(this.view.state.doc, attrs.source));
154
- const cols = table?.cols ?? [];
155
- const rows = table?.rows ?? [];
156
- this.submenu([
157
- { label: "Line", icon: attrs.kind === "line" ? "✓" : "", run: () => this.setAttrs({ kind: "line" }) },
158
- { label: "Bar", icon: attrs.kind === "bar" ? "✓" : "", run: () => this.setAttrs({ kind: "bar" }) },
159
- { 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) }) }))) },
160
- { 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] }) }))) },
161
- { 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 }) }))]) },
162
- { label: "Edit title", focus: false, run: () => this.editTitle() },
163
- ]);
164
- }
165
-
166
- editTitle() {
167
- const input = this.dom.ownerDocument.createElement("input");
168
- input.type = "text";
169
- input.className = "page-chart-title-input";
170
- input.setAttribute("aria-label", "Chart title");
171
- input.value = this.node.attrs.title ?? "";
172
- const finish = (save) => {
173
- input.remove();
174
- this.caption.hidden = false;
175
- if (save) this.setAttrs({ title: input.value });
176
- else this.settings.focus();
177
- };
178
- input.addEventListener("keydown", (event) => {
179
- if (event.key === "Enter" || event.key === "Escape") {
180
- event.preventDefault();
181
- event.stopPropagation();
182
- finish(event.key === "Enter");
183
- }
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();
427
+ });
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 ?? ""),
184
448
  });
185
- this.caption.hidden = true;
186
- this.caption.after(input);
187
- input.focus();
188
- input.select();
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());
189
524
  }
190
525
 
191
526
  update(node) {
@@ -193,13 +528,21 @@ export class ChartView {
193
528
  const changed = !node.sameMarkup(this.node);
194
529
  this.node = node;
195
530
  this.dom.dataset.id = node.attrs.id ?? "";
196
- 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
+ }
197
536
  return true;
198
537
  }
199
538
 
200
- 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"); }
201
540
 
202
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);
203
546
  views.get(this.view)?.delete(this);
204
547
  if (this.frame !== null) {
205
548
  const win = this.dom.ownerDocument.defaultView;