quoin-editor 1.0.0 → 1.0.1

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 (209) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/ai.cjs +135 -94
  3. package/dist/ai.cjs.map +1 -1
  4. package/dist/ai.d.cts +1 -1
  5. package/dist/ai.d.ts +1 -1
  6. package/dist/ai.js +62 -21
  7. package/dist/ai.js.map +1 -1
  8. package/dist/catalogue.cjs +25 -25
  9. package/dist/catalogue.d.cts +6 -6
  10. package/dist/catalogue.d.ts +6 -6
  11. package/dist/catalogue.js +17 -17
  12. package/dist/{chunk-MQ2XQ4MX.js → chunk-26Q4LSIU.js} +6 -6
  13. package/dist/chunk-26Q4LSIU.js.map +1 -0
  14. package/dist/{chunk-AEJXZBVR.cjs → chunk-56C7GCKO.cjs} +110 -103
  15. package/dist/chunk-56C7GCKO.cjs.map +1 -0
  16. package/dist/{chunk-GTTPVJSD.cjs → chunk-5M4QG3ZC.cjs} +262 -245
  17. package/dist/chunk-5M4QG3ZC.cjs.map +1 -0
  18. package/dist/{chunk-2VDK27IF.cjs → chunk-5R3ISX4A.cjs} +312 -335
  19. package/dist/chunk-5R3ISX4A.cjs.map +1 -0
  20. package/dist/{chunk-LCQSG4M6.js → chunk-62WKUAQY.js} +18 -18
  21. package/dist/{chunk-LCQSG4M6.js.map → chunk-62WKUAQY.js.map} +1 -1
  22. package/dist/{chunk-NOKWNK2O.js → chunk-6LMS5J3Y.js} +3 -2
  23. package/dist/{chunk-HELJTLNC.cjs → chunk-6PKVJZQV.cjs} +12 -12
  24. package/dist/chunk-6PKVJZQV.cjs.map +1 -0
  25. package/dist/chunk-6YSGUAVW.js +376 -0
  26. package/dist/chunk-6YSGUAVW.js.map +1 -0
  27. package/dist/{chunk-CDELOHDY.cjs → chunk-75C6PX2L.cjs} +23 -23
  28. package/dist/{chunk-CDELOHDY.cjs.map → chunk-75C6PX2L.cjs.map} +1 -1
  29. package/dist/{chunk-T7SW6OWV.js → chunk-A4IJSJKL.js} +5 -5
  30. package/dist/{chunk-ND5XJVK2.cjs → chunk-BTBHHBVR.cjs} +19 -19
  31. package/dist/{chunk-ND5XJVK2.cjs.map → chunk-BTBHHBVR.cjs.map} +1 -1
  32. package/dist/{chunk-WXMYDXPN.cjs → chunk-CLTHD5GO.cjs} +4 -3
  33. package/dist/chunk-CLTHD5GO.cjs.map +1 -0
  34. package/dist/{chunk-OMOBMXD5.cjs → chunk-EH5KXQXY.cjs} +7 -2
  35. package/dist/chunk-EH5KXQXY.cjs.map +1 -0
  36. package/dist/{chunk-WQHV3MRE.cjs → chunk-ESMAW4NK.cjs} +12 -12
  37. package/dist/{chunk-WQHV3MRE.cjs.map → chunk-ESMAW4NK.cjs.map} +1 -1
  38. package/dist/{chunk-SLVDAHHK.cjs → chunk-EYFCOV3T.cjs} +5 -5
  39. package/dist/{chunk-SLVDAHHK.cjs.map → chunk-EYFCOV3T.cjs.map} +1 -1
  40. package/dist/{chunk-5DNCCN6X.cjs → chunk-F5V3IEEE.cjs} +27 -27
  41. package/dist/{chunk-5DNCCN6X.cjs.map → chunk-F5V3IEEE.cjs.map} +1 -1
  42. package/dist/{chunk-PMWCQSQX.js → chunk-GPIDU3GN.js} +5 -5
  43. package/dist/{chunk-PZ4GT5JO.cjs → chunk-HOIDJEVI.cjs} +316 -300
  44. package/dist/chunk-HOIDJEVI.cjs.map +1 -0
  45. package/dist/{chunk-RDG4VI6E.cjs → chunk-JJ3RASQS.cjs} +90 -90
  46. package/dist/{chunk-RDG4VI6E.cjs.map → chunk-JJ3RASQS.cjs.map} +1 -1
  47. package/dist/{chunk-ZGZEJCRV.js → chunk-LE3F7WIP.js} +23 -46
  48. package/dist/chunk-LE3F7WIP.js.map +1 -0
  49. package/dist/chunk-LE5XMIFJ.cjs +14 -0
  50. package/dist/chunk-LE5XMIFJ.cjs.map +1 -0
  51. package/dist/{chunk-POBM7MGR.js → chunk-LMAEPFTX.js} +2 -2
  52. package/dist/{chunk-ZHOSSWUM.js → chunk-LV6EYPJO.js} +4 -3
  53. package/dist/chunk-LV6EYPJO.js.map +1 -0
  54. package/dist/chunk-MLRAVUFG.cjs +376 -0
  55. package/dist/chunk-MLRAVUFG.cjs.map +1 -0
  56. package/dist/chunk-MQUCCSWG.js +14 -0
  57. package/dist/chunk-MQUCCSWG.js.map +1 -0
  58. package/dist/{chunk-XEBWC7ZL.js → chunk-O4Z2WMFF.js} +7 -2
  59. package/dist/chunk-O4Z2WMFF.js.map +1 -0
  60. package/dist/{chunk-K574XHMM.js → chunk-OCCKQHCY.js} +2 -2
  61. package/dist/{chunk-SG37UBDO.js → chunk-PGSS4IOS.js} +7 -7
  62. package/dist/{chunk-RUDWMKYX.js → chunk-PIWRSX7U.js} +8 -8
  63. package/dist/{chunk-PWUEPDA3.cjs → chunk-PX6LZMH4.cjs} +44 -44
  64. package/dist/{chunk-PWUEPDA3.cjs.map → chunk-PX6LZMH4.cjs.map} +1 -1
  65. package/dist/{chunk-JDPTZUBW.js → chunk-QXG3HWDS.js} +4 -4
  66. package/dist/{chunk-NNDYYEW5.js → chunk-RQW3P3GH.js} +5 -5
  67. package/dist/{chunk-ZS2WA357.js → chunk-SSIRYUCK.js} +144 -127
  68. package/dist/chunk-SSIRYUCK.js.map +1 -0
  69. package/dist/{chunk-47MLKSI2.js → chunk-UBNG5YCG.js} +6 -6
  70. package/dist/{chunk-KXOBZGQO.js → chunk-UCXLRRWY.js} +21 -14
  71. package/dist/chunk-UCXLRRWY.js.map +1 -0
  72. package/dist/{chunk-EL2OEJQC.cjs → chunk-UDAVVEFC.cjs} +56 -56
  73. package/dist/{chunk-EL2OEJQC.cjs.map → chunk-UDAVVEFC.cjs.map} +1 -1
  74. package/dist/{chunk-HJIQT3S5.js → chunk-UPSLDTLT.js} +100 -84
  75. package/dist/chunk-UPSLDTLT.js.map +1 -0
  76. package/dist/{chunk-GLLXHMH2.cjs → chunk-UWNZG2SD.cjs} +3 -3
  77. package/dist/{chunk-GLLXHMH2.cjs.map → chunk-UWNZG2SD.cjs.map} +1 -1
  78. package/dist/{chunk-HZSSEYJS.cjs → chunk-V3KLMSRO.cjs} +30 -30
  79. package/dist/{chunk-HZSSEYJS.cjs.map → chunk-V3KLMSRO.cjs.map} +1 -1
  80. package/dist/{chunk-ZC24WB7Y.cjs → chunk-VACTAZWS.cjs} +14 -14
  81. package/dist/{chunk-ZC24WB7Y.cjs.map → chunk-VACTAZWS.cjs.map} +1 -1
  82. package/dist/{chunk-KAVU6T6J.js → chunk-WMN6566P.js} +5 -5
  83. package/dist/{chunk-2QCRZ7OL.cjs → chunk-XX5FTPJR.cjs} +12 -11
  84. package/dist/{chunk-2QCRZ7OL.cjs.map → chunk-XX5FTPJR.cjs.map} +1 -1
  85. package/dist/comments.cjs +113 -98
  86. package/dist/comments.cjs.map +1 -1
  87. package/dist/comments.d.cts +6 -1
  88. package/dist/comments.d.ts +6 -1
  89. package/dist/comments.js +32 -17
  90. package/dist/comments.js.map +1 -1
  91. package/dist/diff.cjs +43 -40
  92. package/dist/diff.cjs.map +1 -1
  93. package/dist/diff.js +16 -13
  94. package/dist/diff.js.map +1 -1
  95. package/dist/docx.cjs +60 -40
  96. package/dist/docx.cjs.map +1 -1
  97. package/dist/docx.d.cts +6 -0
  98. package/dist/docx.d.ts +6 -0
  99. package/dist/docx.js +32 -12
  100. package/dist/docx.js.map +1 -1
  101. package/dist/embed.cjs +9 -9
  102. package/dist/embed.d.cts +1 -1
  103. package/dist/embed.d.ts +1 -1
  104. package/dist/embed.js +8 -8
  105. package/dist/{emoji-extension-DpQ43HjE.d.ts → emoji-extension-CmPlRssm.d.ts} +1 -1
  106. package/dist/{emoji-extension-C12gDPm6.d.cts → emoji-extension-zJ6F7hW8.d.cts} +1 -1
  107. package/dist/emoji.cjs +8 -8
  108. package/dist/emoji.d.cts +3 -3
  109. package/dist/emoji.d.ts +3 -3
  110. package/dist/emoji.js +7 -7
  111. package/dist/engine.cjs +4 -4
  112. package/dist/engine.d.cts +2 -2
  113. package/dist/engine.d.ts +2 -2
  114. package/dist/engine.js +3 -3
  115. package/dist/{find-extension-_sbZEt3R.d.cts → find-extension-BcRyE9K4.d.cts} +1 -1
  116. package/dist/{find-extension-DKqLLSuP.d.ts → find-extension-CmFehyul.d.ts} +1 -1
  117. package/dist/find.cjs +8 -8
  118. package/dist/find.d.cts +2 -2
  119. package/dist/find.d.ts +2 -2
  120. package/dist/find.js +7 -7
  121. package/dist/headless.cjs +5 -5
  122. package/dist/headless.d.cts +4 -4
  123. package/dist/headless.d.ts +4 -4
  124. package/dist/headless.js +4 -4
  125. package/dist/{index-CgD72kHB.d.cts → index-CFPmCee4.d.cts} +5 -2
  126. package/dist/{index-DNYbTK3w.d.ts → index-DiCGV60d.d.ts} +5 -2
  127. package/dist/index.cjs +7 -7
  128. package/dist/index.d.cts +4 -4
  129. package/dist/index.d.ts +4 -4
  130. package/dist/index.js +6 -6
  131. package/dist/math.cjs +10 -10
  132. package/dist/math.d.cts +1 -1
  133. package/dist/math.d.ts +1 -1
  134. package/dist/math.js +9 -9
  135. package/dist/mention.cjs +10 -10
  136. package/dist/mention.d.cts +1 -1
  137. package/dist/mention.d.ts +1 -1
  138. package/dist/mention.js +9 -9
  139. package/dist/presence.cjs +96 -99
  140. package/dist/presence.cjs.map +1 -1
  141. package/dist/presence.d.cts +2 -2
  142. package/dist/presence.d.ts +2 -2
  143. package/dist/presence.js +64 -67
  144. package/dist/presence.js.map +1 -1
  145. package/dist/{stats-extension-Bly88uio.d.ts → stats-extension-DisOEbbI.d.ts} +1 -1
  146. package/dist/{stats-extension-CJPijvUb.d.cts → stats-extension-Dt1lgP3m.d.cts} +1 -1
  147. package/dist/stats.cjs +9 -9
  148. package/dist/stats.d.cts +3 -3
  149. package/dist/stats.d.ts +3 -3
  150. package/dist/stats.js +8 -8
  151. package/dist/styles.css +1 -1
  152. package/dist/{table-extension-Dzf4k6hI.d.ts → table-extension-BTMJnT7J.d.ts} +1 -1
  153. package/dist/{table-extension-BTLxFy2S.d.cts → table-extension-DxZz3jqp.d.cts} +1 -1
  154. package/dist/table.cjs +8 -8
  155. package/dist/table.d.cts +2 -2
  156. package/dist/table.d.ts +2 -2
  157. package/dist/table.js +7 -7
  158. package/dist/{toc-extension-DWm5alVj.d.ts → toc-extension-CR9U-l6j.d.ts} +1 -1
  159. package/dist/{toc-extension-B3-s6h2x.d.cts → toc-extension-DGHywOBv.d.cts} +1 -1
  160. package/dist/toc.cjs +8 -8
  161. package/dist/toc.d.cts +2 -2
  162. package/dist/toc.d.ts +2 -2
  163. package/dist/toc.js +7 -7
  164. package/dist/{types-a1CRBlEh.d.ts → types-C6_QkXzy.d.ts} +1 -9
  165. package/dist/{types-Cz7XJLaK.d.cts → types-CJ7Gq3GX.d.cts} +1 -9
  166. package/dist/{view-controller-DSHw0ltQ.d.cts → view-controller-BD1KwYfE.d.cts} +6 -19
  167. package/dist/{view-controller-UbpzblGl.d.ts → view-controller-y4cb60ue.d.ts} +6 -19
  168. package/dist/view.cjs +6 -6
  169. package/dist/view.js +3 -3
  170. package/dist/yjs.cjs +234 -396
  171. package/dist/yjs.cjs.map +1 -1
  172. package/dist/yjs.d.cts +3 -3
  173. package/dist/yjs.d.ts +3 -3
  174. package/dist/yjs.js +126 -288
  175. package/dist/yjs.js.map +1 -1
  176. package/package.json +1 -1
  177. package/dist/chunk-2VDK27IF.cjs.map +0 -1
  178. package/dist/chunk-AEJXZBVR.cjs.map +0 -1
  179. package/dist/chunk-E75EHRPA.cjs +0 -175
  180. package/dist/chunk-E75EHRPA.cjs.map +0 -1
  181. package/dist/chunk-GTTPVJSD.cjs.map +0 -1
  182. package/dist/chunk-HELJTLNC.cjs.map +0 -1
  183. package/dist/chunk-HJIQT3S5.js.map +0 -1
  184. package/dist/chunk-JEIFJHEI.js +0 -14
  185. package/dist/chunk-JEIFJHEI.js.map +0 -1
  186. package/dist/chunk-KXOBZGQO.js.map +0 -1
  187. package/dist/chunk-MQ2XQ4MX.js.map +0 -1
  188. package/dist/chunk-OMOBMXD5.cjs.map +0 -1
  189. package/dist/chunk-PZ4GT5JO.cjs.map +0 -1
  190. package/dist/chunk-QAW5NH6S.cjs +0 -14
  191. package/dist/chunk-QAW5NH6S.cjs.map +0 -1
  192. package/dist/chunk-TC4DARU2.js +0 -175
  193. package/dist/chunk-TC4DARU2.js.map +0 -1
  194. package/dist/chunk-WXMYDXPN.cjs.map +0 -1
  195. package/dist/chunk-XEBWC7ZL.js.map +0 -1
  196. package/dist/chunk-ZGZEJCRV.js.map +0 -1
  197. package/dist/chunk-ZHOSSWUM.js.map +0 -1
  198. package/dist/chunk-ZS2WA357.js.map +0 -1
  199. /package/dist/{chunk-NOKWNK2O.js.map → chunk-6LMS5J3Y.js.map} +0 -0
  200. /package/dist/{chunk-T7SW6OWV.js.map → chunk-A4IJSJKL.js.map} +0 -0
  201. /package/dist/{chunk-PMWCQSQX.js.map → chunk-GPIDU3GN.js.map} +0 -0
  202. /package/dist/{chunk-POBM7MGR.js.map → chunk-LMAEPFTX.js.map} +0 -0
  203. /package/dist/{chunk-K574XHMM.js.map → chunk-OCCKQHCY.js.map} +0 -0
  204. /package/dist/{chunk-SG37UBDO.js.map → chunk-PGSS4IOS.js.map} +0 -0
  205. /package/dist/{chunk-RUDWMKYX.js.map → chunk-PIWRSX7U.js.map} +0 -0
  206. /package/dist/{chunk-JDPTZUBW.js.map → chunk-QXG3HWDS.js.map} +0 -0
  207. /package/dist/{chunk-NNDYYEW5.js.map → chunk-RQW3P3GH.js.map} +0 -0
  208. /package/dist/{chunk-47MLKSI2.js.map → chunk-UBNG5YCG.js.map} +0 -0
  209. /package/dist/{chunk-KAVU6T6J.js.map → chunk-WMN6566P.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  Every release of `quoin-editor` is listed here, newest first, in the spirit of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). From 1.0.0 the package follows [semantic versioning](https://semver.org/): a breaking change waits for the next major version. Until then, Quoin was 0.x and a minor version could contain breaking changes; those known at release were marked "Breaking". The 0.x sections are kept as they were released, "Docs" headings included. Moving from 0.4 to 1.0 is covered step by step in [MIGRATION.md](MIGRATION.md). Review finding ids such as F-17 are those of the 0.4.0 review in [#73](https://github.com/AmineSeboui/quoin-editor/issues/73).
4
4
 
5
+ ## 1.0.1 (2026-10-26)
6
+
7
+ ### Added
8
+ - `toDocx` takes an optional `baseUrl`, the address of the page the document comes from: a link or a file whose address is a relative path, such as `/files/a.pdf` or `notes/a.pdf`, is linked to that address read against it, rather than written as its text. A network path (`//host/x`, `\\host\share`, `/\host`) and a fragment such as `#top` stay text. A base that is not an absolute `http` or `https` address is ignored, a user and password in it are left out of the links, and an empty link stays text.
9
+ - `commentsExtension` takes an optional `initialLayout: 'wide' | 'narrow'`, the layout drawn on the server and in the first render before the canvas is measured, so a server-rendered wide canvas no longer shifts by 236 px as it hydrates. The default stays `'narrow'`.
10
+
11
+ ### Changed
12
+ - `Transaction` and `ViewController` keep their private state in native private fields, so a proxy around one of them, such as Vue's `reactive` or Valtio's `proxy`, cannot read it and its methods throw: wrap the object that holds them, not these instances.
13
+ - The transaction that moves the caret to where text is dropped has the `drop` origin, rather than `command`.
14
+ - Docs: chains merge the meta of every part, so a key one part set is seen on the whole chain (extensions page).
15
+ - Docs: two clients can hold a table's alignment as no `align` or as `align: [null, null]`, which the editor and `QuoinDiff` treat as equal; a host comparing saved JSON should normalise `align` (collaboration page).
16
+ - Docs: typing into a block whose region another edit took out can bring along text waiting before it, which then shows one block higher on every client (collaboration page).
17
+ - Docs: edits a headless editor makes while unmounted stay local and are not sent when it connects again (collaboration page).
18
+ - Docs: the `onError` of `yjsCollaboration` is the adapter's own, apart from the canvas's; pass both the same handler (collaboration page).
19
+
20
+ ### Fixed
21
+ - Under a collaboration, `editor.chain().undo().run()` and `editor.chain().redo().run()` undo and redo when the chain holds nothing else, rather than doing nothing. A chain that holds other commands as well still does nothing.
22
+ - A canvas rendered on the server, as with `react-dom/server`, leaves out a code token's `color` that holds what could leave the declaration or load a resource (any of `;` `{` `}` `\` `"` `'` `<` `>` `!`, a `/*`, or `url(`, `image(`, `image-set(` or `expression(`), rather than writing a value such as `red;background-image:url(...)` into the page as it is, and the canvas in the browser leaves out the same values. Other colours, such as `var(--name, #fff)`, `hsl(var(--primary))`, `color-mix()` and `light-dark()`, are drawn by the canvas as in 1.0, and now by `QuoinView` too, which uses the same rule.
23
+ - `toHTML`, `QuoinView` and `QuoinDiff` no longer write an empty accessible name: an empty `taskLabel` names a task with no text `Task` (in `QuoinDiff`, the `emptyTask` label, and `Task` when that is empty too), and a frame whose `frameTitle` is empty is titled with its provider's name, or with its page's address when the provider has no name.
24
+ - `toDocx` writes a link to an `http` or `https` address with no host, such as `http:foo` or `http://?x`, as its text, as it does other targets Word cannot open, rather than as a hyperlink.
25
+ - With presence, a long collaborator name at the end of a line is cut off where the avatars' gutter begins, rather than over the avatars.
26
+ - With presence, a collaborator's caret near the side of a wide table, and its name, are cut off at the table's side as the table's text is, rather than drawn whole past it.
27
+ - With presence, a collaborator's caret, and with `quoin-editor/ai` an answer, in a scroller that sits inside another box that clips what overflows, such as a table in a custom block, is clipped by both, rather than only by the nearest one.
28
+ - With presence, a collaborator's caret in a wide table moves with the table as it scrolls, rather than a frame behind it.
29
+ - With `quoin-editor/ai`, the room kept under a block for its answer no longer adds to the block's bottom padding or to the row gap of a flex or grid canvas under a block another block follows: the answer takes that space first, and the room makes up only the rest. Padding the block paints, with a bottom border or a background, is left to the block.
30
+ - With `quoin-editor/ai`, an answer growing above the part of the page being read no longer pushes that text down in a browser without scroll anchoring, such as Safari, or on a page that turns scroll anchoring off, smooth `scroll-behavior` or not, and in a canvas that scrolls itself.
31
+ - With `quoin-editor/comments`, a thread just resolved or deleted no longer comes back for a moment when your store sets new copies of threads that have not changed before it records the resolution. It comes back once your store, having resolved or removed it, reopens it.
32
+ - With `quoin-editor/comments`, a selection your code sets, as a command or an AI answer does, no longer closes a thread a click opened, "Text removed" included: only the writer's own caret moves close it, an undo, a redo and a drop included.
33
+ - With `quoin-editor/comments`, a thread a click opened closes once the writer moves the caret out of its text, rather than staying open over the lines they go on to type.
34
+ - A canvas drawn inside another canvas's tree owns its own text, menus and popovers: focus in them no longer counts as focus in the outer canvas, which kept the inner canvas's presence, comments and AI from seeing it as their own and let the outer canvas's go on as if focused.
35
+ - With `quoin-editor/yjs`, a row two people add to a table at the same moment, followed by a move of the table and a new column, no longer has one client write cell text at the wrong place, which split it across cells for the others.
36
+ - With `quoin-editor/yjs`, deleting a table while someone adds or removes one of its rows or columns no longer leaves hidden regions in the line after it, over which Enter threw: the cell text shows at the end of that line.
37
+ - Presence on a second editor given a `yjsCollaboration` that already serves one, which stays unconnected, no longer writes to the awareness: unmounting it no longer takes the person and their selection out while the first canvas is still mounted.
38
+ - An editor that disconnects from a `yjsCollaboration` and connects again within a frame, as StrictMode or a quick remount does, keeps what it can undo and redo. A collaboration left disconnected longer lets its undo history go, as in 1.0.
39
+ - `yjsCollaboration` reports to its `onError` a change from the shared document that does not apply to the editor, as the error applying it raised, before the editor takes the document as the shared text reads, rather than recovering in silence.
40
+ - A block that a collaborator's change leaves selected as a whole comes into view with the writer's next change, rather than only once the selection leaves it and comes back.
41
+ - A collaborator's caret move drawn in the same render as a change from your code, such as a command that selects a block below the fold, no longer keeps that change from scrolling it into view. A collaborator's change to the document still scrolls nothing.
42
+ - A table's row or column menu stays open while a collaborator types in its own row or column. It still closes when a change from elsewhere moves that row or column.
43
+ - Two people changing one block's type at the same moment no longer hide the block's text: text left in a region the winning type does not have shows in that type's own region.
44
+ - A caret in a block that a new `blocks` array changes on both sides of it stays on its character, rather than moving by the length of the later change.
45
+
46
+ ### Known limits
47
+ - A task's checkbox and an embed's frame in the canvas still get no name when a host's `task` or `frameTitle` label is empty or blank, and a `taskLabel` or `frameTitle` of spaces only gives `toHTML`, `QuoinView` and `QuoinDiff` a blank name.
48
+ - With presence, a collaborator's caret can stay off its character after a style change that reflows the text without resizing its block, until the next change, scroll or resize.
49
+ - With `quoin-editor/yjs`, a table cell whose region concurrent row and column edits took out cannot be selected or typed into until the table is rewritten, as by adding or removing a row or a column. This is as in 1.0.0.
50
+
5
51
  ## 1.0.0 (2026-10-23)
6
52
 
7
53
  Quoin 1.0 replaces the 0.4 canvas, a card per block with a textarea opened to edit it, by an editor of its own: an engine with no dependency, a view that types into the page in place, and a canvas UI on top. A 0.4 document loads as it is, and the 0.4 `BlockCanvas` props keep their names. [MIGRATION.md](MIGRATION.md) lists what a 0.4 host changes.
package/dist/ai.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  'use client';"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
2
 
3
3
 
4
- var _chunkQAW5NH6Scjs = require('./chunk-QAW5NH6S.cjs');
4
+ var _chunkLE5XMIFJcjs = require('./chunk-LE5XMIFJ.cjs');
5
5
 
6
6
 
7
- var _chunkWXMYDXPNcjs = require('./chunk-WXMYDXPN.cjs');
7
+ var _chunkCLTHD5GOcjs = require('./chunk-CLTHD5GO.cjs');
8
8
 
9
9
 
10
10
 
@@ -17,14 +17,14 @@ var _chunkWXMYDXPNcjs = require('./chunk-WXMYDXPN.cjs');
17
17
 
18
18
 
19
19
 
20
- var _chunk2VDK27IFcjs = require('./chunk-2VDK27IF.cjs');
20
+ var _chunk5R3ISX4Acjs = require('./chunk-5R3ISX4A.cjs');
21
21
 
22
22
 
23
23
 
24
24
 
25
25
 
26
- var _chunkGTTPVJSDcjs = require('./chunk-GTTPVJSD.cjs');
27
- require('./chunk-OMOBMXD5.cjs');
26
+ var _chunk5M4QG3ZCcjs = require('./chunk-5M4QG3ZC.cjs');
27
+ require('./chunk-EH5KXQXY.cjs');
28
28
 
29
29
 
30
30
 
@@ -47,14 +47,15 @@ require('./chunk-OMOBMXD5.cjs');
47
47
 
48
48
 
49
49
 
50
- var _chunkPZ4GT5JOcjs = require('./chunk-PZ4GT5JO.cjs');
51
- require('./chunk-2QCRZ7OL.cjs');
50
+ var _chunkHOIDJEVIcjs = require('./chunk-HOIDJEVI.cjs');
51
+ require('./chunk-XX5FTPJR.cjs');
52
52
 
53
53
 
54
54
 
55
55
 
56
56
 
57
- var _chunkND5XJVK2cjs = require('./chunk-ND5XJVK2.cjs');
57
+
58
+ var _chunkBTBHHBVRcjs = require('./chunk-BTBHHBVR.cjs');
58
59
 
59
60
  // src/extensions/ai/streaming-markdown.ts
60
61
  var FENCE = /^ {0,3}(`{3,}|~{3,})/;
@@ -153,30 +154,30 @@ function closeStreaming(markdown) {
153
154
  // src/extensions/ai/ai-target.ts
154
155
  function targetOfBlocks(state, ids) {
155
156
  const { doc, schema } = state;
156
- const first = doc.blocks[_nullishCoalesce(doc.index.get(ids[0]), () => ( -1))];
157
- const last = doc.blocks[_nullishCoalesce(doc.index.get(ids[ids.length - 1]), () => ( -1))];
157
+ const first = _chunkBTBHHBVRcjs.blockById.call(void 0, doc, ids[0]);
158
+ const last = _chunkBTBHHBVRcjs.blockById.call(void 0, doc, ids[ids.length - 1]);
158
159
  if (first === void 0 || last === void 0) return null;
159
- const start = _chunkPZ4GT5JOcjs.primaryRegion.call(void 0, schema, first);
160
- const end = _chunkPZ4GT5JOcjs.primaryRegion.call(void 0, schema, last);
160
+ const start = _chunkHOIDJEVIcjs.primaryRegion.call(void 0, schema, first);
161
+ const end = _chunkHOIDJEVIcjs.primaryRegion.call(void 0, schema, last);
161
162
  if (start === null || end === null) return null;
162
163
  const from = { block: first.id, region: start, offset: 0 };
163
- return { from, to: { block: last.id, region: end, offset: _chunkPZ4GT5JOcjs.regionLength.call(void 0, last, end) }, selected: ids.length > 1 };
164
+ return { from, to: { block: last.id, region: end, offset: _chunkHOIDJEVIcjs.regionLength.call(void 0, last, end) }, selected: ids.length > 1 };
164
165
  }
165
166
  function targetOfSelection(state) {
166
167
  const { doc, selection } = state;
167
168
  if (selection === null) return null;
168
- if (selection.kind !== "text") return targetOfBlocks(state, _chunkPZ4GT5JOcjs.selectedBlockIds.call(void 0, doc, selection));
169
- const { from, to } = _chunkPZ4GT5JOcjs.orderedRange.call(void 0, doc, selection.anchor, selection.head);
170
- if (_chunkPZ4GT5JOcjs.comparePos.call(void 0, doc, from, to) !== 0) return { from, to, selected: true };
169
+ if (selection.kind !== "text") return targetOfBlocks(state, _chunkHOIDJEVIcjs.selectedBlockIds.call(void 0, doc, selection));
170
+ const { from, to } = _chunkHOIDJEVIcjs.orderedRange.call(void 0, doc, selection.anchor, selection.head);
171
+ if (_chunkHOIDJEVIcjs.comparePos.call(void 0, doc, from, to) !== 0) return { from, to, selected: true };
171
172
  const block = doc.blocks[doc.index.get(from.block)];
172
173
  const whole = { block: from.block, region: from.region };
173
- return { from: { ...whole, offset: 0 }, to: { ...whole, offset: _chunkPZ4GT5JOcjs.regionLength.call(void 0, block, from.region) }, selected: false };
174
+ return { from: { ...whole, offset: 0 }, to: { ...whole, offset: _chunkHOIDJEVIcjs.regionLength.call(void 0, block, from.region) }, selected: false };
174
175
  }
175
176
  function nearestKept(tr, target) {
176
177
  const { doc } = tr.before;
177
178
  const first = _nullishCoalesce(doc.index.get(target.from.block), () => ( 0));
178
179
  const last = _nullishCoalesce(doc.index.get(target.to.block), () => ( first));
179
- const kept = (block) => block && tr.doc.blocks[_nullishCoalesce(tr.doc.index.get(block.id), () => ( -1))];
180
+ const kept = (block) => block && _chunkBTBHHBVRcjs.blockById.call(void 0, tr.doc, block.id);
180
181
  for (let step = 1; first - step >= 0 || last + step < doc.blocks.length; step++) {
181
182
  const found = _nullishCoalesce(kept(doc.blocks[first - step]), () => ( kept(doc.blocks[last + step])));
182
183
  if (found) return found;
@@ -185,10 +186,10 @@ function nearestKept(tr, target) {
185
186
  }
186
187
  function edgeOf(tr, target) {
187
188
  const point = tr.mapPos(target.from, -1);
188
- const block = _nullishCoalesce(tr.doc.blocks[_nullishCoalesce(tr.doc.index.get(point.block), () => ( -1))], () => ( nearestKept(tr, target)));
189
+ const block = _nullishCoalesce(_chunkBTBHHBVRcjs.blockById.call(void 0, tr.doc, point.block), () => ( nearestKept(tr, target)));
189
190
  if (block === void 0) return null;
190
- const region = _nullishCoalesce(_chunkPZ4GT5JOcjs.primaryRegion.call(void 0, tr.before.schema, block), () => ( ""));
191
- const length = region === "" ? 0 : _chunkPZ4GT5JOcjs.regionLength.call(void 0, block, region);
191
+ const region = _nullishCoalesce(_chunkHOIDJEVIcjs.primaryRegion.call(void 0, tr.before.schema, block), () => ( ""));
192
+ const length = region === "" ? 0 : _chunkHOIDJEVIcjs.regionLength.call(void 0, block, region);
192
193
  const offset = block.id === point.block ? Math.min(point.offset, length) : length;
193
194
  const after = offset > 0 || length === 0;
194
195
  const at = { block: block.id, region, offset: after ? length : 0 };
@@ -200,29 +201,29 @@ function mapTarget(tr, target) {
200
201
  const from = tr.mapPos(target.from, inside);
201
202
  const mapped2 = tr.mapPos(target.to, -inside);
202
203
  if (!tr.doc.index.has(from.block) || !tr.doc.index.has(mapped2.block)) return edgeOf(tr, target);
203
- const to = _chunkPZ4GT5JOcjs.comparePos.call(void 0, tr.doc, from, mapped2) > 0 ? from : mapped2;
204
+ const to = _chunkHOIDJEVIcjs.comparePos.call(void 0, tr.doc, from, mapped2) > 0 ? from : mapped2;
204
205
  return from === target.from && to === target.to ? target : { ...target, from, to };
205
206
  }
206
- var targetGone = (doc, target) => target.lost !== void 0 || target.selected && _chunkPZ4GT5JOcjs.comparePos.call(void 0, doc, target.from, target.to) === 0;
207
+ var targetGone = (doc, target) => target.lost !== void 0 || target.selected && _chunkHOIDJEVIcjs.comparePos.call(void 0, doc, target.from, target.to) === 0;
207
208
  function targetRanges(state, { from, to }) {
208
209
  const { doc, schema } = state;
209
210
  const start = doc.index.get(from.block);
210
211
  const end = doc.index.get(to.block);
211
212
  return doc.blocks.slice(start, end + 1).flatMap((block, i) => {
212
- const region = i === 0 ? from.region : block.id === to.block ? to.region : _chunkPZ4GT5JOcjs.primaryRegion.call(void 0, schema, block);
213
+ const region = i === 0 ? from.region : block.id === to.block ? to.region : _chunkHOIDJEVIcjs.primaryRegion.call(void 0, schema, block);
213
214
  if (region === null || !Object.hasOwn(block.regions, region)) return [];
214
215
  const left = i === 0 ? from.offset : 0;
215
- const right = block.id === to.block ? to.offset : _chunkPZ4GT5JOcjs.regionLength.call(void 0, block, region);
216
+ const right = block.id === to.block ? to.offset : _chunkHOIDJEVIcjs.regionLength.call(void 0, block, region);
216
217
  return right > left ? [{ block: block.id, region, from: left, to: right }] : [];
217
218
  });
218
219
  }
219
220
 
220
221
  // src/extensions/ai/ai-plugin.ts
221
- var aiKey = _chunkPZ4GT5JOcjs.createPluginKey.call(void 0, "ai");
222
+ var aiKey = _chunkHOIDJEVIcjs.createPluginKey.call(void 0, "ai");
222
223
  var AI = "ai";
223
224
  var IDLE = { target: null, menu: null, ghost: null, held: [] };
224
- var aiState = (state) => _chunkPZ4GT5JOcjs.getPluginState.call(void 0, state, aiKey);
225
- var aiTransaction = (state, act2) => new (0, _chunkPZ4GT5JOcjs.Transaction)(state).setMeta(AI, act2).setMeta("addToHistory", false);
225
+ var aiState = (state) => _chunkHOIDJEVIcjs.getPluginState.call(void 0, state, aiKey);
226
+ var aiTransaction = (state, act2) => new (0, _chunkHOIDJEVIcjs.Transaction)(state).setMeta(AI, act2).setMeta("addToHistory", false);
226
227
  function written(ghost, key, phase, output) {
227
228
  return ghost.key !== key ? ghost : { ...ghost, phase, output, shown: closeStreaming(output) };
228
229
  }
@@ -254,8 +255,8 @@ function caretBlock(state) {
254
255
  return _optionalChain([selection, 'optionalAccess', _11 => _11.kind]) === "text" ? selection.head.block : null;
255
256
  }
256
257
  function hasText(state, id) {
257
- const block = state.doc.blocks[_nullishCoalesce(state.doc.index.get(id), () => ( -1))];
258
- return block !== void 0 && Object.keys(block.regions).some((region) => _chunkPZ4GT5JOcjs.regionLength.call(void 0, block, region) > 0);
258
+ const block = _chunkBTBHHBVRcjs.blockById.call(void 0, state.doc, id);
259
+ return block !== void 0 && Object.keys(block.regions).some((region) => _chunkHOIDJEVIcjs.regionLength.call(void 0, block, region) > 0);
259
260
  }
260
261
  function heldText(tr, value, before, after) {
261
262
  if (!tr.docChanged) return value.held;
@@ -356,13 +357,13 @@ function aroundSelection(text, start, end, limit = CONTEXT_LIMIT) {
356
357
  }
357
358
  var WHITESPACE_ENTITY = /(?<!\\)&#(32|9);/g;
358
359
  var plain = (markdown) => markdown.replace(WHITESPACE_ENTITY, (_, code) => code === "32" ? " " : " ");
359
- var markdownOf = (state, blocks) => _chunkPZ4GT5JOcjs.toMarkdown.call(void 0, blocks, { schema: state.schema, comments: false });
360
+ var markdownOf = (state, blocks) => _chunkHOIDJEVIcjs.toMarkdown.call(void 0, blocks, { schema: state.schema, comments: false });
360
361
  function rangeMarkdown(state, from, to) {
361
- const slice = _chunkPZ4GT5JOcjs.sliceFromSelection.call(void 0, { ...state, selection: _chunkPZ4GT5JOcjs.textSelection.call(void 0, from, to) }, _chunkND5XJVK2cjs.createBlockCodec.call(void 0, state.schema));
362
+ const slice = _chunkHOIDJEVIcjs.sliceFromSelection.call(void 0, { ...state, selection: _chunkHOIDJEVIcjs.textSelection.call(void 0, from, to) }, _chunkBTBHHBVRcjs.createBlockCodec.call(void 0, state.schema));
362
363
  return slice === null ? "" : markdownOf(state, slice.blocks).replace(/\n$/, "");
363
364
  }
364
365
  function documentContext(state, target, selection) {
365
- const blocks = _chunkND5XJVK2cjs.createBlockCodec.call(void 0, state.schema).blocksFromDoc(state.doc);
366
+ const blocks = _chunkBTBHHBVRcjs.createBlockCodec.call(void 0, state.schema).blocksFromDoc(state.doc);
366
367
  const full = plain(markdownOf(state, blocks));
367
368
  if (full.length <= CONTEXT_LIMIT) return full;
368
369
  const index = _nullishCoalesce(state.doc.index.get(target.from.block), () => ( 0));
@@ -423,7 +424,7 @@ async function read(editor, link, run, stream, request) {
423
424
  } catch (error) {
424
425
  if (!live()) return;
425
426
  end("error");
426
- link.report(_chunk2VDK27IFcjs.toError.call(void 0, error));
427
+ link.report(_chunk5R3ISX4Acjs.toError.call(void 0, error));
427
428
  }
428
429
  }
429
430
  function startStream(editor, link, stream, request, replace) {
@@ -598,11 +599,11 @@ function resolveAiLabels(overrides) {
598
599
  var openOnSpace = (state, dispatch) => {
599
600
  const { selection, doc } = state;
600
601
  const ai = aiState(state);
601
- if (_optionalChain([selection, 'optionalAccess', _26 => _26.kind]) !== "text" || !_chunkPZ4GT5JOcjs.isCollapsed.call(void 0, selection) || ai.menu !== null || ai.ghost !== null) return false;
602
+ if (_optionalChain([selection, 'optionalAccess', _26 => _26.kind]) !== "text" || !_chunkHOIDJEVIcjs.isCollapsed.call(void 0, selection) || ai.menu !== null || ai.ghost !== null) return false;
602
603
  const { head } = selection;
603
604
  const block = doc.blocks[doc.index.get(head.block)];
604
605
  const paragraph = block.type === "MARKDOWN" && block.attrs.kind === "paragraph";
605
- if (!paragraph || _chunkPZ4GT5JOcjs.regionLength.call(void 0, block, head.region) > 0 || ai.held.includes(block.id)) return false;
606
+ if (!paragraph || _chunkHOIDJEVIcjs.regionLength.call(void 0, block, head.region) > 0 || ai.held.includes(block.id)) return false;
606
607
  _optionalChain([dispatch, 'optionalCall', _27 => _27(aiTransaction(state, { type: "open", target: { from: head, to: head, selected: false }, fromSpace: true }))]);
607
608
  return true;
608
609
  };
@@ -614,15 +615,15 @@ var _react = require('react');
614
615
 
615
616
 
616
617
  // src/extensions/ai/ai-accept.ts
617
- var isParagraph = (block) => _optionalChain([block, 'optionalAccess', _28 => _28.type]) === "MARKDOWN" && _chunkND5XJVK2cjs.parseLine.call(void 0, String(_nullishCoalesce(block.data.markdown, () => ( "")))).kind === "paragraph";
618
+ var isParagraph = (block) => _optionalChain([block, 'optionalAccess', _28 => _28.type]) === "MARKDOWN" && _chunkBTBHHBVRcjs.parseLine.call(void 0, String(_nullishCoalesce(block.data.markdown, () => ( "")))).kind === "paragraph";
618
619
  var DISCARD = { type: "discard" };
619
620
  function insertBeside(tr, schema, target, pieces, makeId) {
620
621
  const index = tr.doc.index.get(target.to.block);
621
- const at = _optionalChain([target, 'access', _29 => _29.lost, 'optionalAccess', _30 => _30.after]) ? _chunkPZ4GT5JOcjs.descendantsEnd.call(void 0, tr.doc, index) : index;
622
- const fresh = _chunkPZ4GT5JOcjs.freshBlocks.call(void 0, pieces, tr.doc.blocks[index].indent, makeId);
623
- tr.step(_chunkPZ4GT5JOcjs.insertBlocksStep.call(void 0, at, fresh));
624
- _chunkPZ4GT5JOcjs.capIndents.call(void 0, tr, at, at + fresh.length);
625
- tr.setSelection(_chunkPZ4GT5JOcjs.endOf.call(void 0, tr.doc, schema, fresh[fresh.length - 1].id));
622
+ const at = _optionalChain([target, 'access', _29 => _29.lost, 'optionalAccess', _30 => _30.after]) ? _chunkHOIDJEVIcjs.descendantsEnd.call(void 0, tr.doc, index) : index;
623
+ const fresh = _chunkHOIDJEVIcjs.freshBlocks.call(void 0, pieces, tr.doc.blocks[index].indent, makeId);
624
+ tr.step(_chunkHOIDJEVIcjs.insertBlocksStep.call(void 0, at, fresh));
625
+ _chunkHOIDJEVIcjs.capIndents.call(void 0, tr, at, at + fresh.length);
626
+ tr.setSelection(_chunkHOIDJEVIcjs.endOf.call(void 0, tr.doc, schema, fresh[fresh.length - 1].id));
626
627
  }
627
628
  function acceptAnswer(editor) {
628
629
  const { state } = editor;
@@ -630,7 +631,7 @@ function acceptAnswer(editor) {
630
631
  if (target === null || ghost === null || ghost.phase === "streaming" || ghost.phase === "error") return null;
631
632
  const { codec } = editor;
632
633
  const makeId = codec.context.newId;
633
- const blocks = _chunkND5XJVK2cjs.fromMarkdown.call(void 0, ghost.output, { newId: makeId });
634
+ const blocks = _chunkBTBHHBVRcjs.fromMarkdown.call(void 0, ghost.output, { newId: makeId });
634
635
  const pieces = blocks.flatMap((block) => codec.toEngineBlocks(block));
635
636
  const tr = editor.createTransaction().setOrigin("ai").setMeta(AI, DISCARD);
636
637
  const gone = targetGone(state.doc, target);
@@ -640,12 +641,12 @@ function acceptAnswer(editor) {
640
641
  const { schema } = state;
641
642
  const open = { openStart: isParagraph(blocks[0]), openEnd: isParagraph(blocks[blocks.length - 1]) };
642
643
  if (ghost.replace || gone) {
643
- const caret = ghost.replace && !gone ? _chunkPZ4GT5JOcjs.deleteRange.call(void 0, tr, schema, target.from, target.to, makeId) : target.to;
644
- _chunkPZ4GT5JOcjs.pasteAtCaret.call(void 0, tr, schema, caret, open, pieces, makeId);
644
+ const caret = ghost.replace && !gone ? _chunkHOIDJEVIcjs.deleteRange.call(void 0, tr, schema, target.from, target.to, makeId) : target.to;
645
+ _chunkHOIDJEVIcjs.pasteAtCaret.call(void 0, tr, schema, caret, open, pieces, makeId);
645
646
  } else {
646
647
  const block = tr.doc.blocks[tr.doc.index.get(target.to.block)];
647
- const end = { ...target.to, offset: _chunkPZ4GT5JOcjs.regionLength.call(void 0, block, target.to.region) };
648
- _chunkPZ4GT5JOcjs.pasteAtCaret.call(void 0, tr, schema, end, { openStart: false, openEnd: false }, pieces, makeId);
648
+ const end = { ...target.to, offset: _chunkHOIDJEVIcjs.regionLength.call(void 0, block, target.to.region) };
649
+ _chunkHOIDJEVIcjs.pasteAtCaret.call(void 0, tr, schema, end, { openStart: false, openEnd: false }, pieces, makeId);
649
650
  }
650
651
  }
651
652
  editor.dispatch(tr);
@@ -663,12 +664,12 @@ function AiBar({ env, ghost, gone, barId }) {
663
664
  const [next, setNext] = _react.useState.call(void 0, "");
664
665
  const { phase } = ghost;
665
666
  const focusPrimary = () => _optionalChain([content, 'access', _31 => _31.current, 'optionalAccess', _32 => _32.querySelector, 'call', _33 => _33(`[${PRIMARY}]`), 'optionalAccess', _34 => _34.focus, 'call', _35 => _35()]);
666
- _chunkGTTPVJSDcjs.useIsomorphicLayoutEffect.call(void 0, () => {
667
+ _chunk5M4QG3ZCcjs.useIsomorphicLayoutEffect.call(void 0, () => {
667
668
  const active = _optionalChain([content, 'access', _36 => _36.current, 'optionalAccess', _37 => _37.ownerDocument, 'access', _38 => _38.activeElement]);
668
669
  inside.current = active == null || active === active.ownerDocument.body;
669
670
  if (inside.current) focusPrimary();
670
671
  }, []);
671
- _chunkGTTPVJSDcjs.useIsomorphicLayoutEffect.call(void 0, () => {
672
+ _chunk5M4QG3ZCcjs.useIsomorphicLayoutEffect.call(void 0, () => {
672
673
  const active = _optionalChain([content, 'access', _39 => _39.current, 'optionalAccess', _40 => _40.ownerDocument, 'access', _41 => _41.activeElement]);
673
674
  if (inside.current && !_optionalChain([content, 'access', _42 => _42.current, 'optionalAccess', _43 => _43.contains, 'call', _44 => _44(_nullishCoalesce(active, () => ( null)))])) focusPrimary();
674
675
  }, [phase, ghost.key]);
@@ -716,21 +717,21 @@ function AiBar({ env, ghost, gone, barId }) {
716
717
  children: [
717
718
  phase === "streaming" && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
718
719
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm text-muted-foreground", children: labels.writing }),
719
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2VDK27IFcjs.Button, { ...primary, size: "sm", variant: "outline", onClick: () => stop(editor), children: labels.stop })
720
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5R3ISX4Acjs.Button, { ...primary, size: "sm", variant: "outline", onClick: () => stop(editor), children: labels.stop })
720
721
  ] }),
721
722
  phase === "error" && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
722
723
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { role: "alert", className: "text-sm text-destructive", children: labels.failed }),
723
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2VDK27IFcjs.Button, { ...primary, size: "sm", onClick: () => retry(editor, stream), children: labels.retry }),
724
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2VDK27IFcjs.Button, { size: "sm", variant: "ghost", onClick: onDiscard, children: labels.discard })
724
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5R3ISX4Acjs.Button, { ...primary, size: "sm", onClick: () => retry(editor, stream), children: labels.retry }),
725
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5R3ISX4Acjs.Button, { size: "sm", variant: "ghost", onClick: onDiscard, children: labels.discard })
725
726
  ] }),
726
727
  (phase === "done" || phase === "stopped") && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
727
728
  phase === "stopped" && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm text-muted-foreground", children: labels.stopped }),
728
729
  gone && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "w-full text-xs text-muted-foreground", children: labels.targetGone }),
729
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2VDK27IFcjs.Button, { ...primary, size: "sm", onClick: onAccept, children: labels.accept }),
730
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2VDK27IFcjs.Button, { size: "sm", variant: "ghost", onClick: onDiscard, children: labels.discard }),
731
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2VDK27IFcjs.Button, { size: "sm", variant: "ghost", onClick: () => retry(editor, stream), children: labels.retry }),
730
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5R3ISX4Acjs.Button, { ...primary, size: "sm", onClick: onAccept, children: labels.accept }),
731
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5R3ISX4Acjs.Button, { size: "sm", variant: "ghost", onClick: onDiscard, children: labels.discard }),
732
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5R3ISX4Acjs.Button, { size: "sm", variant: "ghost", onClick: () => retry(editor, stream), children: labels.retry }),
732
733
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "form", { className: "w-full", onSubmit: onFollowUp, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
733
- _chunk2VDK27IFcjs.Input,
734
+ _chunk5R3ISX4Acjs.Input,
734
735
  {
735
736
  "aria-label": labels.followUp,
736
737
  placeholder: labels.followUp,
@@ -754,8 +755,8 @@ function AiBar({ env, ghost, gone, barId }) {
754
755
  var reader = null;
755
756
  function readerOf() {
756
757
  if (reader === null) {
757
- const schema = _chunkND5XJVK2cjs.createSchema.call(void 0, );
758
- reader = { schema, codec: _chunkND5XJVK2cjs.createBlockCodec.call(void 0, schema) };
758
+ const schema = _chunkBTBHHBVRcjs.createSchema.call(void 0, );
759
+ reader = { schema, codec: _chunkBTBHHBVRcjs.createBlockCodec.call(void 0, schema) };
759
760
  }
760
761
  return reader;
761
762
  }
@@ -763,16 +764,16 @@ function fillGhost(ghost, markdown) {
763
764
  const doc = ghost.ownerDocument;
764
765
  const { schema, codec } = readerOf();
765
766
  const drawn2 = [];
766
- for (const block of _chunkND5XJVK2cjs.fromMarkdown.call(void 0, markdown)) {
767
+ for (const block of _chunkBTBHHBVRcjs.fromMarkdown.call(void 0, markdown)) {
767
768
  for (const piece of codec.toEngineBlocks(block)) {
768
769
  const element = doc.createElement("div");
769
770
  element.className = "quoin-ai-ghost-block";
770
771
  element.dataset.kind = String(_nullishCoalesce(piece.attrs.kind, () => ( piece.type.toLowerCase())));
771
772
  if (piece.attrs.level !== void 0) element.dataset.level = String(piece.attrs.level);
772
773
  if (piece.indent > 0) element.style.setProperty("--quoin-ai-indent", String(piece.indent));
773
- const region = _chunkPZ4GT5JOcjs.primaryRegion.call(void 0, schema, piece);
774
- if (region === null) element.textContent = _chunkPZ4GT5JOcjs.toPlainText.call(void 0, [codec.toQuoin(piece)], { schema });
775
- else _chunkGTTPVJSDcjs.renderInline.call(void 0, element, piece.regions[region], []);
774
+ const region = _chunkHOIDJEVIcjs.primaryRegion.call(void 0, schema, piece);
775
+ if (region === null) element.textContent = _chunkHOIDJEVIcjs.toPlainText.call(void 0, [codec.toQuoin(piece)], { schema });
776
+ else _chunk5M4QG3ZCcjs.renderInline.call(void 0, element, piece.regions[region], []);
776
777
  drawn2.push(element);
777
778
  }
778
779
  }
@@ -781,6 +782,45 @@ function fillGhost(ghost, markdown) {
781
782
 
782
783
  // src/extensions/ai/use-ghost-place.ts
783
784
 
785
+
786
+ // src/extensions/ai/keep-reader-still.ts
787
+ function scrollerOf(root) {
788
+ for (let element = root; element !== null; element = element.parentElement) {
789
+ if (/auto|scroll/.test(getComputedStyle(element).overflowY) && element.scrollHeight > element.clientHeight) {
790
+ return element;
791
+ }
792
+ }
793
+ return _nullishCoalesce(root.ownerDocument.scrollingElement, () => ( null));
794
+ }
795
+ function keepReaderStill(root, shell, change) {
796
+ const scroller = scrollerOf(root);
797
+ if (scroller === null) return change();
798
+ const view = scroller === root.ownerDocument.scrollingElement ? 0 : scroller.getBoundingClientRect().top;
799
+ if (shell.getBoundingClientRect().bottom > view) return change();
800
+ const next = shell.nextElementSibling;
801
+ const mark = () => next === null ? root.getBoundingClientRect().bottom : next.getBoundingClientRect().top;
802
+ const before = mark();
803
+ change();
804
+ const moved = mark() - before;
805
+ if (moved) scroller.scrollBy({ top: moved, behavior: "instant" });
806
+ }
807
+
808
+ // src/extensions/ai/space-below.ts
809
+ function rowGap(shell) {
810
+ const parent = shell.parentElement;
811
+ if (parent === null || !_optionalChain([shell, 'access', _49 => _49.nextElementSibling, 'optionalAccess', _50 => _50.hasAttribute, 'call', _51 => _51("data-block-id")])) return 0;
812
+ const { display, flexDirection, rowGap: gap } = getComputedStyle(parent);
813
+ const column = display.includes("grid") || display.includes("flex") && flexDirection.startsWith("column");
814
+ return column ? parseFloat(gap) || 0 : 0;
815
+ }
816
+ function spaceBelow(shell) {
817
+ const own = getComputedStyle(shell);
818
+ const drawn2 = parseFloat(own.borderBottomWidth) || own.backgroundColor + own.backgroundImage !== "rgba(0, 0, 0, 0)none";
819
+ const padding = drawn2 ? 0 : parseFloat(own.paddingBottom) || 0;
820
+ return { padding, gap: rowGap(shell) };
821
+ }
822
+
823
+ // src/extensions/ai/use-ghost-place.ts
784
824
  var ROOM2 = "--quoin-ai-room";
785
825
  var GAP = 4;
786
826
  function place(controller, root, layer, block, region) {
@@ -795,27 +835,28 @@ function place(controller, root, layer, block, region) {
795
835
  const scale = origin.width / root.offsetWidth || 1;
796
836
  const below = shell.getBoundingClientRect();
797
837
  const text = host.getBoundingClientRect();
838
+ const { padding, gap } = spaceBelow(shell);
798
839
  const left = (text.left - origin.left) / scale - root.clientLeft + root.scrollLeft;
799
- const top = (below.bottom - origin.top) / scale - root.clientTop + root.scrollTop + GAP;
840
+ const top = (below.bottom - origin.top) / scale - padding - root.clientTop + root.scrollTop + GAP;
800
841
  layer.style.transform = `translate(${left}px, ${top}px)`;
801
842
  layer.style.width = `${text.width / scale}px`;
802
- clipAside(layer, text, _chunkQAW5NH6Scjs.clipOf.call(void 0, host, root), scale);
803
- root.style.setProperty(ROOM2, `${layer.offsetHeight + 2 * GAP}px`);
843
+ clipAside(layer, text, _chunkLE5XMIFJcjs.clipBoxes.call(void 0, host, root), scale);
844
+ const room = Math.max(0, layer.offsetHeight + 2 * GAP - padding - gap);
845
+ keepReaderStill(root, shell, () => root.style.setProperty(ROOM2, `${room}px`));
804
846
  }
805
- function clipAside(layer, text, clip, scale) {
806
- const area = _optionalChain([clip, 'optionalAccess', _49 => _49.getBoundingClientRect, 'call', _50 => _50()]);
807
- const start = area === void 0 ? 0 : Math.max(0, area.left - text.left) / scale;
808
- const end = area === void 0 ? 0 : Math.max(0, text.right - area.right) / scale;
847
+ function clipAside(layer, text, areas, scale) {
848
+ const start = Math.max(0, ...areas.map((area) => area.left - text.left)) / scale;
849
+ const end = Math.max(0, ...areas.map((area) => text.right - area.right)) / scale;
809
850
  layer.style.clipPath = start > 0 || end > 0 ? `inset(0 ${end}px 0 ${start}px)` : "";
810
851
  if (start + end >= text.width / scale && text.width > 0) layer.style.visibility = "hidden";
811
852
  }
812
853
  function useGhostPlace(controller, layer, block, region) {
813
854
  const placeNow = _react.useRef.call(void 0, () => {
814
855
  });
815
- _chunkGTTPVJSDcjs.useIsomorphicLayoutEffect.call(void 0, () => {
856
+ _chunk5M4QG3ZCcjs.useIsomorphicLayoutEffect.call(void 0, () => {
816
857
  const root = controller.root;
817
858
  const element = layer.current;
818
- const view = _optionalChain([root, 'optionalAccess', _51 => _51.ownerDocument, 'access', _52 => _52.defaultView]);
859
+ const view = _optionalChain([root, 'optionalAccess', _52 => _52.ownerDocument, 'access', _53 => _53.defaultView]);
819
860
  if (!root || !element || !view) return void 0;
820
861
  const update = () => place(controller, root, element, block, region);
821
862
  let frame = 0;
@@ -833,7 +874,7 @@ function useGhostPlace(controller, layer, block, region) {
833
874
  const unsubscribe = controller.editor.subscribe(later);
834
875
  const fonts = root.ownerDocument.fonts;
835
876
  root.addEventListener("scroll", later, { capture: true, passive: true });
836
- _optionalChain([fonts, 'optionalAccess', _53 => _53.addEventListener, 'call', _54 => _54("loadingdone", later)]);
877
+ _optionalChain([fonts, 'optionalAccess', _54 => _54.addEventListener, 'call', _55 => _55("loadingdone", later)]);
837
878
  return () => {
838
879
  placeNow.current = () => {
839
880
  };
@@ -841,7 +882,7 @@ function useGhostPlace(controller, layer, block, region) {
841
882
  observer.disconnect();
842
883
  unsubscribe();
843
884
  root.removeEventListener("scroll", later, { capture: true });
844
- _optionalChain([fonts, 'optionalAccess', _55 => _55.removeEventListener, 'call', _56 => _56("loadingdone", later)]);
885
+ _optionalChain([fonts, 'optionalAccess', _56 => _56.removeEventListener, 'call', _57 => _57("loadingdone", later)]);
845
886
  root.style.removeProperty(ROOM2);
846
887
  };
847
888
  }, [controller, layer, block, region]);
@@ -854,7 +895,7 @@ function AiGhost({ env, ghost, target, gone, barId }) {
854
895
  const layer = _react.useRef.call(void 0, null);
855
896
  const drawn2 = _react.useRef.call(void 0, null);
856
897
  const place2 = useGhostPlace(env.controller, layer, target.to.block, target.to.region);
857
- _chunkGTTPVJSDcjs.useIsomorphicLayoutEffect.call(void 0, () => {
898
+ _chunk5M4QG3ZCcjs.useIsomorphicLayoutEffect.call(void 0, () => {
858
899
  const element = drawn2.current;
859
900
  if (element === null) return;
860
901
  element.setAttribute("inert", "");
@@ -874,7 +915,7 @@ function AiGhost({ env, ghost, target, gone, barId }) {
874
915
 
875
916
  function useTargetAnchor(controller, target) {
876
917
  const latest = _react.useRef.call(void 0, target);
877
- _chunkGTTPVJSDcjs.useIsomorphicLayoutEffect.call(void 0, () => {
918
+ _chunk5M4QG3ZCcjs.useIsomorphicLayoutEffect.call(void 0, () => {
878
919
  latest.current = target;
879
920
  });
880
921
  return _react.useMemo.call(void 0, () => {
@@ -883,8 +924,8 @@ function useTargetAnchor(controller, target) {
883
924
  return { block: to.block, region: to.region, from: to.offset, to: to.offset };
884
925
  };
885
926
  return {
886
- current: _chunk2VDK27IFcjs.virtualAnchor.call(void 0,
887
- () => _chunk2VDK27IFcjs.rangeRect.call(void 0, controller, end()),
927
+ current: _chunk5R3ISX4Acjs.virtualAnchor.call(void 0,
928
+ () => _chunk5R3ISX4Acjs.rangeRect.call(void 0, controller, end()),
888
929
  () => controller.registry.get(end().block, end().region)
889
930
  )
890
931
  };
@@ -941,7 +982,7 @@ function AiMenu({ env, target, fromSpace }) {
941
982
  };
942
983
  const fieldLabel = asking === "tone" ? labels.tonePrompt : asking === "translate" ? labels.translatePrompt : labels.prompt;
943
984
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
944
- _chunk2VDK27IFcjs.Popover,
985
+ _chunk5R3ISX4Acjs.Popover,
945
986
  {
946
987
  open: true,
947
988
  modal: false,
@@ -949,9 +990,9 @@ function AiMenu({ env, target, fromSpace }) {
949
990
  if (!next) closeMenu(controller.editor);
950
991
  },
951
992
  children: [
952
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2VDK27IFcjs.PopoverAnchor, { virtualRef: anchor }),
993
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk5R3ISX4Acjs.PopoverAnchor, { virtualRef: anchor }),
953
994
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
954
- _chunk2VDK27IFcjs.PopoverContent,
995
+ _chunk5R3ISX4Acjs.PopoverContent,
955
996
  {
956
997
  "aria-label": labels.askAi,
957
998
  side: "bottom",
@@ -959,7 +1000,7 @@ function AiMenu({ env, target, fromSpace }) {
959
1000
  className: "w-80 p-1",
960
1001
  onOpenAutoFocus: (event) => {
961
1002
  event.preventDefault();
962
- _optionalChain([field, 'access', _57 => _57.current, 'optionalAccess', _58 => _58.focus, 'call', _59 => _59()]);
1003
+ _optionalChain([field, 'access', _58 => _58.current, 'optionalAccess', _59 => _59.focus, 'call', _60 => _60()]);
963
1004
  },
964
1005
  onCloseAutoFocus: (event) => event.preventDefault(),
965
1006
  onEscapeKeyDown: (event) => {
@@ -968,7 +1009,7 @@ function AiMenu({ env, target, fromSpace }) {
968
1009
  },
969
1010
  children: [
970
1011
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
971
- _chunk2VDK27IFcjs.Input,
1012
+ _chunk5R3ISX4Acjs.Input,
972
1013
  {
973
1014
  ref: field,
974
1015
  role: "combobox",
@@ -992,7 +1033,7 @@ function AiMenu({ env, target, fromSpace }) {
992
1033
  id: optionId(index),
993
1034
  role: "option",
994
1035
  "aria-selected": index === current,
995
- className: _chunk2VDK27IFcjs.cn.call(void 0, _chunk2VDK27IFcjs.MENU_ITEM, index === current && "bg-accent text-accent-foreground"),
1036
+ className: _chunk5R3ISX4Acjs.cn.call(void 0, _chunk5R3ISX4Acjs.MENU_ITEM, index === current && "bg-accent text-accent-foreground"),
996
1037
  onMouseDown: (event) => event.preventDefault(),
997
1038
  onClick: () => choose(choice),
998
1039
  children: choice.label
@@ -1013,17 +1054,17 @@ var goneOf = (state) => {
1013
1054
  const { target, ghost } = aiState(state);
1014
1055
  return target !== null && ghost !== null && targetGone(state.doc, target);
1015
1056
  };
1016
- var plainText = (markdown) => _chunkPZ4GT5JOcjs.toPlainText.call(void 0, _chunkND5XJVK2cjs.fromMarkdown.call(void 0, markdown));
1057
+ var plainText = (markdown) => _chunkHOIDJEVIcjs.toPlainText.call(void 0, _chunkBTBHHBVRcjs.fromMarkdown.call(void 0, markdown));
1017
1058
  function AiOverlay({ controller, stream, labels }) {
1018
- const { onError } = _chunk2VDK27IFcjs.useQuoin.call(void 0, );
1059
+ const { onError } = _chunk5R3ISX4Acjs.useQuoin.call(void 0, );
1019
1060
  const link = linkOf(controller.editor);
1020
- _chunkGTTPVJSDcjs.useIsomorphicLayoutEffect.call(void 0, () => {
1061
+ _chunk5M4QG3ZCcjs.useIsomorphicLayoutEffect.call(void 0, () => {
1021
1062
  link.report = onError;
1022
1063
  link.ended = (phase, output) => controller.announce((phase === "done" ? labels.written : labels.halted)(plainText(output)));
1023
1064
  }, [link, onError, controller, labels]);
1024
- const value = _chunkGTTPVJSDcjs.useEditorState.call(void 0, controller, aiState);
1025
- const gone = _chunkGTTPVJSDcjs.useEditorState.call(void 0, controller, goneOf);
1026
- const readOnly = _chunkGTTPVJSDcjs.useReadOnly.call(void 0, controller);
1065
+ const value = _chunk5M4QG3ZCcjs.useEditorState.call(void 0, controller, aiState);
1066
+ const gone = _chunk5M4QG3ZCcjs.useEditorState.call(void 0, controller, goneOf);
1067
+ const readOnly = _chunk5M4QG3ZCcjs.useReadOnly.call(void 0, controller);
1027
1068
  const env = _react.useMemo.call(void 0, () => ({ controller, stream, labels }), [controller, stream, labels]);
1028
1069
  const barId = _react.useId.call(void 0, );
1029
1070
  const showing = value.ghost !== null;
@@ -1031,8 +1072,8 @@ function AiOverlay({ controller, stream, labels }) {
1031
1072
  if (!showing) return;
1032
1073
  const toBar = (event) => {
1033
1074
  if (event.key !== "Escape" || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return false;
1034
- const bar = _optionalChain([controller, 'access', _60 => _60.root, 'optionalAccess', _61 => _61.ownerDocument, 'access', _62 => _62.getElementById, 'call', _63 => _63(barId)]);
1035
- _optionalChain([bar, 'optionalAccess', _64 => _64.querySelector, 'call', _65 => _65(`[${PRIMARY}]`), 'optionalAccess', _66 => _66.focus, 'call', _67 => _67()]);
1075
+ const bar = _optionalChain([controller, 'access', _61 => _61.root, 'optionalAccess', _62 => _62.ownerDocument, 'access', _63 => _63.getElementById, 'call', _64 => _64(barId)]);
1076
+ _optionalChain([bar, 'optionalAccess', _65 => _65.querySelector, 'call', _66 => _66(`[${PRIMARY}]`), 'optionalAccess', _67 => _67.focus, 'call', _68 => _68()]);
1036
1077
  return bar != null;
1037
1078
  };
1038
1079
  controller.keyInterceptors.add(toBar);
@@ -1042,7 +1083,7 @@ function AiOverlay({ controller, stream, labels }) {
1042
1083
  const root = controller.root;
1043
1084
  if (!root) return;
1044
1085
  let away = true;
1045
- return _chunkWXMYDXPNcjs.watchCanvasFocus.call(void 0, controller, root.ownerDocument, (inside, node) => {
1086
+ return _chunkCLTHD5GOcjs.watchCanvasFocus.call(void 0, controller, root.ownerDocument, (inside, node) => {
1046
1087
  if (!inside) away = true;
1047
1088
  else if (root.contains(node)) {
1048
1089
  if (away) noteFocus(controller.editor);
@@ -1081,7 +1122,7 @@ function aiExtension(options) {
1081
1122
  blockMenuItems: [aiBlockMenuItem(labels)],
1082
1123
  overlays: [Overlay]
1083
1124
  };
1084
- return _chunkPZ4GT5JOcjs.defineExtension.call(void 0, {
1125
+ return _chunkHOIDJEVIcjs.defineExtension.call(void 0, {
1085
1126
  name: "ai",
1086
1127
  plugins: [aiPlugin],
1087
1128
  decorations: aiDecorations,
@@ -1092,7 +1133,7 @@ function aiExtension(options) {
1092
1133
  if (link.mounts++ === 0) link.stopWatching = watchGhost(editor, link);
1093
1134
  return () => {
1094
1135
  if (--link.mounts > 0) return;
1095
- _optionalChain([link, 'access', _68 => _68.stopWatching, 'optionalCall', _69 => _69()]);
1136
+ _optionalChain([link, 'access', _69 => _69.stopWatching, 'optionalCall', _70 => _70()]);
1096
1137
  link.stopWatching = null;
1097
1138
  endRun(link);
1098
1139
  };