quoin-editor 2.0.4 → 2.1.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 (215) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/MIGRATION.md +1 -1
  3. package/README.md +14 -13
  4. package/dist/ai.cjs +124 -94
  5. package/dist/ai.d.cts +7 -0
  6. package/dist/ai.d.ts +7 -0
  7. package/dist/ai.js +50 -20
  8. package/dist/ai.js.map +1 -1
  9. package/dist/catalogue.cjs +28 -26
  10. package/dist/catalogue.js +19 -17
  11. package/dist/catalogue.js.map +1 -1
  12. package/dist/{chunk-JGEDJYIV.cjs → chunk-2FUWXPPC.cjs} +92 -98
  13. package/dist/{chunk-NHNW5DK3.js → chunk-3F76QQ65.js} +6 -6
  14. package/dist/{chunk-NHNW5DK3.js.map → chunk-3F76QQ65.js.map} +1 -1
  15. package/dist/{chunk-DBOZFNWU.js → chunk-3QYFAAQR.js} +3 -3
  16. package/dist/{chunk-DBOZFNWU.js.map → chunk-3QYFAAQR.js.map} +1 -1
  17. package/dist/{chunk-I7X7B3WD.cjs → chunk-4JDRDPLC.cjs} +17 -8
  18. package/dist/{chunk-EBSDPHPS.cjs → chunk-4UFWFV43.cjs} +1060 -719
  19. package/dist/{chunk-LM3DHROX.cjs → chunk-5AUIA4B3.cjs} +2 -2
  20. package/dist/{chunk-3INFNIGN.cjs → chunk-5H3BB5VJ.cjs} +131 -78
  21. package/dist/{chunk-DJU25PFC.cjs → chunk-5L2NSIHZ.cjs} +11 -11
  22. package/dist/{chunk-W376L2GM.js → chunk-6Y7JAWYY.js} +8 -8
  23. package/dist/chunk-6Y7JAWYY.js.map +1 -0
  24. package/dist/{chunk-UDBLZILE.js → chunk-ATKDXQEY.js} +12 -7
  25. package/dist/{chunk-UDBLZILE.js.map → chunk-ATKDXQEY.js.map} +1 -1
  26. package/dist/{chunk-A2IQHMIH.js → chunk-BE2SYPK5.js} +49 -54
  27. package/dist/chunk-BE2SYPK5.js.map +1 -0
  28. package/dist/{chunk-PURYVDC3.cjs → chunk-CFICD2JP.cjs} +21 -16
  29. package/dist/{chunk-3HW3WRYC.cjs → chunk-CSIUXYRU.cjs} +2 -2
  30. package/dist/{chunk-N7BSFDDM.cjs → chunk-DBKL524I.cjs} +23 -23
  31. package/dist/{chunk-XE3IYW5B.js → chunk-DEFYFIWW.js} +90 -37
  32. package/dist/chunk-DEFYFIWW.js.map +1 -0
  33. package/dist/{chunk-7E43TURF.cjs → chunk-ECI3PSMS.cjs} +7 -7
  34. package/dist/{chunk-4G645EYO.js → chunk-F73QEF7E.js} +5 -5
  35. package/dist/{chunk-4G645EYO.js.map → chunk-F73QEF7E.js.map} +1 -1
  36. package/dist/{chunk-KHRVCTQQ.cjs → chunk-FUT7XLMP.cjs} +61 -74
  37. package/dist/{chunk-7J63427U.cjs → chunk-GQSBOBOM.cjs} +375 -320
  38. package/dist/{chunk-LU3T3JB4.js → chunk-HPNSQWRD.js} +15 -28
  39. package/dist/chunk-HPNSQWRD.js.map +1 -0
  40. package/dist/{chunk-7Y4B7NHO.js → chunk-I34BFOLL.js} +18 -11
  41. package/dist/chunk-I34BFOLL.js.map +1 -0
  42. package/dist/{chunk-AJT54OMZ.js → chunk-IFRXEFQH.js} +9 -15
  43. package/dist/chunk-IFRXEFQH.js.map +1 -0
  44. package/dist/{chunk-YYW33WUL.cjs → chunk-K3XWVRL4.cjs} +8 -8
  45. package/dist/{chunk-LSI4RMTY.js → chunk-L5L6YNLT.js} +3 -3
  46. package/dist/{chunk-LSI4RMTY.js.map → chunk-L5L6YNLT.js.map} +1 -1
  47. package/dist/{chunk-2BPXVQBP.js → chunk-MO4UMXMT.js} +238 -86
  48. package/dist/chunk-MO4UMXMT.js.map +1 -0
  49. package/dist/{chunk-2QKFRKOP.cjs → chunk-N25EH2GZ.cjs} +351 -205
  50. package/dist/{chunk-CSPCDVCT.js → chunk-NCV2ML25.js} +3 -3
  51. package/dist/{chunk-CSPCDVCT.js.map → chunk-NCV2ML25.js.map} +1 -1
  52. package/dist/{chunk-NUQ64S5B.cjs → chunk-OJSKSQTL.cjs} +26 -26
  53. package/dist/chunk-PIWMU4BV.js +9 -0
  54. package/dist/chunk-PIWMU4BV.js.map +1 -0
  55. package/dist/chunk-QFGWSFOI.cjs +10 -0
  56. package/dist/{chunk-GPTJYWGS.js → chunk-RNBLVGWY.js} +3 -3
  57. package/dist/{chunk-GPTJYWGS.js.map → chunk-RNBLVGWY.js.map} +1 -1
  58. package/dist/{chunk-5RBMZGKV.js → chunk-TL2MJOWM.js} +325 -57
  59. package/dist/chunk-TL2MJOWM.js.map +1 -0
  60. package/dist/{chunk-YEDPBYO5.cjs → chunk-UDQ52EEC.cjs} +4 -4
  61. package/dist/{chunk-EIWPJWY3.js → chunk-UE2NQPNG.js} +5 -5
  62. package/dist/{chunk-EIWPJWY3.js.map → chunk-UE2NQPNG.js.map} +1 -1
  63. package/dist/{chunk-BE2YHZXA.cjs → chunk-ULV3SCYP.cjs} +26 -15
  64. package/dist/{chunk-WDBFGX3U.cjs → chunk-VTTMJGWP.cjs} +72 -77
  65. package/dist/{chunk-556PFYES.js → chunk-WK4UGDZN.js} +290 -235
  66. package/dist/chunk-WK4UGDZN.js.map +1 -0
  67. package/dist/{chunk-JZF6UQH5.cjs → chunk-WMKA37NJ.cjs} +12 -12
  68. package/dist/{chunk-F2ZK6Z52.js → chunk-Y26F2X4E.js} +3 -3
  69. package/dist/{chunk-F2ZK6Z52.js.map → chunk-Y26F2X4E.js.map} +1 -1
  70. package/dist/{chunk-FK7EBZPX.cjs → chunk-Z5EEDCRI.cjs} +549 -276
  71. package/dist/{chunk-G6NBQYMM.js → chunk-ZGFZRVQA.js} +817 -478
  72. package/dist/chunk-ZGFZRVQA.js.map +1 -0
  73. package/dist/{chunk-QLVX7MPS.js → chunk-ZQF45LA4.js} +19 -8
  74. package/dist/chunk-ZQF45LA4.js.map +1 -0
  75. package/dist/comments.cjs +127 -65
  76. package/dist/comments.d.cts +35 -2
  77. package/dist/comments.d.ts +35 -2
  78. package/dist/comments.js +73 -12
  79. package/dist/comments.js.map +1 -1
  80. package/dist/{diagram-extension-22x_njXB.d.ts → diagram-extension-BMlh8GCw.d.ts} +1 -1
  81. package/dist/{diagram-extension-C_FSkoj9.d.cts → diagram-extension-gmndUpti.d.cts} +1 -1
  82. package/dist/diagram.cjs +10 -9
  83. package/dist/diagram.d.cts +2 -2
  84. package/dist/diagram.d.ts +2 -2
  85. package/dist/diagram.js +7 -6
  86. package/dist/diff.cjs +25 -25
  87. package/dist/diff.js +3 -3
  88. package/dist/docx.cjs +31 -30
  89. package/dist/docx.js +5 -4
  90. package/dist/docx.js.map +1 -1
  91. package/dist/embed.cjs +10 -9
  92. package/dist/embed.js +8 -7
  93. package/dist/{emoji-extension-Dk5IQLZf.d.cts → emoji-extension-DD9CQTQj.d.cts} +1 -1
  94. package/dist/{emoji-extension-C832oGeT.d.ts → emoji-extension-U_PWZ2fX.d.ts} +1 -1
  95. package/dist/emoji.cjs +10 -9
  96. package/dist/emoji.d.cts +2 -2
  97. package/dist/emoji.d.ts +2 -2
  98. package/dist/emoji.js +7 -6
  99. package/dist/engine.cjs +58 -58
  100. package/dist/engine.d.cts +3 -3
  101. package/dist/engine.d.ts +3 -3
  102. package/dist/engine.js +3 -3
  103. package/dist/{find-extension-DmK9_N81.d.ts → find-extension-BGlqooZi.d.ts} +1 -1
  104. package/dist/{find-extension-BfruAPLi.d.cts → find-extension-BPnAJ8bY.d.cts} +1 -1
  105. package/dist/find.cjs +11 -10
  106. package/dist/find.d.cts +1 -1
  107. package/dist/find.d.ts +1 -1
  108. package/dist/find.js +7 -6
  109. package/dist/headless.cjs +70 -69
  110. package/dist/headless.d.cts +33 -10
  111. package/dist/headless.d.ts +33 -10
  112. package/dist/headless.js +5 -4
  113. package/dist/highlight.cjs +7 -6
  114. package/dist/highlight.js +6 -5
  115. package/dist/highlight.js.map +1 -1
  116. package/dist/{index-3fYvwqUZ.d.cts → index-DN7vRKFw.d.cts} +1 -1
  117. package/dist/{index-CgxnzCO5.d.ts → index-DyIYI3PM.d.ts} +1 -1
  118. package/dist/index.cjs +82 -81
  119. package/dist/index.d.cts +19 -12
  120. package/dist/index.d.ts +19 -12
  121. package/dist/index.js +6 -5
  122. package/dist/{labels-CiCxj2jP.d.cts → labels-BJORhN93.d.ts} +10 -5
  123. package/dist/{labels-BhRJYo46.d.ts → labels-CVYJ2-1X.d.cts} +10 -5
  124. package/dist/locale-ar.cjs +10 -0
  125. package/dist/locale-ar.d.cts +1 -1
  126. package/dist/locale-ar.d.ts +1 -1
  127. package/dist/locale-ar.js +10 -0
  128. package/dist/locale-ar.js.map +1 -1
  129. package/dist/locale-de.cjs +4 -0
  130. package/dist/locale-de.d.cts +1 -1
  131. package/dist/locale-de.d.ts +1 -1
  132. package/dist/locale-de.js +4 -0
  133. package/dist/locale-de.js.map +1 -1
  134. package/dist/locale-es.cjs +4 -0
  135. package/dist/locale-es.d.cts +1 -1
  136. package/dist/locale-es.d.ts +1 -1
  137. package/dist/locale-es.js +4 -0
  138. package/dist/locale-es.js.map +1 -1
  139. package/dist/locale-fr.cjs +4 -0
  140. package/dist/locale-fr.d.cts +1 -1
  141. package/dist/locale-fr.d.ts +1 -1
  142. package/dist/locale-fr.js +4 -0
  143. package/dist/locale-fr.js.map +1 -1
  144. package/dist/locale-ja.cjs +4 -0
  145. package/dist/locale-ja.d.cts +1 -1
  146. package/dist/locale-ja.d.ts +1 -1
  147. package/dist/locale-ja.js +4 -0
  148. package/dist/locale-ja.js.map +1 -1
  149. package/dist/locale-pt.cjs +4 -0
  150. package/dist/locale-pt.d.cts +1 -1
  151. package/dist/locale-pt.d.ts +1 -1
  152. package/dist/locale-pt.js +4 -0
  153. package/dist/locale-pt.js.map +1 -1
  154. package/dist/locale-zh.cjs +4 -0
  155. package/dist/locale-zh.d.cts +1 -1
  156. package/dist/locale-zh.d.ts +1 -1
  157. package/dist/locale-zh.js +4 -0
  158. package/dist/locale-zh.js.map +1 -1
  159. package/dist/math.cjs +11 -10
  160. package/dist/math.js +9 -8
  161. package/dist/mention.cjs +11 -10
  162. package/dist/mention.js +9 -8
  163. package/dist/{node-reveal-DY6bimv3.d.cts → node-reveal-Be7Xa0BL.d.cts} +1 -1
  164. package/dist/{node-reveal-BnFcnHhz.d.ts → node-reveal-CftIEqF_.d.ts} +1 -1
  165. package/dist/palette-DuIFHYHc.d.cts +10 -0
  166. package/dist/palette-DuIFHYHc.d.ts +10 -0
  167. package/dist/presence.cjs +39 -38
  168. package/dist/presence.js +8 -7
  169. package/dist/presence.js.map +1 -1
  170. package/dist/source.cjs +10 -9
  171. package/dist/source.d.cts +1 -1
  172. package/dist/source.d.ts +1 -1
  173. package/dist/source.js +6 -5
  174. package/dist/source.js.map +1 -1
  175. package/dist/{stats-extension-Dmw0Ujxe.d.ts → stats-extension-Bbpr593p.d.ts} +1 -1
  176. package/dist/{stats-extension-D5HySF0a.d.cts → stats-extension-D7FaeydW.d.cts} +1 -1
  177. package/dist/stats.cjs +12 -10
  178. package/dist/stats.d.cts +2 -2
  179. package/dist/stats.d.ts +2 -2
  180. package/dist/stats.js +9 -7
  181. package/dist/styles.css +1 -1
  182. package/dist/{table-extension-7LkLNK6Q.d.cts → table-extension-DNijjmxV.d.cts} +1 -1
  183. package/dist/{table-extension-DEpV0DnP.d.ts → table-extension-Dp6FYGPh.d.ts} +1 -1
  184. package/dist/table.cjs +26 -25
  185. package/dist/table.d.cts +4 -3
  186. package/dist/table.d.ts +4 -3
  187. package/dist/table.js +7 -6
  188. package/dist/{toc-extension-Dvh37bWQ.d.cts → toc-extension-BGEVAmSY.d.cts} +1 -1
  189. package/dist/{toc-extension-SVckrFmb.d.ts → toc-extension-DZIe40eV.d.ts} +1 -1
  190. package/dist/toc.cjs +10 -9
  191. package/dist/toc.d.cts +2 -2
  192. package/dist/toc.d.ts +2 -2
  193. package/dist/toc.js +7 -6
  194. package/dist/{types-DMuuDBVh.d.ts → types-Byu2a9qk.d.ts} +6 -1
  195. package/dist/{types-Bzhq5AXj.d.cts → types-DdDlCyyZ.d.cts} +6 -1
  196. package/dist/view.cjs +4 -4
  197. package/dist/view.js +2 -2
  198. package/dist/yjs.cjs +121 -121
  199. package/dist/yjs.d.cts +2 -2
  200. package/dist/yjs.d.ts +2 -2
  201. package/dist/yjs.js +5 -5
  202. package/package.json +4 -3
  203. package/dist/chunk-2BPXVQBP.js.map +0 -1
  204. package/dist/chunk-556PFYES.js.map +0 -1
  205. package/dist/chunk-5RBMZGKV.js.map +0 -1
  206. package/dist/chunk-7Y4B7NHO.js.map +0 -1
  207. package/dist/chunk-A2IQHMIH.js.map +0 -1
  208. package/dist/chunk-AJT54OMZ.js.map +0 -1
  209. package/dist/chunk-G6NBQYMM.js.map +0 -1
  210. package/dist/chunk-LU3T3JB4.js.map +0 -1
  211. package/dist/chunk-QLVX7MPS.js.map +0 -1
  212. package/dist/chunk-W376L2GM.js.map +0 -1
  213. package/dist/chunk-XE3IYW5B.js.map +0 -1
  214. package/dist/palette-BrX-hEd-.d.cts +0 -4
  215. package/dist/palette-BrX-hEd-.d.ts +0 -4
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var chunk2QKFRKOP_cjs = require('./chunk-2QKFRKOP.cjs');
3
+ var chunkN25EH2GZ_cjs = require('./chunk-N25EH2GZ.cjs');
4
4
  var chunkZWTIRGY5_cjs = require('./chunk-ZWTIRGY5.cjs');
5
5
  var chunkA6PGQVTR_cjs = require('./chunk-A6PGQVTR.cjs');
6
- var chunkFK7EBZPX_cjs = require('./chunk-FK7EBZPX.cjs');
7
- var chunkI7X7B3WD_cjs = require('./chunk-I7X7B3WD.cjs');
6
+ var chunkQFGWSFOI_cjs = require('./chunk-QFGWSFOI.cjs');
7
+ var chunkZ5EEDCRI_cjs = require('./chunk-Z5EEDCRI.cjs');
8
+ var chunk4JDRDPLC_cjs = require('./chunk-4JDRDPLC.cjs');
8
9
  var reactDirection = require('@radix-ui/react-direction');
9
10
  var React7 = require('react');
10
11
  var jsxRuntime = require('react/jsx-runtime');
@@ -472,21 +473,9 @@ function frozen(labels) {
472
473
  var englishLabels = frozen({
473
474
  document: "Editor",
474
475
  editorHint: "Press Escape, then Tab, to leave the editor.",
475
- paragraph: "Paragraph",
476
- heading: (level) => `Heading ${level}`,
477
- bulletItem: "Bullet list item",
478
- numberedItem: (n) => `Numbered list item ${n}`,
479
- task: "Task",
480
- quote: "Quote",
481
- callout: (tone) => `Callout, ${tone}`,
482
- calloutTones: { info: "info", tip: "tip", success: "success", warning: "warning", danger: "danger" },
476
+ ...chunkN25EH2GZ_cjs.englishRendererLabels,
483
477
  calloutIcon: "Callout icon",
484
478
  calloutToneMenu: (tone) => `Callout tone: ${tone}`,
485
- toggle: "Toggle",
486
- toggleButton: (hidden) => {
487
- if (hidden === 0) return "Toggle";
488
- return `Toggle, ${hidden} ${hidden === 1 ? "block" : "blocks"} hidden`;
489
- },
490
479
  placeholderEmpty: "Type '/' for commands",
491
480
  placeholderHeading: (level) => `Heading ${level}`,
492
481
  placeholderList: "List",
@@ -509,18 +498,21 @@ var englishLabels = frozen({
509
498
  color: "Color",
510
499
  textColor: "Text color",
511
500
  backgroundColor: "Background color",
501
+ textColorSample: "A",
512
502
  tone: "Tone",
513
503
  icon: "Icon",
514
504
  duplicate: "Duplicate",
515
505
  copy: "Copy",
516
506
  copyLink: "Copy link to block",
517
507
  copied: "Copied",
508
+ copyFailed: "Could not copy",
518
509
  moveUp: "Move up",
519
510
  moveDown: "Move down",
520
511
  delete: "Delete",
521
512
  askAi: "Ask AI",
522
513
  addBelow: "Add block below",
523
514
  dragHandle: "Drag to move, click for options",
515
+ blockOptions: "Block options",
524
516
  slashMenu: "Commands",
525
517
  noResults: "No results",
526
518
  resultCount: (n) => `${n} ${n === 1 ? "result" : "results"}`,
@@ -584,6 +576,7 @@ var englishLabels = frozen({
584
576
  },
585
577
  blockMoved: (position, total) => `Block moved to position ${position} of ${total}`,
586
578
  blockInserted: (position, total) => `Block inserted at position ${position} of ${total}`,
579
+ blocksInserted: (n, position, total) => `${n} blocks inserted at position ${position} of ${total}`,
587
580
  blockDeleted: (n) => n === 1 ? "Block deleted" : `${n} blocks deleted`,
588
581
  blocksSelected: (n) => `${n} ${n === 1 ? "block" : "blocks"} selected`,
589
582
  undoHint: (shortcut) => `Press ${shortcut} to undo`,
@@ -706,7 +699,7 @@ function useLabels() {
706
699
  }
707
700
 
708
701
  // src/blocks/caption-markdown.ts
709
- var captionText = (markdown) => chunkI7X7B3WD_cjs.inlineText(chunkI7X7B3WD_cjs.parseInline(markdown), () => "");
702
+ var captionText = (markdown) => chunk4JDRDPLC_cjs.inlineText(chunk4JDRDPLC_cjs.parseInline(markdown), () => "");
710
703
  function Token({ text: text2, color, className }) {
711
704
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: chunkA6PGQVTR_cjs.tokenColour(color) }, className, children: text2 });
712
705
  }
@@ -727,17 +720,17 @@ function assetHref(resolveAssetUrl, storageKey) {
727
720
  if (storageKey === "") return null;
728
721
  try {
729
722
  const src = resolveAssetUrl(storageKey);
730
- return typeof src === "string" && chunkI7X7B3WD_cjs.sanitizeAssetUrl(src) || null;
723
+ return typeof src === "string" && chunk4JDRDPLC_cjs.sanitizeAssetUrl(src) || null;
731
724
  } catch {
732
725
  return null;
733
726
  }
734
727
  }
735
728
 
736
729
  // src/blocks/image/image-source.ts
737
- var hasPicture = (attrs) => chunkI7X7B3WD_cjs.str(attrs.url) !== "" || chunkI7X7B3WD_cjs.str(attrs.storageKey) !== "";
730
+ var hasPicture = (attrs) => chunk4JDRDPLC_cjs.str(attrs.url) !== "" || chunk4JDRDPLC_cjs.str(attrs.storageKey) !== "";
738
731
  function imageSource(attrs, resolveAssetUrl) {
739
- const url = chunkI7X7B3WD_cjs.str(attrs.url);
740
- return url === "" ? assetHref(resolveAssetUrl, chunkI7X7B3WD_cjs.str(attrs.storageKey)) : chunkI7X7B3WD_cjs.sanitizeHref(url);
732
+ const url = chunk4JDRDPLC_cjs.str(attrs.url);
733
+ return url === "" ? assetHref(resolveAssetUrl, chunk4JDRDPLC_cjs.str(attrs.storageKey)) : chunk4JDRDPLC_cjs.sanitizeHref(url);
741
734
  }
742
735
  var HINT = "text-sm text-muted-foreground";
743
736
  var TONE = {
@@ -749,36 +742,35 @@ var TONE = {
749
742
  };
750
743
  function MarkdownPreview({ data }) {
751
744
  const labels = useLabels();
752
- const markdown = chunkI7X7B3WD_cjs.str(data.markdown);
745
+ const markdown = chunk4JDRDPLC_cjs.str(data.markdown);
753
746
  if (markdown.trim() === "") return /* @__PURE__ */ jsxRuntime.jsx("p", { className: HINT, children: labels.emptyMarkdown });
754
747
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-pre-wrap text-base leading-7", children: markdown });
755
748
  }
756
749
  function CodePreview({ data }) {
757
- return /* @__PURE__ */ jsxRuntime.jsx("div", { dir: "ltr", className: "quoin-code quoin-code-scroll pt-3", children: /* @__PURE__ */ jsxRuntime.jsx(HighlightOverlay, { code: chunkI7X7B3WD_cjs.str(data.code), language: chunkI7X7B3WD_cjs.str(data.language) }) });
750
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { dir: "ltr", className: "quoin-code quoin-code-scroll pt-3", children: /* @__PURE__ */ jsxRuntime.jsx(HighlightOverlay, { code: chunk4JDRDPLC_cjs.str(data.code), language: chunk4JDRDPLC_cjs.str(data.language) }) });
758
751
  }
759
752
  function ImagePreview({ data }) {
760
753
  const { resolveAssetUrl, labels } = useQuoin();
761
754
  const src = imageSource(data, resolveAssetUrl);
762
755
  if (src === null) return /* @__PURE__ */ jsxRuntime.jsx("p", { className: HINT, children: labels.emptyImage });
763
- const caption = captionText(chunkI7X7B3WD_cjs.str(data.caption));
756
+ const caption = captionText(chunk4JDRDPLC_cjs.str(data.caption));
764
757
  return /* @__PURE__ */ jsxRuntime.jsxs("figure", { children: [
765
- /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: chunkI7X7B3WD_cjs.str(data.alt) || labels.imageNoAlt, className: "max-w-full rounded-md" }),
758
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: chunk4JDRDPLC_cjs.str(data.alt) || labels.imageNoAlt, className: "max-w-full rounded-md" }),
766
759
  caption && /* @__PURE__ */ jsxRuntime.jsx("figcaption", { className: HINT, children: caption })
767
760
  ] });
768
761
  }
769
762
  function FilePreview({ data }) {
770
763
  const labels = useLabels();
771
- const name = chunkI7X7B3WD_cjs.str(data.filename);
764
+ const name = chunk4JDRDPLC_cjs.str(data.filename);
772
765
  if (name === "") return /* @__PURE__ */ jsxRuntime.jsx("p", { className: HINT, children: labels.emptyFile });
773
766
  return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: name });
774
767
  }
775
768
  function CalloutPreview({ data }) {
776
769
  const labels = useLabels();
777
- const body = chunkI7X7B3WD_cjs.str(data.body);
770
+ const body = chunk4JDRDPLC_cjs.str(data.body);
778
771
  if (body.trim() === "") return /* @__PURE__ */ jsxRuntime.jsx("p", { className: HINT, children: labels.emptyCallout });
779
- const tone = chunkI7X7B3WD_cjs.str(data.tone) || "info";
780
- const paint = TONE[tone] ?? "border-border bg-muted/50";
781
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-tone": tone, className: `rounded-md border-l-4 ${paint} p-3 text-sm`, children: body });
772
+ const tone = chunkQFGWSFOI_cjs.calloutTone(data.tone);
773
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-tone": tone, className: `rounded-md border-l-4 ${TONE[tone]} p-3 text-sm`, children: body });
782
774
  }
783
775
 
784
776
  // src/blocks/core.ts
@@ -825,7 +817,7 @@ function selectNode(controller, blockId) {
825
817
  const { editor } = controller;
826
818
  const { selection } = editor.state;
827
819
  if (selection?.kind === "node" && selection.block === blockId) return;
828
- chunk2QKFRKOP_cjs.dispatchSelection(controller, chunkFK7EBZPX_cjs.nodeSelection(blockId));
820
+ chunkN25EH2GZ_cjs.dispatchSelection(controller, chunkZ5EEDCRI_cjs.nodeSelection(blockId));
829
821
  }
830
822
  function focusNode(controller, blockId) {
831
823
  selectNode(controller, blockId);
@@ -855,8 +847,8 @@ var OPEN_EDITOR = "openEditor";
855
847
  var ABOVE_CORE_KEYS = 200;
856
848
  function turnedBlock(tr, { doc, selection }) {
857
849
  if (selection?.kind !== "node" || tr.origin === "remote" || tr.origin === "reconcile") return void 0;
858
- const before = chunkI7X7B3WD_cjs.blockById(tr.before.doc, selection.block);
859
- return before !== void 0 && before.type !== chunkI7X7B3WD_cjs.blockById(doc, selection.block)?.type ? selection.block : void 0;
850
+ const before = chunk4JDRDPLC_cjs.blockById(tr.before.doc, selection.block);
851
+ return before !== void 0 && before.type !== chunk4JDRDPLC_cjs.blockById(doc, selection.block)?.type ? selection.block : void 0;
860
852
  }
861
853
  function createVoidEditing() {
862
854
  const starters = /* @__PURE__ */ new Map();
@@ -876,7 +868,7 @@ function createVoidEditing() {
876
868
  start?.();
877
869
  };
878
870
  return {
879
- extension: chunkFK7EBZPX_cjs.defineExtension({
871
+ extension: chunkZ5EEDCRI_cjs.defineExtension({
880
872
  name: "void-editing",
881
873
  priority: ABOVE_CORE_KEYS,
882
874
  keymap: { Enter: openEditor },
@@ -898,7 +890,8 @@ function createVoidEditing() {
898
890
  // src/blocks/text/return-focus-to-text.ts
899
891
  function focusBelongsInText(controller, focused) {
900
892
  if (!focused || focused === focused.ownerDocument.body) return true;
901
- return focused.getAttribute("aria-haspopup") === "menu" && controller.root?.contains(focused) === true;
893
+ const canvasMenuButton = focused.getAttribute("aria-haspopup") === "menu" && focused.tabIndex < 0;
894
+ return canvasMenuButton && controller.root?.contains(focused) === true;
902
895
  }
903
896
  function returnFocusToText(controller) {
904
897
  return (event) => {
@@ -974,7 +967,10 @@ var FOCUS_RING = [
974
967
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid",
975
968
  "focus-visible:outline-[var(--quoin-ring)]"
976
969
  ].join(" ");
977
- var HIGHLIGHT_BAR = "data-[highlighted]:shadow-[inset_2px_0_0_var(--quoin-ring)]";
970
+ var HIGHLIGHT_BAR = [
971
+ "data-[highlighted]:shadow-[inset_2px_0_0_var(--quoin-ring)]",
972
+ "rtl:data-[highlighted]:shadow-[inset_-2px_0_0_var(--quoin-ring)]"
973
+ ].join(" ");
978
974
 
979
975
  // src/ui/touch-target.ts
980
976
  var TAP_TARGET_FLOOR = "pointer-coarse:min-h-11";
@@ -990,24 +986,24 @@ var FLOATING_TOOLBAR = "flex w-auto items-center gap-0.5 p-1 shadow-md";
990
986
  var MENU_CONTENT = "quoin bg-popover text-popover-foreground z-50 max-h-(--radix-popper-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-lg border border-border p-1 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0";
991
987
  var MENU_SUB_CONTENT = "quoin bg-popover text-popover-foreground z-50 max-h-(--radix-popper-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-lg border border-border p-1 shadow-lg";
992
988
  var MENU_ITEM = [
993
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm focus:outline-hidden select-none data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4",
989
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm focus:outline-hidden select-none data-[inset]:ps-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4",
994
990
  HIGHLIGHT_BAR,
995
991
  TAP_TARGET_FLOOR
996
992
  ].join(" ");
997
993
  var MENU_CHECKABLE_ITEM = [
998
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm focus:outline-hidden select-none",
994
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pe-2 ps-8 text-sm focus:outline-hidden select-none",
999
995
  HIGHLIGHT_BAR,
1000
996
  TAP_TARGET_FLOOR
1001
997
  ].join(" ");
1002
- var MENU_ITEM_INDICATOR = "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center";
998
+ var MENU_ITEM_INDICATOR = "pointer-events-none absolute start-2 flex size-3.5 items-center justify-center";
1003
999
  var MENU_SUB_TRIGGER = [
1004
- "focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm focus:outline-hidden select-none data-[inset]:pl-8",
1000
+ "focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm focus:outline-hidden select-none data-[inset]:ps-8",
1005
1001
  HIGHLIGHT_BAR,
1006
1002
  TAP_TARGET_FLOOR
1007
1003
  ].join(" ");
1008
- var MENU_LABEL = "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8";
1004
+ var MENU_LABEL = "px-2 py-1.5 text-sm font-medium data-[inset]:ps-8";
1009
1005
  var MENU_SEPARATOR = "bg-border -mx-1 my-1 h-px";
1010
- var MENU_SHORTCUT = "text-muted-foreground ml-auto text-xs tracking-widest";
1006
+ var MENU_SHORTCUT = "text-muted-foreground ms-auto text-xs tracking-widest";
1011
1007
  function ContextMenu({ modal = false, ...props }) {
1012
1008
  return /* @__PURE__ */ jsxRuntime.jsx(ContextMenuPrimitive__namespace.Root, { modal, ...props });
1013
1009
  }
@@ -1098,7 +1094,7 @@ var ContextMenuSubTrigger = /* @__PURE__ */ React7__namespace.forwardRef(functio
1098
1094
  ...props,
1099
1095
  children: [
1100
1096
  children,
1101
- /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, { className: "ml-auto size-4" })
1097
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, { className: "ms-auto size-4 rtl:rotate-180" })
1102
1098
  ]
1103
1099
  }
1104
1100
  );
@@ -1115,150 +1111,27 @@ var ContextMenuSubContent = /* @__PURE__ */ React7__namespace.forwardRef(functio
1115
1111
  );
1116
1112
  });
1117
1113
 
1118
- // src/gutter/copy-text.ts
1119
- function copyThroughTextArea(text2, doc) {
1120
- const previous = doc.activeElement instanceof HTMLElement ? doc.activeElement : null;
1121
- const area = doc.createElement("textarea");
1122
- area.value = text2;
1123
- area.readOnly = true;
1124
- area.setAttribute("aria-hidden", "true");
1125
- area.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none";
1126
- const beside = previous !== null && previous !== doc.body ? previous.parentElement : null;
1127
- (beside ?? doc.body).append(area);
1128
- area.focus({ preventScroll: true });
1129
- area.select();
1130
- try {
1131
- return doc.execCommand("copy");
1132
- } catch {
1133
- return false;
1134
- } finally {
1135
- area.remove();
1136
- previous?.focus({ preventScroll: true });
1137
- }
1138
- }
1139
- async function copyText(text2, doc = document) {
1140
- const clipboard = doc.defaultView?.navigator.clipboard;
1141
- if (clipboard === void 0) return copyThroughTextArea(text2, doc);
1142
- try {
1143
- await clipboard.writeText(text2);
1144
- return true;
1145
- } catch {
1146
- return copyThroughTextArea(text2, doc);
1147
- }
1148
- }
1149
-
1150
- // src/gutter/run-on-blocks.ts
1151
- function settle(tr, before) {
1152
- const { doc } = tr;
1153
- const { schema } = tr.before;
1154
- const result = tr.selection;
1155
- if (result?.kind !== "blocks" || before?.kind === "blocks") return result;
1156
- const ids = chunkFK7EBZPX_cjs.selectedBlockIds(doc, result);
1157
- const head = chunkFK7EBZPX_cjs.headBlockId(before);
1158
- if (head !== null && ids.includes(head) && chunkFK7EBZPX_cjs.validateSelection(doc, schema, before) === before) return before;
1159
- return chunkFK7EBZPX_cjs.endOf(doc, schema, ids[0]);
1160
- }
1161
- function runOnBlocks(controller, blockIds, command) {
1162
- const { state } = controller.editor;
1163
- const ids = blockIds.filter((id) => state.doc.index.has(id));
1164
- if (ids.length === 0) return false;
1165
- const before = state.selection;
1166
- const exact = chunkI7X7B3WD_cjs.sameItems(chunkFK7EBZPX_cjs.selectedBlockIds(state.doc, before), ids);
1167
- const ran = exact ? before : chunkFK7EBZPX_cjs.blockRangeSelection(ids[0], ids[ids.length - 1]);
1168
- const { ok, tr } = chunkFK7EBZPX_cjs.runCommands(state, [chunkFK7EBZPX_cjs.setSelection(ran), command]);
1169
- const result = ok && tr !== null ? tr : new chunkFK7EBZPX_cjs.Transaction(state).setSelection(ran);
1170
- const settled = exact ? result.selection : settle(result, before);
1171
- if (!chunkFK7EBZPX_cjs.sameSelection(settled, result.selection)) result.setSelection(settled);
1172
- if (result.docChanged || !chunkFK7EBZPX_cjs.sameSelection(result.selection, before)) controller.dispatch(result);
1173
- return ok;
1174
- }
1175
- var noChange = () => true;
1176
-
1177
- // src/gutter/block-menu-items.ts
1178
- var rangeOf = (blockIds) => chunkFK7EBZPX_cjs.blockRangeSelection(blockIds[0], blockIds[blockIds.length - 1]);
1114
+ // src/canvas/block-targets.ts
1179
1115
  function selectedWith(state, blockId) {
1180
1116
  const { doc, selection } = state;
1181
- const selected = chunkFK7EBZPX_cjs.selectedBlockIds(doc, selection);
1117
+ const selected = chunkZ5EEDCRI_cjs.selectedBlockIds(doc, selection);
1182
1118
  const ofBlocks = selection?.kind === "blocks" || selection?.kind === "text" && selected.length > 1;
1183
1119
  return ofBlocks && selected.includes(blockId) ? selected : null;
1184
1120
  }
1185
- function menuTargets(state, blockId) {
1121
+ function handleTargets(state, blockId) {
1186
1122
  const selected = selectedWith(state, blockId);
1187
1123
  if (selected !== null) return selected;
1188
1124
  const { doc } = state;
1189
- const index = chunkI7X7B3WD_cjs.requireBlockIndex(doc, blockId);
1190
- return doc.blocks.slice(index, chunkFK7EBZPX_cjs.descendantsEnd(doc, index)).map((block) => block.id);
1125
+ const index = chunk4JDRDPLC_cjs.requireBlockIndex(doc, blockId);
1126
+ return doc.blocks.slice(index, chunkZ5EEDCRI_cjs.descendantsEnd(doc, index)).map((block) => block.id);
1191
1127
  }
1192
1128
  function styleTargets(state, blockId) {
1193
1129
  return selectedWith(state, blockId) ?? [blockId];
1194
1130
  }
1195
- function menuActions(core, extra = []) {
1196
- return [...core.filter((action) => !action.destructive), ...extra, ...core.filter((action) => action.destructive)];
1197
- }
1198
- function markdownOf(editor, blockIds, resolveAssetUrl) {
1199
- const { doc } = editor.state;
1200
- const range = chunkFK7EBZPX_cjs.selectedBlockRange(doc, rangeOf(blockIds));
1201
- if (range === null) return "";
1202
- const blocks = editor.codec.blocksFromDoc(chunkI7X7B3WD_cjs.createDoc(doc.blocks.slice(range.start, range.end)));
1203
- return chunkFK7EBZPX_cjs.toMarkdown(blocks, { schema: editor.schema, resolveAssetUrl, comments: false });
1204
- }
1205
- var applies = (command) => (state, blockIds) => blockIds.every((id) => state.doc.index.has(id)) && chunkFK7EBZPX_cjs.runCommands(state, [chunkFK7EBZPX_cjs.setSelection(rangeOf(blockIds)), command]).ok;
1206
- function coreBlockMenuActions(labels, options) {
1207
- const copy = (controller, blockIds, text2) => {
1208
- void copyText(text2).then((copied) => {
1209
- if (copied) options.onCopied?.();
1210
- });
1211
- runOnBlocks(controller, blockIds, noChange);
1212
- };
1213
- return [
1214
- {
1215
- id: "duplicate",
1216
- label: labels.duplicate,
1217
- icon: CopyPlus,
1218
- shortcut: "Mod-d",
1219
- run: (controller, blockIds) => runOnBlocks(controller, blockIds, chunkFK7EBZPX_cjs.duplicateBlocks())
1220
- },
1221
- {
1222
- id: "copy",
1223
- label: labels.copy,
1224
- icon: Copy,
1225
- run: (controller, blockIds) => copy(controller, blockIds, markdownOf(controller.editor, blockIds, options.resolveAssetUrl))
1226
- },
1227
- {
1228
- id: "copy-link",
1229
- label: labels.copyLink,
1230
- icon: Link,
1231
- run: (controller, blockIds) => copy(controller, blockIds, options.blockLink(blockIds[0]))
1232
- },
1233
- {
1234
- id: "move-up",
1235
- label: labels.moveUp,
1236
- icon: ArrowUp,
1237
- shortcut: "Mod-Shift-ArrowUp",
1238
- run: (controller, blockIds) => runOnBlocks(controller, blockIds, chunkFK7EBZPX_cjs.moveBlocksUp),
1239
- available: applies(chunkFK7EBZPX_cjs.moveBlocksUp)
1240
- },
1241
- {
1242
- id: "move-down",
1243
- label: labels.moveDown,
1244
- icon: ArrowDown,
1245
- shortcut: "Mod-Shift-ArrowDown",
1246
- run: (controller, blockIds) => runOnBlocks(controller, blockIds, chunkFK7EBZPX_cjs.moveBlocksDown),
1247
- available: applies(chunkFK7EBZPX_cjs.moveBlocksDown)
1248
- },
1249
- {
1250
- id: "delete",
1251
- label: labels.delete,
1252
- icon: Trash,
1253
- destructive: true,
1254
- run: (controller, blockIds) => runOnBlocks(controller, blockIds, chunkFK7EBZPX_cjs.deleteBlocks)
1255
- }
1256
- ];
1257
- }
1258
1131
 
1259
1132
  // src/gutter/block-menu-state.ts
1260
1133
  var targetsOf = (state, blockId) => ({
1261
- blockIds: menuTargets(state, blockId),
1134
+ blockIds: handleTargets(state, blockId),
1262
1135
  styleIds: styleTargets(state, blockId)
1263
1136
  });
1264
1137
  function openBlockMenu(store, state, blockId) {
@@ -1283,24 +1156,54 @@ var TONE_ICONS = {
1283
1156
  function calloutIcon(attrs, tone) {
1284
1157
  return typeof attrs.icon === "string" && attrs.icon !== "" ? attrs.icon : TONE_ICONS[tone];
1285
1158
  }
1159
+ function iconTellsTone(attrs, tone) {
1160
+ return calloutIcon(attrs, tone) === TONE_ICONS[tone];
1161
+ }
1286
1162
  function MenuIcon({ icon: Icon2 }) {
1287
1163
  return /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(Icon2, { className: "size-4" }) });
1288
1164
  }
1165
+
1166
+ // src/gutter/run-on-blocks.ts
1167
+ function settle(tr, before) {
1168
+ const { doc } = tr;
1169
+ const { schema } = tr.before;
1170
+ const result = tr.selection;
1171
+ if (result?.kind !== "blocks" || before?.kind === "blocks") return result;
1172
+ const ids = chunkZ5EEDCRI_cjs.selectedBlockIds(doc, result);
1173
+ const head = chunkZ5EEDCRI_cjs.headBlockId(before);
1174
+ if (head !== null && ids.includes(head) && chunkZ5EEDCRI_cjs.validateSelection(doc, schema, before) === before) return before;
1175
+ return chunkZ5EEDCRI_cjs.endOf(doc, schema, ids[0]);
1176
+ }
1177
+ function runOnBlocks(controller, blockIds, command) {
1178
+ const { state } = controller.editor;
1179
+ const ids = blockIds.filter((id) => state.doc.index.has(id));
1180
+ if (ids.length === 0) return false;
1181
+ const before = state.selection;
1182
+ const exact = chunk4JDRDPLC_cjs.sameItems(chunkZ5EEDCRI_cjs.selectedBlockIds(state.doc, before), ids);
1183
+ const ran = exact ? before : chunkZ5EEDCRI_cjs.blockRangeSelection(ids[0], ids[ids.length - 1]);
1184
+ const { ok, tr } = chunkZ5EEDCRI_cjs.runCommands(state, [chunkZ5EEDCRI_cjs.setSelection(ran), command]);
1185
+ const result = ok && tr !== null ? tr : new chunkZ5EEDCRI_cjs.Transaction(state).setSelection(ran);
1186
+ const settled = exact ? result.selection : settle(result, before);
1187
+ if (!chunkZ5EEDCRI_cjs.sameSelection(settled, result.selection)) result.setSelection(settled);
1188
+ if (result.docChanged || !chunkZ5EEDCRI_cjs.sameSelection(result.selection, before)) controller.dispatch(result);
1189
+ return ok;
1190
+ }
1191
+ var noChange = () => true;
1289
1192
  var sharedTone = (blocks) => {
1290
- const tones = new Set(blocks.map((block) => chunk2QKFRKOP_cjs.calloutTone(block.attrs.tone)));
1193
+ const tones = new Set(blocks.map((block) => chunkQFGWSFOI_cjs.calloutTone(block.attrs.tone)));
1291
1194
  return tones.size === 1 ? [...tones][0] : "";
1292
1195
  };
1293
1196
  function CalloutMenus({ kit, controller, blocks }) {
1294
1197
  const labels = useLabels();
1295
1198
  const blockIds = blocks.map((block) => block.id);
1296
- const apply = (patch) => runOnBlocks(controller, blockIds, chunkFK7EBZPX_cjs.setAttrs(patch));
1199
+ const apply = (patch) => runOnBlocks(controller, blockIds, chunkZ5EEDCRI_cjs.setAttrs(patch));
1297
1200
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1298
1201
  /* @__PURE__ */ jsxRuntime.jsxs(kit.Sub, { children: [
1299
1202
  /* @__PURE__ */ jsxRuntime.jsxs(kit.SubTrigger, { children: [
1300
1203
  /* @__PURE__ */ jsxRuntime.jsx(MenuIcon, { icon: Droplet }),
1301
1204
  labels.tone
1302
1205
  ] }),
1303
- /* @__PURE__ */ jsxRuntime.jsx(kit.SubContent, { children: /* @__PURE__ */ jsxRuntime.jsx(kit.RadioGroup, { value: sharedTone(blocks), onValueChange: (tone) => apply({ tone }), children: chunk2QKFRKOP_cjs.CALLOUT_TONES.map((tone) => /* @__PURE__ */ jsxRuntime.jsx(kit.RadioItem, { value: tone, indicator: /* @__PURE__ */ jsxRuntime.jsx(Check, { className: "size-4" }), children: labels.calloutTones[tone] }, tone)) }) })
1206
+ /* @__PURE__ */ jsxRuntime.jsx(kit.SubContent, { children: /* @__PURE__ */ jsxRuntime.jsx(kit.RadioGroup, { value: sharedTone(blocks), onValueChange: (tone) => apply({ tone }), children: chunkQFGWSFOI_cjs.CALLOUT_TONES.map((tone) => /* @__PURE__ */ jsxRuntime.jsx(kit.RadioItem, { value: tone, indicator: /* @__PURE__ */ jsxRuntime.jsx(Check, { className: "size-4" }), children: labels.calloutTones[tone] }, tone)) }) })
1304
1207
  ] }),
1305
1208
  /* @__PURE__ */ jsxRuntime.jsxs(kit.Sub, { children: [
1306
1209
  /* @__PURE__ */ jsxRuntime.jsxs(kit.SubTrigger, { children: [
@@ -1313,11 +1216,11 @@ function CalloutMenus({ kit, controller, blocks }) {
1313
1216
  }
1314
1217
  function sharedColor(blocks, key) {
1315
1218
  const coloured = blocks.filter((block) => block.type === "MARKDOWN");
1316
- const colors = new Set(coloured.map((block) => chunkI7X7B3WD_cjs.paletteOrUndefined(block.attrs[key]) ?? "default"));
1219
+ const colors = new Set(coloured.map((block) => chunk4JDRDPLC_cjs.paletteOrUndefined(block.attrs[key]) ?? "default"));
1317
1220
  return colors.size === 1 ? [...colors][0] : "";
1318
1221
  }
1319
1222
  var swatches = {
1320
- color: (name) => /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", "data-color": name, className: "w-4 text-center font-medium", children: "A" }),
1223
+ color: (name, labels) => /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", "data-color": name, className: "w-4 text-center font-medium", children: labels.textColorSample ?? englishLabels.textColorSample }),
1321
1224
  background: (name) => /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", "data-bg": name, className: "size-4 rounded-sm border border-border" })
1322
1225
  };
1323
1226
  function ColorGroups({ kit, value, onPick }) {
@@ -1326,8 +1229,8 @@ function ColorGroups({ kit, value, onPick }) {
1326
1229
  const backgroundTitle = React7.useId();
1327
1230
  const group = (key, title, titleId) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1328
1231
  /* @__PURE__ */ jsxRuntime.jsx(kit.Label, { id: titleId, children: title }),
1329
- /* @__PURE__ */ jsxRuntime.jsx(kit.RadioGroup, { "aria-labelledby": titleId, value: value[key], onValueChange: (name) => onPick(key, name), children: chunkI7X7B3WD_cjs.PALETTE.map((name) => /* @__PURE__ */ jsxRuntime.jsxs(kit.RadioItem, { value: name, indicator: /* @__PURE__ */ jsxRuntime.jsx(Check, { className: "size-4" }), children: [
1330
- swatches[key](name),
1232
+ /* @__PURE__ */ jsxRuntime.jsx(kit.RadioGroup, { "aria-labelledby": titleId, value: value[key], onValueChange: (name) => onPick(key, name), children: chunk4JDRDPLC_cjs.PALETTE.map((name) => /* @__PURE__ */ jsxRuntime.jsxs(kit.RadioItem, { value: name, indicator: /* @__PURE__ */ jsxRuntime.jsx(Check, { className: "size-4" }), children: [
1233
+ swatches[key](name, labels),
1331
1234
  labels.colors[name]
1332
1235
  ] }, name)) })
1333
1236
  ] });
@@ -1341,7 +1244,7 @@ function ColorMenu({ kit, controller, blocks }) {
1341
1244
  const labels = useLabels();
1342
1245
  const blockIds = blocks.map((block) => block.id);
1343
1246
  const value = { color: sharedColor(blocks, "color"), background: sharedColor(blocks, "background") };
1344
- const pick = (key, name) => runOnBlocks(controller, blockIds, chunkFK7EBZPX_cjs.setBlockColor({ [key]: name === "default" ? null : name }));
1247
+ const pick = (key, name) => runOnBlocks(controller, blockIds, chunkZ5EEDCRI_cjs.setBlockColor({ [key]: name === "default" ? null : name }));
1345
1248
  return /* @__PURE__ */ jsxRuntime.jsxs(kit.Sub, { children: [
1346
1249
  /* @__PURE__ */ jsxRuntime.jsxs(kit.SubTrigger, { children: [
1347
1250
  /* @__PURE__ */ jsxRuntime.jsx(MenuIcon, { icon: Palette }),
@@ -1372,7 +1275,7 @@ function modifierText(modifier, isMac, { keyNames }) {
1372
1275
  return Object.hasOwn(MODIFIERS, name) ? keyNames[MODIFIERS[name]] : capitalized(modifier);
1373
1276
  }
1374
1277
  function formatShortcut(shortcut, platform = {}) {
1375
- const { isMac = chunkFK7EBZPX_cjs.detectMac(), labels = englishLabels } = platform;
1278
+ const { isMac = chunkZ5EEDCRI_cjs.detectMac(), labels = englishLabels } = platform;
1376
1279
  const parts = shortcut.split(shortcut.includes("+") ? "+" : "-");
1377
1280
  const key = parts.pop() ?? shortcut;
1378
1281
  const written = [...parts.map((modifier) => modifierText(modifier, isMac, labels)), keyText(key, labels)];
@@ -1452,21 +1355,21 @@ function languageOptions(query, plainText) {
1452
1355
  }
1453
1356
 
1454
1357
  // src/blocks/insert-blocks.ts
1455
- var isEmptyParagraph = (schema, block) => chunkFK7EBZPX_cjs.isParagraph(block) && chunkFK7EBZPX_cjs.regionLength(block, chunkFK7EBZPX_cjs.primaryRegion(schema, block) ?? "") === 0;
1358
+ var isEmptyParagraph = (schema, block) => chunkZ5EEDCRI_cjs.isParagraph(block) && chunkZ5EEDCRI_cjs.regionLength(block, chunkZ5EEDCRI_cjs.primaryRegion(schema, block) ?? "") === 0;
1456
1359
  function fresh(schema, { id, type, attrs, regions }, indent3) {
1457
- const block = chunkFK7EBZPX_cjs.createBlock(schema, type, attrs, indent3, id);
1360
+ const block = chunkZ5EEDCRI_cjs.createBlock(schema, type, attrs, indent3, id);
1458
1361
  return regions === void 0 ? block : { ...block, regions };
1459
1362
  }
1460
1363
  function landOnLine(tr, schema, index, indent3) {
1461
1364
  const next = tr.doc.blocks[index];
1462
1365
  const reused = next !== void 0 && next.indent === indent3 && isEmptyParagraph(schema, next);
1463
- const line = reused ? next : chunkFK7EBZPX_cjs.createBlock(schema, schema.defaultTextBlock, {}, indent3);
1464
- if (!reused) tr.step(chunkFK7EBZPX_cjs.insertBlocksStep(index, [line]));
1465
- tr.setSelection(chunkFK7EBZPX_cjs.startOf(tr.doc, schema, line.id));
1366
+ const line = reused ? next : chunkZ5EEDCRI_cjs.createBlock(schema, schema.defaultTextBlock, {}, indent3);
1367
+ if (!reused) tr.step(chunkZ5EEDCRI_cjs.insertBlocksStep(index, [line]));
1368
+ tr.setSelection(chunkZ5EEDCRI_cjs.startOf(tr.doc, schema, line.id));
1466
1369
  }
1467
1370
  function land(tr, schema, landing, first, after) {
1468
- if (landing === "text") tr.setSelection(chunkFK7EBZPX_cjs.startOf(tr.doc, schema, first.id));
1469
- if (landing === "editor") tr.setSelection(chunkFK7EBZPX_cjs.nodeSelection(first.id)).setMeta(OPEN_EDITOR, first.id);
1371
+ if (landing === "text") tr.setSelection(chunkZ5EEDCRI_cjs.startOf(tr.doc, schema, first.id));
1372
+ if (landing === "editor") tr.setSelection(chunkZ5EEDCRI_cjs.nodeSelection(first.id)).setMeta(OPEN_EDITOR, first.id);
1470
1373
  if (landing === "line") landOnLine(tr, schema, after, first.indent);
1471
1374
  }
1472
1375
  function anchorIndex(doc, id) {
@@ -1476,24 +1379,24 @@ function anchorIndex(doc, id) {
1476
1379
  function insertBlocks(blocks, landing, anchorId) {
1477
1380
  return (state, dispatch) => {
1478
1381
  const { doc, schema } = state;
1479
- const id = anchorId === void 0 ? chunkFK7EBZPX_cjs.headBlockId(state.selection) : anchorId;
1382
+ const id = anchorId === void 0 ? chunkZ5EEDCRI_cjs.headBlockId(state.selection) : anchorId;
1480
1383
  const index = anchorIndex(doc, id);
1481
1384
  if (index === void 0 || blocks.length === 0) return false;
1482
1385
  const anchor = id === null ? void 0 : doc.blocks[index];
1483
1386
  const created2 = blocks.map((block) => fresh(schema, block, anchor?.indent ?? 0));
1484
- const tr = new chunkFK7EBZPX_cjs.Transaction(state);
1387
+ const tr = new chunkZ5EEDCRI_cjs.Transaction(state);
1485
1388
  const empty = anchor !== void 0 && landing !== "stay" && isEmptyParagraph(schema, anchor);
1486
1389
  if (empty && landing === "line") {
1487
- tr.step(chunkFK7EBZPX_cjs.insertBlocksStep(index, created2)).setSelection(chunkFK7EBZPX_cjs.startOf(tr.doc, schema, anchor.id));
1390
+ tr.step(chunkZ5EEDCRI_cjs.insertBlocksStep(index, created2)).setSelection(chunkZ5EEDCRI_cjs.startOf(tr.doc, schema, anchor.id));
1488
1391
  } else if (empty) {
1489
1392
  const [first, ...rest] = created2;
1490
1393
  const filled = { ...first, id: anchor.id };
1491
- tr.step(chunkFK7EBZPX_cjs.replaceBlockStep(filled));
1492
- if (rest.length > 0) tr.step(chunkFK7EBZPX_cjs.insertBlocksStep(index + 1, rest));
1394
+ tr.step(chunkZ5EEDCRI_cjs.replaceBlockStep(filled));
1395
+ if (rest.length > 0) tr.step(chunkZ5EEDCRI_cjs.insertBlocksStep(index + 1, rest));
1493
1396
  land(tr, schema, landing, filled, index + created2.length);
1494
1397
  } else {
1495
- const at = anchor === void 0 ? 0 : chunkFK7EBZPX_cjs.descendantsEnd(doc, index);
1496
- tr.step(chunkFK7EBZPX_cjs.insertBlocksStep(at, created2));
1398
+ const at = anchor === void 0 ? 0 : chunkZ5EEDCRI_cjs.descendantsEnd(doc, index);
1399
+ tr.step(chunkZ5EEDCRI_cjs.insertBlocksStep(at, created2));
1497
1400
  land(tr, schema, landing, created2[0], at + created2.length);
1498
1401
  }
1499
1402
  dispatch?.(tr);
@@ -1506,7 +1409,7 @@ function landingFor(flow, hasEditor) {
1506
1409
  }
1507
1410
  function insertBlockToWrite(block, hasEditor, anchorId) {
1508
1411
  return (state, dispatch) => {
1509
- const landing = landingFor(chunkI7X7B3WD_cjs.specFor(state.schema, block.type).flow, hasEditor);
1412
+ const landing = landingFor(chunk4JDRDPLC_cjs.specFor(state.schema, block.type).flow, hasEditor);
1510
1413
  return insertBlocks([block], landing, anchorId)(state, dispatch);
1511
1414
  };
1512
1415
  }
@@ -1514,20 +1417,20 @@ function insertBlockToWrite(block, hasEditor, anchorId) {
1514
1417
  // src/blocks/code/code-actions.ts
1515
1418
  function writeCode(controller, blockId, code, group) {
1516
1419
  const { editor } = controller;
1517
- const block = chunkI7X7B3WD_cjs.blockById(editor.state.doc, blockId);
1420
+ const block = chunk4JDRDPLC_cjs.blockById(editor.state.doc, blockId);
1518
1421
  if (block === void 0 || block.attrs.code === code) return;
1519
- controller.dispatch(editor.createTransaction().step(chunkFK7EBZPX_cjs.setAttrsStep(blockId, { code })).setMeta(chunkFK7EBZPX_cjs.HISTORY_GROUP, group));
1422
+ controller.dispatch(editor.createTransaction().step(chunkZ5EEDCRI_cjs.setAttrsStep(blockId, { code })).setMeta(chunkZ5EEDCRI_cjs.HISTORY_GROUP, group));
1520
1423
  }
1521
1424
  function codeToParagraph(blockId) {
1522
1425
  return (state, dispatch) => {
1523
- const turn = chunkFK7EBZPX_cjs.setBlockType("MARKDOWN", { kind: "paragraph" });
1524
- const { ok, tr } = chunkFK7EBZPX_cjs.runCommands(state, [chunkFK7EBZPX_cjs.setSelection(chunkFK7EBZPX_cjs.nodeSelection(blockId)), turn]);
1426
+ const turn = chunkZ5EEDCRI_cjs.setBlockType("MARKDOWN", { kind: "paragraph" });
1427
+ const { ok, tr } = chunkZ5EEDCRI_cjs.runCommands(state, [chunkZ5EEDCRI_cjs.setSelection(chunkZ5EEDCRI_cjs.nodeSelection(blockId)), turn]);
1525
1428
  if (ok && tr !== null) dispatch?.(tr);
1526
1429
  return ok;
1527
1430
  };
1528
1431
  }
1529
1432
  function leaveCode(controller, blockId, direction, addsLine = false) {
1530
- const moved = chunk2QKFRKOP_cjs.moveToNeighbour(controller, blockId, direction);
1433
+ const moved = chunkN25EH2GZ_cjs.moveToNeighbour(controller, blockId, direction);
1531
1434
  if (!moved && !(addsLine && controller.run(insertBlocks([{ type: "MARKDOWN" }], "text", blockId)))) return false;
1532
1435
  controller.focus();
1533
1436
  return true;
@@ -1605,8 +1508,8 @@ var MOD_KEYS = [
1605
1508
  ["Mod-a", ({ value, start, end }) => start === 0 && end === value.length ? { kind: "selectAll" } : null]
1606
1509
  ];
1607
1510
  function codeKeyAction(event, field, isMac) {
1608
- const name = chunkFK7EBZPX_cjs.keyName(event, isMac);
1609
- const modKey = MOD_KEYS.find(([key]) => chunkFK7EBZPX_cjs.normalizeKey(key, isMac) === name);
1511
+ const name = chunkZ5EEDCRI_cjs.keyName(event, isMac);
1512
+ const modKey = MOD_KEYS.find(([key]) => chunkZ5EEDCRI_cjs.normalizeKey(key, isMac) === name);
1610
1513
  const handler = modKey?.[1] ?? (Object.hasOwn(KEYS, name) ? KEYS[name] : void 0);
1611
1514
  return handler?.(field) ?? null;
1612
1515
  }
@@ -1648,13 +1551,16 @@ function selectAndFocus({ controller }, command) {
1648
1551
  controller.focus();
1649
1552
  return true;
1650
1553
  }
1651
- function travel({ controller, blockId, area }, command) {
1554
+ function travel({ controller, blockId, area, reveal }, command) {
1652
1555
  const before = area.value;
1653
1556
  controller.run(command);
1654
1557
  const { selection } = controller.editor.state;
1655
- const edit2 = chunk2QKFRKOP_cjs.textDiff(before, area.value);
1558
+ const edit2 = chunkN25EH2GZ_cjs.textDiff(before, area.value);
1656
1559
  if (selection?.kind !== "node" || selection.block !== blockId) controller.focus();
1657
- else if (edit2 !== null) area.setSelectionRange(edit2.from + edit2.insert.length, edit2.from + edit2.insert.length);
1560
+ else if (edit2 !== null) {
1561
+ area.setSelectionRange(edit2.from + edit2.insert.length, edit2.from + edit2.insert.length);
1562
+ reveal();
1563
+ }
1658
1564
  return true;
1659
1565
  }
1660
1566
  function runCodeAction(action, context) {
@@ -1668,13 +1574,13 @@ function runCodeAction(action, context) {
1668
1574
  case "paragraph":
1669
1575
  return selectAndFocus(context, codeToParagraph(blockId));
1670
1576
  case "undo":
1671
- return travel(context, chunkFK7EBZPX_cjs.undo);
1577
+ return travel(context, chunkZ5EEDCRI_cjs.undo);
1672
1578
  case "redo":
1673
- return travel(context, chunkFK7EBZPX_cjs.redo);
1579
+ return travel(context, chunkZ5EEDCRI_cjs.redo);
1674
1580
  case "selectAll":
1675
- return selectAndFocus(context, chunkFK7EBZPX_cjs.selectAll);
1581
+ return selectAndFocus(context, chunkZ5EEDCRI_cjs.selectAll);
1676
1582
  case "escape":
1677
- return selectAndFocus(context, chunkFK7EBZPX_cjs.setSelection(chunkFK7EBZPX_cjs.blockRangeSelection(blockId)));
1583
+ return selectAndFocus(context, chunkZ5EEDCRI_cjs.setSelection(chunkZ5EEDCRI_cjs.blockRangeSelection(blockId)));
1678
1584
  case "controls":
1679
1585
  context.focusControls();
1680
1586
  return true;
@@ -1737,18 +1643,18 @@ function CodeEditor({ block, controller, field, focusControls }) {
1737
1643
  useTypeIntoCode(controller, blockId, field, edit2);
1738
1644
  const onKeyDown = (event) => {
1739
1645
  const area = event.currentTarget;
1740
- if (chunk2QKFRKOP_cjs.isComposingKey(controller, event.nativeEvent)) return;
1646
+ if (chunkN25EH2GZ_cjs.isComposingKey(controller, event.nativeEvent)) return;
1741
1647
  const action = codeKeyAction(event, fieldOf(area), controller.isMac);
1742
- if (action !== null && runCodeAction(action, { controller, blockId, area, edit: edit2, focusControls })) event.preventDefault();
1648
+ if (action !== null && runCodeAction(action, { controller, blockId, area, edit: edit2, reveal, focusControls })) event.preventDefault();
1743
1649
  };
1744
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ...{ [chunk2QKFRKOP_cjs.NATIVE_INTERACTION]: "" }, dir: "ltr", className: "quoin-code-scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "quoin-code-lines", children: [
1650
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...{ [chunkN25EH2GZ_cjs.NATIVE_INTERACTION]: "" }, dir: "ltr", className: "quoin-code-scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "quoin-code-lines", children: [
1745
1651
  /* @__PURE__ */ jsxRuntime.jsx(
1746
1652
  HighlightOverlay,
1747
1653
  {
1748
1654
  "aria-hidden": "true",
1749
1655
  drawnRef: drawn,
1750
- code: chunkI7X7B3WD_cjs.str(block.attrs.code),
1751
- language: chunkI7X7B3WD_cjs.str(block.attrs.language)
1656
+ code: chunk4JDRDPLC_cjs.str(block.attrs.code),
1657
+ language: chunk4JDRDPLC_cjs.str(block.attrs.language)
1752
1658
  }
1753
1659
  ),
1754
1660
  /* @__PURE__ */ jsxRuntime.jsx("span", { ref: mark, "aria-hidden": "true", className: "quoin-code-caret" }),
@@ -1758,7 +1664,7 @@ function CodeEditor({ block, controller, field, focusControls }) {
1758
1664
  ref: field,
1759
1665
  "aria-label": labels.codeText,
1760
1666
  "aria-keyshortcuts": "Alt+F10",
1761
- value: chunkI7X7B3WD_cjs.str(block.attrs.code),
1667
+ value: chunk4JDRDPLC_cjs.str(block.attrs.code),
1762
1668
  wrap: "off",
1763
1669
  tabIndex: -1,
1764
1670
  spellCheck: false,
@@ -1777,6 +1683,38 @@ function CodeEditor({ block, controller, field, focusControls }) {
1777
1683
  )
1778
1684
  ] }) });
1779
1685
  }
1686
+
1687
+ // src/gutter/copy-text.ts
1688
+ function copyThroughTextArea(text2, doc) {
1689
+ const previous = doc.activeElement instanceof HTMLElement ? doc.activeElement : null;
1690
+ const area = doc.createElement("textarea");
1691
+ area.value = text2;
1692
+ area.readOnly = true;
1693
+ area.setAttribute("aria-hidden", "true");
1694
+ area.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none";
1695
+ const beside = previous !== null && previous !== doc.body ? previous.parentElement : null;
1696
+ (beside ?? doc.body).append(area);
1697
+ area.focus({ preventScroll: true });
1698
+ area.select();
1699
+ try {
1700
+ return doc.execCommand("copy");
1701
+ } catch {
1702
+ return false;
1703
+ } finally {
1704
+ area.remove();
1705
+ previous?.focus({ preventScroll: true });
1706
+ }
1707
+ }
1708
+ async function copyText(text2, doc = document) {
1709
+ const clipboard = doc.defaultView?.navigator.clipboard;
1710
+ if (clipboard === void 0) return copyThroughTextArea(text2, doc);
1711
+ try {
1712
+ await clipboard.writeText(text2);
1713
+ return true;
1714
+ } catch {
1715
+ return copyThroughTextArea(text2, doc);
1716
+ }
1717
+ }
1780
1718
  var BASE = [
1781
1719
  "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,background-color,border-color,box-shadow,opacity] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 shrink-0",
1782
1720
  FOCUS_RING
@@ -1811,7 +1749,7 @@ var Button = React7__namespace.forwardRef(function Button2({ className, variant
1811
1749
  var COPIED_FOR_MS = 1500;
1812
1750
  function CopyCodeButton({ code, controller }) {
1813
1751
  const labels = useLabels();
1814
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
1752
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
1815
1753
  const [copied, setCopied] = React7.useState(false);
1816
1754
  React7.useEffect(() => {
1817
1755
  if (!copied) return;
@@ -1873,10 +1811,12 @@ var PopoverAnchor = /* @__PURE__ */ React7__namespace.forwardRef(function Popove
1873
1811
  return /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Anchor, { ref, "data-slot": "popover-anchor", ...props });
1874
1812
  });
1875
1813
  var PopoverContent = /* @__PURE__ */ React7__namespace.forwardRef(function PopoverContent2({ className, align = "center", sideOffset = 4, ...props }, ref) {
1814
+ const dir = reactDirection.useDirection();
1876
1815
  return /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
1877
1816
  PopoverPrimitive__namespace.Content,
1878
1817
  {
1879
1818
  ref,
1819
+ dir,
1880
1820
  "data-slot": "popover-content",
1881
1821
  align,
1882
1822
  sideOffset,
@@ -1894,7 +1834,7 @@ var preventDefault = (event) => event.preventDefault();
1894
1834
 
1895
1835
  // src/blocks/text/set-block-attrs.ts
1896
1836
  function setBlockAttrs(controller, blockId, patch) {
1897
- if (!controller.readOnly) controller.run(chunk2QKFRKOP_cjs.onBlock(blockId, chunkFK7EBZPX_cjs.setAttrs(patch)));
1837
+ if (!controller.readOnly) controller.run(chunkN25EH2GZ_cjs.onBlock(blockId, chunkZ5EEDCRI_cjs.setAttrs(patch)));
1898
1838
  }
1899
1839
  var FIELD = `quoin-code-language quoin-code-control ${FOCUS_RING}`;
1900
1840
  var CONTENT = `${MENU_CONTENT} flex max-h-64 w-48 flex-col overflow-y-hidden`;
@@ -1910,7 +1850,7 @@ function LanguageCombobox({ blockId, controller, language: language2, field, onD
1910
1850
  const options = draft === null ? [] : languageOptions(draft === shown ? "" : draft, labels.plainText);
1911
1851
  const open = options.length > 0;
1912
1852
  const optionId = (index) => `${listboxId}-${index}`;
1913
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
1853
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
1914
1854
  list2.current?.querySelector('[aria-selected="true"]')?.scrollIntoView?.({ block: "nearest" });
1915
1855
  }, [active]);
1916
1856
  const close = () => {
@@ -1936,7 +1876,7 @@ function LanguageCombobox({ blockId, controller, language: language2, field, onD
1936
1876
  onDone();
1937
1877
  };
1938
1878
  const onKeyDown = (event) => {
1939
- if (chunk2QKFRKOP_cjs.isComposingKey(controller, event.nativeEvent)) return;
1879
+ if (chunkN25EH2GZ_cjs.isComposingKey(controller, event.nativeEvent)) return;
1940
1880
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1941
1881
  event.preventDefault();
1942
1882
  const step = event.key === "ArrowDown" ? 1 : -1;
@@ -2010,11 +1950,11 @@ function LanguageCombobox({ blockId, controller, language: language2, field, onD
2010
1950
  }
2011
1951
  var CodeBlock = ({ block, controller }) => {
2012
1952
  const labels = useLabels();
2013
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
1953
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
2014
1954
  const languageField = React7.useRef(null);
2015
1955
  const codeField = React7.useRef(null);
2016
- const code = chunkI7X7B3WD_cjs.str(block.attrs.code);
2017
- const language2 = chunkI7X7B3WD_cjs.str(block.attrs.language);
1956
+ const code = chunk4JDRDPLC_cjs.str(block.attrs.code);
1957
+ const language2 = chunk4JDRDPLC_cjs.str(block.attrs.language);
2018
1958
  const name = languageName(language2) ?? language2;
2019
1959
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": labels.blockNames.code, contentEditable: false, className: "quoin-code", children: [
2020
1960
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "quoin-code-header", children: [
@@ -2043,6 +1983,7 @@ var CodeBlock = ({ block, controller }) => {
2043
1983
  };
2044
1984
  function FilePickerButton({ label, accept, disabled, busy = false, onPick }) {
2045
1985
  const inputRef = React7__namespace.useRef(null);
1986
+ const wrapper = React7__namespace.useRef(null);
2046
1987
  const restoreFocus = React7__namespace.useRef(false);
2047
1988
  React7__namespace.useEffect(() => {
2048
1989
  if (disabled || !restoreFocus.current) return;
@@ -2050,34 +1991,49 @@ function FilePickerButton({ label, accept, disabled, busy = false, onPick }) {
2050
1991
  const { activeElement } = document;
2051
1992
  if (activeElement === null || activeElement === document.body) inputRef.current?.focus();
2052
1993
  }, [disabled]);
2053
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative inline-flex rounded-md has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-[var(--quoin-ring)]", children: [
2054
- /* @__PURE__ */ jsxRuntime.jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-hidden": "true", className: disabled || busy ? "pointer-events-none opacity-50" : void 0, children: [
2055
- /* @__PURE__ */ jsxRuntime.jsx(Upload, { "aria-hidden": "true" }),
2056
- label
2057
- ] }) }),
2058
- /* @__PURE__ */ jsxRuntime.jsx(
2059
- "input",
2060
- {
2061
- ref: inputRef,
2062
- type: "file",
2063
- accept,
2064
- "aria-label": label,
2065
- disabled,
2066
- "aria-disabled": busy || void 0,
2067
- className: "absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed aria-disabled:cursor-not-allowed",
2068
- onClick: (e) => {
2069
- if (busy) e.preventDefault();
2070
- },
2071
- onChange: (e) => {
2072
- const file = e.target.files?.[0];
2073
- e.target.value = "";
2074
- if (!file || busy) return;
2075
- restoreFocus.current = true;
2076
- onPick(file);
2077
- }
2078
- }
2079
- )
2080
- ] });
1994
+ React7__namespace.useEffect(() => {
1995
+ const doc = wrapper.current?.ownerDocument;
1996
+ const pressed = (event) => {
1997
+ if (!wrapper.current?.contains(event.target)) restoreFocus.current = false;
1998
+ };
1999
+ doc?.addEventListener("pointerdown", pressed, true);
2000
+ return () => doc?.removeEventListener("pointerdown", pressed, true);
2001
+ }, []);
2002
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2003
+ "span",
2004
+ {
2005
+ ref: wrapper,
2006
+ className: "relative inline-flex rounded-md has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-[var(--quoin-ring)]",
2007
+ children: [
2008
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-hidden": "true", className: disabled || busy ? "pointer-events-none opacity-50" : void 0, children: [
2009
+ /* @__PURE__ */ jsxRuntime.jsx(Upload, { "aria-hidden": "true" }),
2010
+ label
2011
+ ] }) }),
2012
+ /* @__PURE__ */ jsxRuntime.jsx(
2013
+ "input",
2014
+ {
2015
+ ref: inputRef,
2016
+ type: "file",
2017
+ accept,
2018
+ "aria-label": label,
2019
+ disabled,
2020
+ "aria-disabled": busy || void 0,
2021
+ className: "absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed aria-disabled:cursor-not-allowed",
2022
+ onClick: (e) => {
2023
+ if (busy) e.preventDefault();
2024
+ },
2025
+ onChange: (e) => {
2026
+ const file = e.target.files?.[0];
2027
+ e.target.value = "";
2028
+ if (!file || busy) return;
2029
+ restoreFocus.current = true;
2030
+ onPick(file);
2031
+ }
2032
+ }
2033
+ )
2034
+ ]
2035
+ }
2036
+ );
2081
2037
  }
2082
2038
 
2083
2039
  // src/blocks/void/interactive-target.ts
@@ -2112,7 +2068,7 @@ function selectHandlers(controller, blockId, root, onSelect) {
2112
2068
  return {
2113
2069
  onKeyDown(event) {
2114
2070
  const key = event.nativeEvent;
2115
- if (chunk2QKFRKOP_cjs.isComposingKey(controller, key)) return;
2071
+ if (chunkN25EH2GZ_cjs.isComposingKey(controller, key)) return;
2116
2072
  if (event.key === "Escape") select();
2117
2073
  else if (!(event.metaKey || event.ctrlKey) || event.target.matches("input:not([type=file])") || !controller.edit(() => controller.editor.handleKey(key)))
2118
2074
  return;
@@ -2124,6 +2080,29 @@ function selectHandlers(controller, blockId, root, onSelect) {
2124
2080
  }
2125
2081
  };
2126
2082
  }
2083
+
2084
+ // src/blocks/upload/upload-targets.ts
2085
+ var targets = /* @__PURE__ */ new WeakMap();
2086
+ function offerUploadTarget(controller, blockId, target) {
2087
+ const offered = targets.get(controller) ?? /* @__PURE__ */ new Map();
2088
+ targets.set(controller, offered.set(blockId, target));
2089
+ return () => {
2090
+ if (offered.get(blockId) === target) offered.delete(blockId);
2091
+ };
2092
+ }
2093
+ function fillSelectedBlock(controller, files) {
2094
+ const { selection } = controller.editor.state;
2095
+ const target = selection?.kind === "node" ? targets.get(controller)?.get(selection.block) : void 0;
2096
+ const taken = target && files.find((file) => target.takes(file));
2097
+ if (taken === void 0) return files;
2098
+ target.start(taken);
2099
+ return files.filter((file) => file !== taken);
2100
+ }
2101
+
2102
+ // src/upload/upload-error.ts
2103
+ var uploadError = (cause, failed) => cause instanceof Error && cause.message ? cause : new Error(failed);
2104
+
2105
+ // src/blocks/upload/use-block-upload.ts
2127
2106
  var IDLE = { busy: false, error: "" };
2128
2107
  function useBlockUpload(controller, blockId, area, uploader) {
2129
2108
  const { upload, resolveAssetUrl, onError, labels } = useQuoin();
@@ -2137,12 +2116,13 @@ function useBlockUpload(controller, blockId, area, uploader) {
2137
2116
  if (started.current === run) setState(next);
2138
2117
  };
2139
2118
  setState({ busy: true, error: "" });
2119
+ controller.announce(labels.uploading);
2140
2120
  const onLand = () => {
2141
2121
  if (area.current?.contains(area.current.ownerDocument.activeElement)) focusNode(controller, blockId);
2142
2122
  };
2143
2123
  uploader(controller, blockId, file, { upload, resolveAssetUrl, onLand }).then(
2144
2124
  () => settle2(IDLE),
2145
- (cause) => settle2({ busy: false, error: cause instanceof Error && cause.message || labels.uploadFailed })
2125
+ (cause) => settle2({ busy: false, error: uploadError(cause, labels.uploadFailed).message })
2146
2126
  );
2147
2127
  },
2148
2128
  [controller, blockId, area, uploader, upload, resolveAssetUrl, onError, labels]
@@ -2167,6 +2147,7 @@ function UploadArea(props) {
2167
2147
  element?.addEventListener("drop", onDrop);
2168
2148
  return () => element?.removeEventListener("drop", onDrop);
2169
2149
  }, [start, takes]);
2150
+ React7.useEffect(() => offerUploadTarget(controller, blockId, { takes, start }), [controller, blockId, takes, start]);
2170
2151
  const onPaste = (event) => {
2171
2152
  const file = fileIn(event.clipboardData, takes);
2172
2153
  if (file === void 0) return;
@@ -2187,7 +2168,7 @@ function UploadArea(props) {
2187
2168
  ] }),
2188
2169
  children,
2189
2170
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "quoin-block-message", children: upload ? hint : labels.uploadsUnavailable }),
2190
- /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: busy ? "quoin-block-message" : "sr-only", children: busy ? labels.uploading : "" }),
2171
+ busy && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "quoin-block-message", children: labels.uploading }),
2191
2172
  error !== "" && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "quoin-block-message text-destructive", children: error })
2192
2173
  ]
2193
2174
  }
@@ -2195,8 +2176,8 @@ function UploadArea(props) {
2195
2176
  }
2196
2177
  var CONTROL = "input:not([disabled]), button:not([disabled])";
2197
2178
  var typed = (event) => !event.ctrlKey && !event.metaKey && [...event.key].length === 1;
2198
- function useNodeKeys(controller, blockId, editable, opens, root, { enter, type } = {}) {
2199
- const { editing, stop } = useVoidEditing(controller, blockId, editable && opens);
2179
+ function useNodeKeys(controller, blockId, editable, opens2, root, { enter, type } = {}) {
2180
+ const { editing, stop } = useVoidEditing(controller, blockId, editable && opens2);
2200
2181
  React7.useEffect(() => {
2201
2182
  if (!editing) return;
2202
2183
  stop();
@@ -2216,7 +2197,7 @@ function useNodeKeys(controller, blockId, editable, opens, root, { enter, type }
2216
2197
  }, [controller, blockId, editable, type]);
2217
2198
  }
2218
2199
 
2219
- // src/menus/bubble/bubble-part.ts
2200
+ // src/toolbar/bubble-part.ts
2220
2201
  var BUBBLE_PART = "data-quoin-bubble";
2221
2202
  var partIds = /* @__PURE__ */ new WeakMap();
2222
2203
  var created = 0;
@@ -2252,16 +2233,16 @@ var kindOf = (value) => KINDS.find(([, pattern]) => pattern.test(value))?.[0];
2252
2233
  var fileIcon = (mime, filename) => kindOf(mime.toLowerCase()) ?? kindOf(/\.([^.]+)$/.exec(filename)?.[1].toLowerCase() ?? "") ?? FileIcon;
2253
2234
 
2254
2235
  // src/blocks/file/file-source.ts
2255
- var fileName = (attrs) => chunkI7X7B3WD_cjs.str(attrs.filename) || chunkI7X7B3WD_cjs.str(attrs.storageKey);
2236
+ var fileName = (attrs) => chunk4JDRDPLC_cjs.str(attrs.filename) || chunk4JDRDPLC_cjs.str(attrs.storageKey);
2256
2237
  var hasFile = (attrs) => fileName(attrs) !== "";
2257
2238
  function FileCard({ block, controller, readOnly }) {
2258
2239
  const { labels, resolveAssetUrl } = useQuoin();
2259
2240
  const card = React7.useRef(null);
2260
2241
  const { id, attrs } = block;
2261
2242
  const name = fileName(attrs);
2262
- const size = chunkI7X7B3WD_cjs.optionalNumber(attrs.size);
2263
- const href = assetHref(resolveAssetUrl, chunkI7X7B3WD_cjs.str(attrs.storageKey));
2264
- const Icon2 = fileIcon(chunkI7X7B3WD_cjs.str(attrs.mime), name);
2243
+ const size = chunk4JDRDPLC_cjs.optionalNumber(attrs.size);
2244
+ const href = assetHref(resolveAssetUrl, chunk4JDRDPLC_cjs.str(attrs.storageKey));
2245
+ const Icon2 = fileIcon(chunk4JDRDPLC_cjs.str(attrs.mime), name);
2265
2246
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: card, className: "quoin-file-card", ...selectHandlers(controller, id, card), children: [
2266
2247
  /* @__PURE__ */ jsxRuntime.jsx(Icon2, { "aria-hidden": "true", className: "quoin-file-icon" }),
2267
2248
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "quoin-file-name", children: name }),
@@ -2270,7 +2251,7 @@ function FileCard({ block, controller, readOnly }) {
2270
2251
  "a",
2271
2252
  {
2272
2253
  href,
2273
- download: chunkI7X7B3WD_cjs.str(attrs.filename),
2254
+ download: chunk4JDRDPLC_cjs.str(attrs.filename),
2274
2255
  target: "_blank",
2275
2256
  rel: "noopener",
2276
2257
  "aria-label": labels.downloadFile(name),
@@ -2296,21 +2277,22 @@ function claimUpload(controller, blockId) {
2296
2277
  }
2297
2278
  var isNewestUpload = (controller, blockId, token) => newestUploads.get(controller)?.get(blockId) === token;
2298
2279
  function assetOf(controller, blockId, type) {
2299
- const block = chunkI7X7B3WD_cjs.blockById(controller.editor.state.doc, blockId);
2280
+ const block = chunk4JDRDPLC_cjs.blockById(controller.editor.state.doc, blockId);
2300
2281
  if (block?.type !== type || controller.readOnly) return null;
2301
- return { url: chunkI7X7B3WD_cjs.str(block.attrs.url), storageKey: chunkI7X7B3WD_cjs.str(block.attrs.storageKey) };
2282
+ return { url: chunk4JDRDPLC_cjs.str(block.attrs.url), storageKey: chunk4JDRDPLC_cjs.str(block.attrs.storageKey) };
2302
2283
  }
2284
+ var isStored = (stored) => typeof stored?.storageKey === "string" && stored.storageKey !== "";
2303
2285
  var sameAsset = (a, b) => a !== null && b !== null && a.url === b.url && a.storageKey === b.storageKey;
2304
2286
  function writeAttrs(controller, blockId, patch, recorded) {
2305
2287
  const { editor } = controller;
2306
- const tr = editor.createTransaction().step(chunkFK7EBZPX_cjs.setAttrsStep(blockId, patch));
2288
+ const tr = editor.createTransaction().step(chunkZ5EEDCRI_cjs.setAttrsStep(blockId, patch));
2307
2289
  editor.dispatch(recorded ? tr : tr.setMeta("addToHistory", false));
2308
2290
  }
2309
2291
  async function uploadInto(controller, blockId, file, { type, category }, { upload, onLand }, land2) {
2310
2292
  const token = claimUpload(controller, blockId);
2311
2293
  const before = assetOf(controller, blockId, type);
2312
2294
  const stored = await upload(file, category);
2313
- if (typeof stored?.storageKey !== "string" || stored.storageKey === "") throw new Error();
2295
+ if (!isStored(stored)) throw new Error();
2314
2296
  if (!isNewestUpload(controller, blockId, token) || !sameAsset(assetOf(controller, blockId, type), before)) return;
2315
2297
  onLand?.();
2316
2298
  await land2(stored, token);
@@ -2334,7 +2316,7 @@ var uploadFile = (controller, blockId, file, host) => uploadInto(
2334
2316
  var anyFile = () => true;
2335
2317
  var FileBlock = ({ block, controller }) => {
2336
2318
  const labels = useLabels();
2337
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
2319
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
2338
2320
  const root = React7.useRef(null);
2339
2321
  const filled = hasFile(block.attrs);
2340
2322
  useNodeKeys(controller, block.id, !readOnly, !filled, root);
@@ -2360,7 +2342,7 @@ function useReplacePicker(controller, blockId, uploader, accept) {
2360
2342
  if (file === void 0 || !upload) return;
2361
2343
  controller.announce(labels.uploading);
2362
2344
  uploader(controller, blockId, file, { upload, resolveAssetUrl }).catch(
2363
- (cause) => onError(cause instanceof Error && cause.message ? cause : new Error(labels.uploadFailed))
2345
+ (cause) => onError(uploadError(cause, labels.uploadFailed))
2364
2346
  );
2365
2347
  };
2366
2348
  return {
@@ -2400,7 +2382,7 @@ function usePartAnchor(controller, blockId, selector) {
2400
2382
  // src/blocks/void/node-toolbar-items.ts
2401
2383
  var replaceItem = (title, run) => run ? [{ id: "replace", title, icon: Upload, run }] : [];
2402
2384
  function deleteBlock(controller, blockId) {
2403
- runOnBlocks(controller, [blockId], chunkFK7EBZPX_cjs.deleteBlocks);
2385
+ runOnBlocks(controller, [blockId], chunkZ5EEDCRI_cjs.deleteBlocks);
2404
2386
  controller.focus();
2405
2387
  }
2406
2388
  var deleteItem = (blockId, title) => ({
@@ -2410,37 +2392,10 @@ var deleteItem = (blockId, title) => ({
2410
2392
  run: (controller) => deleteBlock(controller, blockId)
2411
2393
  });
2412
2394
  var PRESSED = "aria-pressed:bg-accent aria-pressed:text-accent-foreground";
2413
- function markItem(mark, title, icon) {
2414
- return {
2415
- id: mark,
2416
- title,
2417
- icon,
2418
- isActive: (state) => chunkFK7EBZPX_cjs.isActive(state, mark),
2419
- run: (controller) => void controller.run(chunkFK7EBZPX_cjs.toggleMark(mark))
2420
- };
2421
- }
2422
- var linkItem = (labels, openLink) => ({
2423
- id: "link",
2424
- title: labels.link,
2425
- icon: Link,
2426
- run: openLink
2427
- });
2428
- function bubbleItems(labels, openLink) {
2429
- return {
2430
- inline: [
2431
- markItem("bold", labels.bold, Bold),
2432
- markItem("italic", labels.italic, Italic),
2433
- markItem("underline", labels.underline, Underline),
2434
- markItem("strike", labels.strike, Strikethrough),
2435
- markItem("code", labels.code, Code),
2436
- linkItem(labels, openLink)
2437
- ],
2438
- scripts: [markItem("sub", labels.subscript, Subscript), markItem("sup", labels.superscript, Superscript)]
2439
- };
2440
- }
2441
2395
  function ToolbarButton({ item, state, controller, buttonRef }) {
2442
2396
  if (item.available?.(state) === false) return null;
2443
2397
  const Icon2 = item.icon;
2398
+ const disabled = item.enabled?.(state) === false;
2444
2399
  return /* @__PURE__ */ jsxRuntime.jsx(
2445
2400
  Button,
2446
2401
  {
@@ -2450,8 +2405,12 @@ function ToolbarButton({ item, state, controller, buttonRef }) {
2450
2405
  size: "icon-sm",
2451
2406
  "aria-label": item.title,
2452
2407
  "aria-pressed": item.isActive?.(state),
2408
+ "aria-haspopup": item.opensDialog ? "dialog" : void 0,
2409
+ "aria-disabled": disabled || void 0,
2453
2410
  className: PRESSED,
2454
- onClick: () => item.run(controller),
2411
+ onClick: () => {
2412
+ if (!disabled) item.run(controller);
2413
+ },
2455
2414
  children: /* @__PURE__ */ jsxRuntime.jsx(Icon2, {})
2456
2415
  }
2457
2416
  );
@@ -2486,7 +2445,7 @@ function useRovingFocus() {
2486
2445
  },
2487
2446
  [syncTabStop]
2488
2447
  );
2489
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(syncTabStop);
2448
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(syncTabStop);
2490
2449
  const onFocus = (event) => {
2491
2450
  if (!owns(event.target)) return;
2492
2451
  current.current = event.target;
@@ -2495,7 +2454,7 @@ function useRovingFocus() {
2495
2454
  const onKeyDown = (event) => {
2496
2455
  const all = items();
2497
2456
  const index = all.indexOf(event.target);
2498
- const key = row.current === null ? event.key : chunk2QKFRKOP_cjs.arrowAsWritten(event.key, chunk2QKFRKOP_cjs.inlineDirection(row.current));
2457
+ const key = row.current === null ? event.key : chunkN25EH2GZ_cjs.arrowAsWritten(event.key, chunkN25EH2GZ_cjs.inlineDirection(row.current));
2499
2458
  if (index === -1 || !Object.hasOwn(MOVES, key)) return;
2500
2459
  event.preventDefault();
2501
2460
  all[MOVES[key](index, all.length)].focus();
@@ -2506,7 +2465,7 @@ function useRovingFocus() {
2506
2465
  // src/menus/floating/use-focus-key.ts
2507
2466
  var isFocusKey = (event) => event.key === "F10" && event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey;
2508
2467
  function useFocusKey(controller, focus) {
2509
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
2468
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
2510
2469
  const intercept = (event) => {
2511
2470
  if (!isFocusKey(event)) return false;
2512
2471
  event.preventDefault();
@@ -2518,11 +2477,11 @@ function useFocusKey(controller, focus) {
2518
2477
  }
2519
2478
  function NodeToolbar(props) {
2520
2479
  const { blockId, controller, label, anchor, items, next, buttonRefs, children, popover } = props;
2521
- const state = chunk2QKFRKOP_cjs.useEditorState(controller, chunk2QKFRKOP_cjs.wholeState);
2480
+ const state = chunkN25EH2GZ_cjs.useEditorState(controller, chunkN25EH2GZ_cjs.wholeState);
2522
2481
  const roving = useRovingFocus();
2523
2482
  useFocusKey(controller, roving.focusCurrent);
2524
2483
  const onKeyDown = (event) => {
2525
- if (!roving.owns(event.target) || chunk2QKFRKOP_cjs.isComposingKey(controller, event.nativeEvent)) return;
2484
+ if (!roving.owns(event.target) || chunkN25EH2GZ_cjs.isComposingKey(controller, event.nativeEvent)) return;
2526
2485
  const tab = event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey;
2527
2486
  const ahead = tab && !event.shiftKey && next !== void 0 ? blockPart(controller, blockId, next) : void 0;
2528
2487
  if (ahead !== void 0) ahead.focus();
@@ -2560,10 +2519,12 @@ function createSettled(controller) {
2560
2519
  let pressed = false;
2561
2520
  let composing = false;
2562
2521
  const inEditor = (event) => event.target instanceof Node && controller.root?.contains(event.target) === true;
2522
+ const release = () => void (pressed = false);
2563
2523
  const handlers = {
2564
2524
  pointerdown: (event) => void (pressed ||= inEditor(event)),
2565
- pointerup: () => void (pressed = false),
2566
- pointercancel: () => void (pressed = false),
2525
+ pointerup: release,
2526
+ pointercancel: release,
2527
+ dragend: release,
2567
2528
  compositionstart: (event) => void (composing ||= inEditor(event)),
2568
2529
  compositionend: () => void (composing = false),
2569
2530
  focusin: () => {
@@ -2584,9 +2545,27 @@ function createSettled(controller) {
2584
2545
  doc.addEventListener(type, listener, true);
2585
2546
  return () => doc.removeEventListener(type, listener, true);
2586
2547
  });
2548
+ const timers = /* @__PURE__ */ new Set();
2549
+ const afterView = () => {
2550
+ const timer = setTimeout(() => {
2551
+ timers.delete(timer);
2552
+ notify();
2553
+ }, 0);
2554
+ timers.add(timer);
2555
+ };
2556
+ const stopDoc = chunkN25EH2GZ_cjs.listen(doc, { compositionend: afterView });
2557
+ const onWindowBlur = () => {
2558
+ release();
2559
+ notify();
2560
+ };
2561
+ const stopWindow = doc.defaultView ? chunkN25EH2GZ_cjs.listen(doc.defaultView, { blur: onWindowBlur }) : () => {
2562
+ };
2587
2563
  const stopState = controller.editor.subscribe(() => notify());
2588
2564
  return () => {
2589
2565
  listeners2.forEach((remove) => remove());
2566
+ timers.forEach(clearTimeout);
2567
+ stopDoc();
2568
+ stopWindow();
2590
2569
  stopState();
2591
2570
  };
2592
2571
  }
@@ -2600,12 +2579,12 @@ function useSelectionSettled(controller) {
2600
2579
 
2601
2580
  // src/blocks/void/use-toolbar-block.ts
2602
2581
  var selectedBlock = (type, filled) => ({ doc, selection }) => {
2603
- const block = selection?.kind === "node" ? chunkI7X7B3WD_cjs.blockById(doc, selection.block) : void 0;
2582
+ const block = selection?.kind === "node" ? chunk4JDRDPLC_cjs.blockById(doc, selection.block) : void 0;
2604
2583
  return block?.type === type && filled(block.attrs) ? block.id : null;
2605
2584
  };
2606
2585
  function useToolbarBlock(controller, selected, keptFor = null) {
2607
- const blockId = chunk2QKFRKOP_cjs.useEditorState(controller, selected);
2608
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
2586
+ const blockId = chunkN25EH2GZ_cjs.useEditorState(controller, selected);
2587
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
2609
2588
  const settled = useSelectionSettled(controller);
2610
2589
  return blockId !== null && !readOnly && (settled || keptFor === blockId) ? blockId : null;
2611
2590
  }
@@ -2645,6 +2624,23 @@ function requestAlt(controller, blockId) {
2645
2624
  listeners.get(controller)?.(blockId);
2646
2625
  }
2647
2626
 
2627
+ // src/blocks/image/owed-size.ts
2628
+ var owedSizes = /* @__PURE__ */ new WeakMap();
2629
+ var showsKey = (controller, blockId, storageKey) => sameAsset(assetOf(controller, blockId, "IMAGE"), { url: "", storageKey });
2630
+ function writeSize(controller, blockId, measured) {
2631
+ if (!isNewestUpload(controller, blockId, measured.token)) return;
2632
+ if (showsKey(controller, blockId, measured.storageKey)) writeAttrs(controller, blockId, { ...measured.size }, false);
2633
+ else owedSizes.set(controller, (owedSizes.get(controller) ?? /* @__PURE__ */ new Map()).set(blockId, measured));
2634
+ }
2635
+ function payOwedSize(controller, blockId) {
2636
+ const owing = owedSizes.get(controller);
2637
+ const owed = owing?.get(blockId);
2638
+ if (owed === void 0 || !showsKey(controller, blockId, owed.storageKey)) return;
2639
+ owing.delete(blockId);
2640
+ const unsized = chunk4JDRDPLC_cjs.blockById(controller.editor.state.doc, blockId)?.attrs.width === void 0;
2641
+ if (unsized && isNewestUpload(controller, blockId, owed.token)) writeAttrs(controller, blockId, { ...owed.size }, false);
2642
+ }
2643
+
2648
2644
  // src/blocks/image/resize-width.ts
2649
2645
  var MIN_WIDTH = 10;
2650
2646
  var MAX_WIDTH = 100;
@@ -2684,7 +2680,7 @@ function ResizeHandles({ blockId, controller, frame, percent, centred, onPreview
2684
2680
  const value = percent ?? drawn;
2685
2681
  const write = (widthPercent) => setBlockAttrs(controller, blockId, { widthPercent });
2686
2682
  const onKeyDown = (event) => {
2687
- if (chunk2QKFRKOP_cjs.isComposingKey(controller, event.nativeEvent)) return;
2683
+ if (chunkN25EH2GZ_cjs.isComposingKey(controller, event.nativeEvent)) return;
2688
2684
  const next = keyedWidth(event.key, value);
2689
2685
  if (event.key === "Escape") controller.focus();
2690
2686
  else if (next === null) return;
@@ -2726,7 +2722,7 @@ function ResizeHandles({ blockId, controller, frame, percent, centred, onPreview
2726
2722
  "aria-valuetext": labels.widthValue(value),
2727
2723
  "data-side": side,
2728
2724
  className: "quoin-image-handle",
2729
- ...{ [chunk2QKFRKOP_cjs.NATIVE_INTERACTION]: "" },
2725
+ ...{ [chunkN25EH2GZ_cjs.NATIVE_INTERACTION]: "" },
2730
2726
  ...bubblePart(controller),
2731
2727
  onKeyDown,
2732
2728
  onPointerDown: onPointerDown(side),
@@ -2747,15 +2743,16 @@ function ImageFigure({ block, controller, readOnly }) {
2747
2743
  const [failed, setFailed] = React7.useState(null);
2748
2744
  const { id, attrs, regions } = block;
2749
2745
  const src = imageSource(attrs, resolveAssetUrl);
2750
- const alt = chunkI7X7B3WD_cjs.str(attrs.alt);
2746
+ const alt = chunk4JDRDPLC_cjs.str(attrs.alt);
2751
2747
  const name = alt || labels.imageNoAlt;
2752
- const percent = chunkI7X7B3WD_cjs.optionalNumber(attrs.widthPercent);
2748
+ const percent = chunk4JDRDPLC_cjs.optionalNumber(attrs.widthPercent);
2753
2749
  const width = preview ?? percent;
2754
2750
  const flagged = !readOnly && alt === "" && attrs.decorative !== true;
2755
2751
  const select = () => focusNode(controller, id);
2756
2752
  const badgeText = labels.noAltBadge ?? englishLabels.noAltBadge;
2757
2753
  const badgeName = labels.addAltText ?? englishLabels.addAltText;
2758
2754
  const translatedAlone = badgeText !== englishLabels.noAltBadge && badgeName === englishLabels.addAltText;
2755
+ React7.useEffect(() => payOwedSize(controller, id), [controller, id, attrs.storageKey]);
2759
2756
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2760
2757
  /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: frame, className: "quoin-image-frame", style: width === void 0 ? void 0 : { width: `${width}%` }, children: [
2761
2758
  src !== null && src !== failed ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -2763,8 +2760,8 @@ function ImageFigure({ block, controller, readOnly }) {
2763
2760
  {
2764
2761
  src,
2765
2762
  alt: name,
2766
- width: chunkI7X7B3WD_cjs.optionalNumber(attrs.width),
2767
- height: chunkI7X7B3WD_cjs.optionalNumber(attrs.height),
2763
+ width: chunk4JDRDPLC_cjs.optionalNumber(attrs.width),
2764
+ height: chunk4JDRDPLC_cjs.optionalNumber(attrs.height),
2768
2765
  draggable: false,
2769
2766
  onError: () => setFailed(src),
2770
2767
  onClick: select
@@ -2799,7 +2796,7 @@ function ImageFigure({ block, controller, readOnly }) {
2799
2796
  )
2800
2797
  ] }),
2801
2798
  (!readOnly || (regions.caption?.length ?? 0) > 0) && /* @__PURE__ */ jsxRuntime.jsx(
2802
- chunk2QKFRKOP_cjs.EditableRegion,
2799
+ chunkN25EH2GZ_cjs.EditableRegion,
2803
2800
  {
2804
2801
  block: id,
2805
2802
  region: "caption",
@@ -2812,18 +2809,20 @@ function ImageFigure({ block, controller, readOnly }) {
2812
2809
  }
2813
2810
 
2814
2811
  // src/menus/link/link-address.ts
2815
- var BARE_DOMAIN = /^[^\s/#?.]\S*\.\S*$/;
2816
- var EMAIL = /^[^\s@/]+@[^\s@/]+\.[^\s@/]+$/;
2812
+ var DOMAIN_START = /^[^/#?.]/;
2813
+ var EMAIL = /^[^@/]+@([^@/]+)$/;
2817
2814
  var WHITESPACE = /\s/;
2815
+ var isEmail = (address) => EMAIL.exec(address)?.[1].slice(1, -1).includes(".") === true;
2816
+ var isBareDomain = (address) => DOMAIN_START.test(address) && address.includes(".", 1);
2818
2817
  function scheme(address) {
2819
- if (EMAIL.test(address)) return `mailto:${address}`;
2820
- return BARE_DOMAIN.test(address) ? `https://${address}` : address;
2818
+ if (isEmail(address)) return `mailto:${address}`;
2819
+ return isBareDomain(address) ? `https://${address}` : address;
2821
2820
  }
2822
2821
  function linkAddress(typed2) {
2823
2822
  const address = typed2.trim();
2824
2823
  if (WHITESPACE.test(address)) return null;
2825
- const completed = chunkI7X7B3WD_cjs.SCHEME.test(address) ? address : scheme(address);
2826
- const href = chunkI7X7B3WD_cjs.sanitizeHref(completed);
2824
+ const completed = chunk4JDRDPLC_cjs.SCHEME.test(address) ? address : scheme(address);
2825
+ const href = chunk4JDRDPLC_cjs.sanitizeHref(completed);
2827
2826
  return href || null;
2828
2827
  }
2829
2828
  var Input = React7__namespace.forwardRef(function Input2({ className, type, ...props }, ref) {
@@ -2897,9 +2896,7 @@ var isImageFile = (file) => file.type.startsWith("image/");
2897
2896
  async function measureImage(controller, blockId, storageKey, resolveAssetUrl, token = claimUpload(controller, blockId)) {
2898
2897
  const src = assetHref(resolveAssetUrl, storageKey);
2899
2898
  const size = src === null ? null : await intrinsicSize(src);
2900
- if (size && isNewestUpload(controller, blockId, token) && sameAsset(assetOf(controller, blockId, "IMAGE"), { url: "", storageKey })) {
2901
- writeAttrs(controller, blockId, { ...size }, false);
2902
- }
2899
+ if (size) writeSize(controller, blockId, { storageKey, size, token });
2903
2900
  }
2904
2901
  var uploadImage = (controller, blockId, file, host) => uploadInto(controller, blockId, file, { type: "IMAGE", category: "image" }, host, async ({ storageKey }, token) => {
2905
2902
  writeAttrs(controller, blockId, { storageKey, url: void 0, width: void 0, height: void 0 }, true);
@@ -2929,8 +2926,8 @@ function ImageUploadArea({ blockId, controller }) {
2929
2926
  );
2930
2927
  }
2931
2928
  function captionEnd(controller, blockId) {
2932
- const caption = chunkI7X7B3WD_cjs.blockById(controller.editor.state.doc, blockId)?.regions.caption ?? [];
2933
- return { block: blockId, region: "caption", offset: chunkI7X7B3WD_cjs.inlineLength(caption) };
2929
+ const caption = chunk4JDRDPLC_cjs.blockById(controller.editor.state.doc, blockId)?.regions.caption ?? [];
2930
+ return { block: blockId, region: "caption", offset: chunk4JDRDPLC_cjs.inlineLength(caption) };
2934
2931
  }
2935
2932
  var plainEnter = (event) => event.key === "Enter" && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey;
2936
2933
  var menuOpenOn = (host) => host?.getAttribute("aria-expanded") === "true";
@@ -2942,7 +2939,7 @@ function useImageKeys(controller, blockId, picture, editable, root) {
2942
2939
  // then follows.
2943
2940
  enter() {
2944
2941
  const { editor } = controller;
2945
- editor.dispatch(editor.createTransaction().setSelection(chunkFK7EBZPX_cjs.textSelection(captionEnd(controller, blockId))));
2942
+ editor.dispatch(editor.createTransaction().setSelection(chunkZ5EEDCRI_cjs.textSelection(captionEnd(controller, blockId))));
2946
2943
  controller.focus();
2947
2944
  },
2948
2945
  type(key) {
@@ -2967,7 +2964,7 @@ function useImageKeys(controller, blockId, picture, editable, root) {
2967
2964
  }
2968
2965
  var ImageBlock = ({ block, controller }) => {
2969
2966
  const labels = useLabels();
2970
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
2967
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
2971
2968
  const root = React7.useRef(null);
2972
2969
  const picture = hasPicture(block.attrs);
2973
2970
  useImageKeys(controller, block.id, picture, !readOnly, root);
@@ -2980,17 +2977,39 @@ var ImageBlock = ({ block, controller }) => {
2980
2977
  "aria-label": labels.blockNames.image,
2981
2978
  contentEditable: false,
2982
2979
  className: "quoin-image",
2983
- ...picture ? { [chunk2QKFRKOP_cjs.NODE_ENTRY]: "" } : void 0,
2980
+ ...picture ? { [chunkN25EH2GZ_cjs.NODE_ENTRY]: "" } : void 0,
2984
2981
  children: picture ? /* @__PURE__ */ jsxRuntime.jsx(ImageFigure, { block, controller, readOnly }) : empty
2985
2982
  }
2986
2983
  );
2987
2984
  };
2985
+ function useClosing(controller, { store, onClose, onReturn }) {
2986
+ const closed = React7.useRef(false);
2987
+ const finish = (save, refocus = false) => {
2988
+ if (closed.current) return false;
2989
+ closed.current = true;
2990
+ if (save) store(refocus);
2991
+ return true;
2992
+ };
2993
+ const latestFinish = React7.useRef(finish);
2994
+ latestFinish.current = finish;
2995
+ React7.useEffect(() => {
2996
+ closed.current = false;
2997
+ return () => void latestFinish.current(true);
2998
+ }, []);
2999
+ const close = (save, refocus = true) => {
3000
+ if (!refocus) controller.readSelectionFromDom();
3001
+ if (!finish(save, refocus)) return;
3002
+ onClose();
3003
+ if (refocus) onReturn();
3004
+ };
3005
+ return { finish, close };
3006
+ }
2988
3007
  function useOutsidePress(element, ownerDocument, onPress) {
2989
3008
  const latest = React7.useRef(onPress);
2990
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
3009
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
2991
3010
  latest.current = onPress;
2992
3011
  });
2993
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
3012
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
2994
3013
  const doc = ownerDocument();
2995
3014
  const inside = (target) => element.current?.contains(target) === true;
2996
3015
  let touched = null;
@@ -3005,15 +3024,15 @@ function useOutsidePress(element, ownerDocument, onPress) {
3005
3024
  if (touched !== null && !inside(event.target)) latest.current(touched);
3006
3025
  touched = null;
3007
3026
  };
3008
- return chunk2QKFRKOP_cjs.listen(doc, { pointerdown: down, click }, true);
3027
+ return chunkN25EH2GZ_cjs.listen(doc, { pointerdown: down, click }, true);
3009
3028
  }, [element, ownerDocument]);
3010
3029
  }
3011
3030
  function storeAlt(controller, blockId, opened, patch) {
3012
3031
  const { editor } = controller;
3013
- const attrs = chunkI7X7B3WD_cjs.blockById(editor.state.doc, blockId)?.attrs ?? {};
3014
- const changed = chunkI7X7B3WD_cjs.str(attrs.alt) !== opened.alt || attrs.decorative === true !== opened.decorative;
3032
+ const attrs = chunk4JDRDPLC_cjs.blockById(editor.state.doc, blockId)?.attrs ?? {};
3033
+ const changed = chunk4JDRDPLC_cjs.str(attrs.alt) !== opened.alt || attrs.decorative === true !== opened.decorative;
3015
3034
  if (controller.readOnly || controller.root === null || changed) return;
3016
- chunk2QKFRKOP_cjs.onBlock(blockId, chunkFK7EBZPX_cjs.setAttrs(patch))(editor.state, (tr) => editor.dispatch(tr));
3035
+ chunkN25EH2GZ_cjs.onBlock(blockId, chunkZ5EEDCRI_cjs.setAttrs(patch))(editor.state, (tr) => editor.dispatch(tr));
3017
3036
  }
3018
3037
  function AltTextPopover({ blockId, controller, anchor, opener, alt, decorative, onClose }) {
3019
3038
  const labels = useLabels();
@@ -3022,29 +3041,18 @@ function AltTextPopover({ blockId, controller, anchor, opener, alt, decorative,
3022
3041
  const [marked, setMarked] = React7.useState(decorative);
3023
3042
  const fieldId = React7.useId();
3024
3043
  const hintId = `${fieldId}-hint`;
3025
- const closed = React7.useRef(false);
3026
- const finish = (store) => {
3027
- if (closed.current) return false;
3028
- closed.current = true;
3029
- if (store && (draft !== alt || marked !== decorative))
3030
- storeAlt(controller, blockId, opened, { alt: draft, decorative: marked || void 0 });
3031
- return true;
3032
- };
3033
- const latestFinish = React7.useRef(finish);
3034
- latestFinish.current = finish;
3035
- React7.useEffect(() => {
3036
- closed.current = false;
3037
- return () => void latestFinish.current(true);
3038
- }, []);
3044
+ const { finish, close } = useClosing(controller, {
3045
+ store() {
3046
+ if (draft !== alt || marked !== decorative) {
3047
+ storeAlt(controller, blockId, opened, { alt: draft, decorative: marked || void 0 });
3048
+ }
3049
+ },
3050
+ onClose,
3051
+ onReturn: () => controller.focus()
3052
+ });
3039
3053
  React7.useEffect(() => {
3040
3054
  if ((alt !== opened.alt || decorative !== opened.decorative) && finish(false)) onClose();
3041
3055
  });
3042
- const close = (store, refocus = true) => {
3043
- if (!refocus) controller.readSelectionFromDom();
3044
- if (!finish(store)) return;
3045
- onClose();
3046
- if (refocus) controller.focus();
3047
- };
3048
3056
  const content = React7.useRef(null);
3049
3057
  const ownerDocument = React7.useCallback(() => controller.root?.ownerDocument ?? document, [controller]);
3050
3058
  useOutsidePress(content, ownerDocument, (target) => {
@@ -3055,7 +3063,7 @@ function AltTextPopover({ blockId, controller, anchor, opener, alt, decorative,
3055
3063
  close(true);
3056
3064
  };
3057
3065
  const onKeyDown = (event) => {
3058
- if (event.key !== "Tab" || chunk2QKFRKOP_cjs.isComposingKey(controller, event.nativeEvent)) return;
3066
+ if (event.key !== "Tab" || chunkN25EH2GZ_cjs.isComposingKey(controller, event.nativeEvent)) return;
3059
3067
  const controls = [...event.currentTarget.elements];
3060
3068
  const next = controls[controls.indexOf(event.target) + (event.shiftKey ? -1 : 1)];
3061
3069
  event.preventDefault();
@@ -3083,7 +3091,7 @@ function AltTextPopover({ blockId, controller, anchor, opener, alt, decorative,
3083
3091
  onCloseAutoFocus: preventDefault,
3084
3092
  onEscapeKeyDown: (event) => {
3085
3093
  event.preventDefault();
3086
- if (!chunk2QKFRKOP_cjs.isComposingKey(controller, event)) close(false);
3094
+ if (!chunkN25EH2GZ_cjs.isComposingKey(controller, event)) close(false);
3087
3095
  },
3088
3096
  ...bubblePart(controller),
3089
3097
  children: /* @__PURE__ */ jsxRuntime.jsxs("form", { noValidate: true, className: "quoin-image-alt", onSubmit: save, onKeyDown, children: [
@@ -3124,6 +3132,21 @@ function AltTextPopover({ blockId, controller, anchor, opener, alt, decorative,
3124
3132
  }
3125
3133
  );
3126
3134
  }
3135
+ function around(a, b) {
3136
+ const left = Math.min(a.left, b.left);
3137
+ const top = Math.min(a.top, b.top);
3138
+ return new DOMRect(left, top, Math.max(a.right, b.right) - left, Math.max(a.bottom, b.bottom) - top);
3139
+ }
3140
+ function useAroundToolbar(part, inToolbar) {
3141
+ return React7.useMemo(() => {
3142
+ const measure2 = () => {
3143
+ const box = part.current.getBoundingClientRect();
3144
+ const toolbar = inToolbar.current?.closest('[role="toolbar"]')?.getBoundingClientRect();
3145
+ return toolbar === void 0 ? box : around(box, toolbar);
3146
+ };
3147
+ return { current: virtualAnchor(measure2, () => part.current.contextElement) };
3148
+ }, [part, inToolbar]);
3149
+ }
3127
3150
 
3128
3151
  // src/blocks/image/image-toolbar-items.ts
3129
3152
  var ALIGNS = [
@@ -3137,7 +3160,7 @@ function imageToolbarItems(blockId, labels, actions) {
3137
3160
  id: align,
3138
3161
  title: labels[label],
3139
3162
  icon,
3140
- isActive: (state) => (chunkI7X7B3WD_cjs.alignOrUndefined(chunkI7X7B3WD_cjs.blockById(state.doc, blockId)?.attrs.align) ?? "left") === align,
3163
+ isActive: (state) => (chunk4JDRDPLC_cjs.alignOrUndefined(chunk4JDRDPLC_cjs.blockById(state.doc, blockId)?.attrs.align) ?? "left") === align,
3141
3164
  run: (controller) => setBlockAttrs(controller, blockId, { align })
3142
3165
  })
3143
3166
  );
@@ -3179,11 +3202,12 @@ function ImageToolbarOverlay({ controller }) {
3179
3202
  }
3180
3203
  function ImageToolbar({ blockId, controller, editingAlt, onEditAlt }) {
3181
3204
  const { labels, resolveAssetUrl } = useQuoin();
3182
- const state = chunk2QKFRKOP_cjs.useEditorState(controller, chunk2QKFRKOP_cjs.wholeState);
3205
+ const state = chunkN25EH2GZ_cjs.useEditorState(controller, chunkN25EH2GZ_cjs.wholeState);
3183
3206
  const picker = useReplacePicker(controller, blockId, uploadImage, "image/*");
3184
3207
  const altButton = React7.useRef(null);
3185
3208
  const anchor = usePartAnchor(controller, blockId, ".quoin-image-frame");
3186
- const attrs = chunkI7X7B3WD_cjs.blockById(state.doc, blockId)?.attrs ?? {};
3209
+ const altAnchor = useAroundToolbar(anchor, altButton);
3210
+ const attrs = chunk4JDRDPLC_cjs.blockById(state.doc, blockId)?.attrs ?? {};
3187
3211
  const items = imageToolbarItems(blockId, labels, {
3188
3212
  replace: picker.open,
3189
3213
  editAlt: () => {
@@ -3207,9 +3231,9 @@ function ImageToolbar({ blockId, controller, editingAlt, onEditAlt }) {
3207
3231
  {
3208
3232
  blockId,
3209
3233
  controller,
3210
- anchor,
3234
+ anchor: altAnchor,
3211
3235
  opener: altButton,
3212
- alt: chunkI7X7B3WD_cjs.str(attrs.alt),
3236
+ alt: chunk4JDRDPLC_cjs.str(attrs.alt),
3213
3237
  decorative: attrs.decorative === true,
3214
3238
  onClose: () => onEditAlt(null)
3215
3239
  }
@@ -3337,7 +3361,7 @@ var DropdownMenuSubTrigger = /* @__PURE__ */ React7__namespace.forwardRef(functi
3337
3361
  ...props,
3338
3362
  children: [
3339
3363
  children,
3340
- /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, { className: "ml-auto size-4" })
3364
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, { className: "ms-auto size-4 rtl:rotate-180" })
3341
3365
  ]
3342
3366
  }
3343
3367
  );
@@ -3383,7 +3407,7 @@ var SHOWN_WITH_THE_BLOCK = [
3383
3407
  "opacity-0 group-hover/callout:opacity-100 group-focus-within/callout:opacity-100",
3384
3408
  "in-data-[selected=true]:opacity-100 pointer-coarse:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100"
3385
3409
  ].join(" ");
3386
- function CalloutToneMenu({ blockId, controller, tone }) {
3410
+ function CalloutToneMenu({ blockId, controller, tone, alwaysShown }) {
3387
3411
  const labels = useLabels();
3388
3412
  const name = labels.calloutTones[tone];
3389
3413
  return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
@@ -3395,7 +3419,7 @@ function CalloutToneMenu({ blockId, controller, tone }) {
3395
3419
  size: "xs",
3396
3420
  tabIndex: -1,
3397
3421
  "aria-label": labels.calloutToneMenu(name),
3398
- className: SHOWN_WITH_THE_BLOCK,
3422
+ className: alwaysShown ? void 0 : SHOWN_WITH_THE_BLOCK,
3399
3423
  children: name
3400
3424
  }
3401
3425
  ) }),
@@ -3404,20 +3428,21 @@ function CalloutToneMenu({ blockId, controller, tone }) {
3404
3428
  {
3405
3429
  value: tone,
3406
3430
  onValueChange: (value) => setBlockAttrs(controller, blockId, { tone: value }),
3407
- children: chunk2QKFRKOP_cjs.CALLOUT_TONES.map((option) => /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuRadioItem, { value: option, children: labels.calloutTones[option] }, option))
3431
+ children: chunkQFGWSFOI_cjs.CALLOUT_TONES.map((option) => /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuRadioItem, { value: option, children: labels.calloutTones[option] }, option))
3408
3432
  }
3409
3433
  ) })
3410
3434
  ] });
3411
3435
  }
3412
3436
  var CalloutBlock = ({ block, controller }) => {
3413
3437
  const labels = useLabels();
3414
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
3415
- const tone = chunk2QKFRKOP_cjs.calloutTone(block.attrs.tone);
3438
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
3439
+ const tone = chunkQFGWSFOI_cjs.calloutTone(block.attrs.tone);
3416
3440
  const icon = calloutIcon(block.attrs, tone);
3441
+ const toneInWords = !iconTellsTone(block.attrs, tone);
3417
3442
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("quoin-callout group/callout flex items-start gap-2", TONE[tone]), "data-tone": tone, children: [
3418
3443
  readOnly ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex size-6 items-center justify-center text-base", children: icon }) : /* @__PURE__ */ jsxRuntime.jsx(CalloutIconMenu, { blockId: block.id, controller, icon }),
3419
3444
  /* @__PURE__ */ jsxRuntime.jsx(
3420
- chunk2QKFRKOP_cjs.EditableRegion,
3445
+ chunkN25EH2GZ_cjs.EditableRegion,
3421
3446
  {
3422
3447
  block: block.id,
3423
3448
  region: "body",
@@ -3425,7 +3450,7 @@ var CalloutBlock = ({ block, controller }) => {
3425
3450
  className: "min-w-0 flex-1"
3426
3451
  }
3427
3452
  ),
3428
- !readOnly && /* @__PURE__ */ jsxRuntime.jsx(CalloutToneMenu, { blockId: block.id, controller, tone })
3453
+ readOnly ? toneInWords && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex h-6 items-center px-2 text-xs", children: labels.calloutTones[tone] }) : /* @__PURE__ */ jsxRuntime.jsx(CalloutToneMenu, { blockId: block.id, controller, tone, alwaysShown: toneInWords })
3429
3454
  ] });
3430
3455
  };
3431
3456
  var DividerBlock = ({ block, controller }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2", contentEditable: false, onClick: () => focusNode(controller, block.id), children: /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "border-t border-border" }) });
@@ -3473,92 +3498,23 @@ var COUNTERS = [decimal, lowerAlpha, lowerRoman];
3473
3498
  function bulletGlyph(depth) {
3474
3499
  return BULLETS[cycleOf(depth)];
3475
3500
  }
3476
- function orderedLabel(n, depth) {
3477
- return `${COUNTERS[cycleOf(depth)](n)}.`;
3478
- }
3479
- function ListMarker(props) {
3480
- const text2 = props.list === "bullet" ? bulletGlyph(props.depth) : orderedLabel(props.number, props.depth);
3481
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "quoin-marker", "aria-hidden": "true", children: text2 });
3482
- }
3483
- var REGION = "text";
3484
- var HEADING_LEVELS = ["mt-6 text-3xl", "mt-5 text-2xl", "mt-4 text-xl", "mt-3 text-lg", "mt-3 text-base", "mt-3 text-sm"];
3485
- var headingStyles = (level) => `quoin-heading mb-1 font-semibold leading-tight in-[[data-quoin-block]:first-child]:mt-0 ${HEADING_LEVELS[level - 1]}`;
3486
- function Heading({ block }) {
3487
- const labels = useLabels();
3488
- const level = chunkI7X7B3WD_cjs.headingLevel(block.attrs.level);
3489
- return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "heading", "aria-level": level, className: headingStyles(level), children: /* @__PURE__ */ jsxRuntime.jsx(chunk2QKFRKOP_cjs.EditableRegion, { block: block.id, region: REGION, label: labels.heading(level) }) });
3490
- }
3491
- function NumberedItem({ block, controller }) {
3492
- const labels = useLabels();
3493
- const number = chunk2QKFRKOP_cjs.useOrderedNumber(controller, block.id);
3494
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "quoin-list-item", "data-list": "ordered", children: [
3495
- /* @__PURE__ */ jsxRuntime.jsx(ListMarker, { list: "ordered", number, depth: block.indent }),
3496
- /* @__PURE__ */ jsxRuntime.jsx(chunk2QKFRKOP_cjs.EditableRegion, { block: block.id, region: REGION, label: labels.numberedItem(number) })
3497
- ] });
3498
- }
3499
- function TaskItem({ block, controller }) {
3500
- const labels = useLabels();
3501
- const hostId = chunk2QKFRKOP_cjs.useRegionDomId(block.id, REGION);
3502
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "quoin-task", "data-checked": block.attrs.checked === true, children: [
3503
- /* @__PURE__ */ jsxRuntime.jsx(chunk2QKFRKOP_cjs.TaskCheckbox, { block, controller, label: labels.task, labelledBy: hostId }),
3504
- /* @__PURE__ */ jsxRuntime.jsx(chunk2QKFRKOP_cjs.EditableRegion, { id: hostId, block: block.id, region: REGION, label: labels.task })
3505
- ] });
3506
- }
3507
- var MarkdownBlock = (props) => {
3508
- const { block } = props;
3509
- const labels = useLabels();
3510
- switch (block.attrs.kind) {
3511
- case "heading":
3512
- return /* @__PURE__ */ jsxRuntime.jsx(Heading, { block });
3513
- case "bullet":
3514
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "quoin-list-item", "data-list": "bullet", children: [
3515
- /* @__PURE__ */ jsxRuntime.jsx(ListMarker, { list: "bullet", depth: block.indent }),
3516
- /* @__PURE__ */ jsxRuntime.jsx(chunk2QKFRKOP_cjs.EditableRegion, { block: block.id, region: REGION, label: labels.bulletItem })
3517
- ] });
3518
- case "ordered":
3519
- return /* @__PURE__ */ jsxRuntime.jsx(NumberedItem, { ...props });
3520
- case "task":
3521
- return /* @__PURE__ */ jsxRuntime.jsx(TaskItem, { ...props });
3522
- case "quote":
3523
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "quoin-quote", children: /* @__PURE__ */ jsxRuntime.jsx(chunk2QKFRKOP_cjs.EditableRegion, { block: block.id, region: REGION, label: labels.quote }) });
3524
- default:
3525
- return /* @__PURE__ */ jsxRuntime.jsx(chunk2QKFRKOP_cjs.EditableRegion, { block: block.id, region: REGION, label: labels.paragraph });
3526
- }
3527
- };
3528
-
3529
- // src/blocks/text/use-nested-blocks.ts
3530
- function nestedBlockIds({ doc }, blockId) {
3531
- const index = doc.index.get(blockId);
3532
- if (index === void 0) return [];
3533
- return doc.blocks.slice(index + 1, chunkFK7EBZPX_cjs.descendantsEnd(doc, index)).map((block) => block.id);
3501
+ function orderedLabel(n, depth) {
3502
+ return `${COUNTERS[cycleOf(depth)](n)}.`;
3534
3503
  }
3535
- function useNestedBlocks(controller, blockId) {
3536
- return chunk2QKFRKOP_cjs.useEditorState(controller, (state) => nestedBlockIds(state, blockId), chunkI7X7B3WD_cjs.sameItems);
3504
+ function ListMarker(props) {
3505
+ const text2 = props.list === "bullet" ? bulletGlyph(props.depth) : orderedLabel(props.number, props.depth);
3506
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "quoin-marker", "aria-hidden": "true", children: text2 });
3537
3507
  }
3538
- var ToggleBlock = ({ block, controller }) => {
3539
- const labels = useLabels();
3540
- const domId = chunk2QKFRKOP_cjs.useBlockDomId();
3541
- const nested = useNestedBlocks(controller, block.id);
3542
- const open = chunk2QKFRKOP_cjs.useToggleOpen(controller, block);
3543
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "quoin-toggle", "data-open": open, children: [
3544
- /* @__PURE__ */ jsxRuntime.jsx(
3545
- chunk2QKFRKOP_cjs.ToggleChevron,
3546
- {
3547
- block,
3548
- controller,
3549
- label: labels.toggleButton(open ? 0 : nested.length),
3550
- controls: nested.length === 0 ? void 0 : nested.map(domId).join(" ")
3551
- }
3552
- ),
3553
- /* @__PURE__ */ jsxRuntime.jsx(chunk2QKFRKOP_cjs.EditableRegion, { block: block.id, region: "text", label: labels.toggle })
3554
- ] });
3555
- };
3508
+ var HEADING_LEVELS = ["mt-6 text-3xl", "mt-5 text-2xl", "mt-4 text-xl", "mt-3 text-lg", "mt-3 text-base", "mt-3 text-sm"];
3509
+ var headingStyles = (level) => `quoin-heading mb-1 font-semibold leading-tight in-[[data-quoin-block]:first-child]:mt-0 ${HEADING_LEVELS[level - 1]}`;
3510
+ var CANVAS_LOOK = { Marker: ListMarker, headingClassName: headingStyles };
3511
+ var MarkdownBlock = (props) => /* @__PURE__ */ jsxRuntime.jsx(chunkN25EH2GZ_cjs.MarkdownText, { ...props, look: CANVAS_LOOK });
3556
3512
 
3557
3513
  // src/blocks/text/text-renderers.ts
3558
3514
  var textRenderers = {
3559
3515
  MARKDOWN: MarkdownBlock,
3560
3516
  CALLOUT: CalloutBlock,
3561
- TOGGLE: ToggleBlock,
3517
+ TOGGLE: chunkN25EH2GZ_cjs.ToggleBlock,
3562
3518
  DIVIDER: DividerBlock
3563
3519
  };
3564
3520
 
@@ -3576,6 +3532,7 @@ function drawnAsText(definitions, type, readOnly) {
3576
3532
  return !hostDefines(definitions, type) || !readOnly && TYPED_IN_PLACE.includes(type);
3577
3533
  }
3578
3534
  var definedTypedInPlace = (definitions) => TYPED_IN_PLACE.filter((type) => hostDefines(definitions, type));
3535
+ var hostLabel = (definitions, type) => !TYPED_IN_PLACE.includes(type) && hostDefines(definitions, type) ? chunkA6PGQVTR_cjs.blockByType(definitions, type)?.label : void 0;
3579
3536
  function drawnInPlace(definitions, type) {
3580
3537
  return Object.hasOwn(inPlaceRenderers, type) && chunkA6PGQVTR_cjs.blockByType(definitions, type) !== void 0 && !hostDefines(definitions, type);
3581
3538
  }
@@ -3611,10 +3568,10 @@ function turnIntoTargets(labels) {
3611
3568
  }
3612
3569
  function TurnIntoItems({ kit, controller, blockIds }) {
3613
3570
  const { blockTypes, labels } = useQuoin();
3614
- const targets = turnIntoTargets(labels).filter((target) => drawsType(blockTypes, target.type));
3615
- return targets.map((target) => /* @__PURE__ */ jsxRuntime.jsxs(kit.Item, { onSelect: () => runOnBlocks(controller, blockIds, chunkFK7EBZPX_cjs.setBlockType(target.type, target.attrs)), children: [
3571
+ const targets2 = turnIntoTargets(labels).filter((target) => drawsType(blockTypes, target.type));
3572
+ return targets2.map((target) => /* @__PURE__ */ jsxRuntime.jsxs(kit.Item, { onSelect: () => runOnBlocks(controller, blockIds, chunkZ5EEDCRI_cjs.setBlockType(target.type, target.attrs)), children: [
3616
3573
  /* @__PURE__ */ jsxRuntime.jsx(MenuIcon, { icon: target.icon }),
3617
- target.label
3574
+ hostLabel(blockTypes, target.type) ?? target.label
3618
3575
  ] }, target.id));
3619
3576
  }
3620
3577
  function TurnIntoMenu(props) {
@@ -3647,10 +3604,10 @@ function ActionItem({ kit, controller, action, blockIds }) {
3647
3604
  );
3648
3605
  }
3649
3606
  function BlockMenuItems({ kit, controller, blocks, styled, title, actions }) {
3650
- const state = chunk2QKFRKOP_cjs.useEditorState(controller, chunk2QKFRKOP_cjs.wholeState);
3607
+ const state = chunkN25EH2GZ_cjs.useEditorState(controller, chunkN25EH2GZ_cjs.wholeState);
3651
3608
  const blockIds = blocks.map((block) => block.id);
3652
3609
  const shown = actions.filter((action) => action.available?.(state, blockIds) ?? true);
3653
- const anyText = styled.some((block) => chunkFK7EBZPX_cjs.isTextFlow(state.schema, block));
3610
+ const anyText = styled.some((block) => chunkZ5EEDCRI_cjs.isTextFlow(state.schema, block));
3654
3611
  const anyMarkdown = styled.some((block) => block.type === "MARKDOWN");
3655
3612
  const allCallouts = styled.length > 0 && styled.every((block) => block.type === "CALLOUT");
3656
3613
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -3692,7 +3649,7 @@ var CONTEXT_KIT = {
3692
3649
  SubTrigger: ContextMenuSubTrigger
3693
3650
  };
3694
3651
  var GUTTER_BLOCK = "data-quoin-gutter-block";
3695
- var OWN_MENU = `[${chunk2QKFRKOP_cjs.NATIVE_INTERACTION}], input, textarea, select`;
3652
+ var OWN_MENU = `[${chunkN25EH2GZ_cjs.NATIVE_INTERACTION}], input, textarea, select`;
3696
3653
  function contextBlock(controller, target) {
3697
3654
  const { state } = controller.editor;
3698
3655
  const gutter = target.closest(`[${GUTTER_BLOCK}]`)?.getAttribute(GUTTER_BLOCK);
@@ -3701,8 +3658,8 @@ function contextBlock(controller, target) {
3701
3658
  if (selection?.kind === "blocks") return selection.head;
3702
3659
  if (target.closest(OWN_MENU) || controller.registry.locate(target) !== null) return null;
3703
3660
  const id = controller.blockAt(target);
3704
- const block = id === void 0 ? void 0 : chunkI7X7B3WD_cjs.blockById(state.doc, id);
3705
- return block === void 0 || chunkFK7EBZPX_cjs.isTextFlow(state.schema, block) ? null : block.id;
3661
+ const block = id === void 0 ? void 0 : chunk4JDRDPLC_cjs.blockById(state.doc, id);
3662
+ return block === void 0 || chunkZ5EEDCRI_cjs.isTextFlow(state.schema, block) ? null : block.id;
3706
3663
  }
3707
3664
  function useContextMenuOpener(controller, store, trigger) {
3708
3665
  React7.useEffect(() => {
@@ -3712,8 +3669,15 @@ function useContextMenuOpener(controller, store, trigger) {
3712
3669
  const onMouseDown = (event) => {
3713
3670
  if (event.button === 2 && blockOf(event) !== null) event.preventDefault();
3714
3671
  };
3672
+ let pressed = null;
3673
+ const onPointerDown = (event) => {
3674
+ const block = event.button === 2 ? blockOf(event) : null;
3675
+ pressed = block === null ? null : { block, x: event.clientX, y: event.clientY };
3676
+ };
3677
+ const pressedAt = ({ clientX, clientY }) => pressed !== null && Math.hypot(clientX - pressed.x, clientY - pressed.y) <= 2 ? pressed.block : null;
3715
3678
  const onContextMenu = (event) => {
3716
- const block = blockOf(event);
3679
+ const block = blockOf(event) ?? pressedAt(event);
3680
+ pressed = null;
3717
3681
  const hidden = trigger.current;
3718
3682
  if (block === null || hidden === null) return;
3719
3683
  event.preventDefault();
@@ -3721,7 +3685,7 @@ function useContextMenuOpener(controller, store, trigger) {
3721
3685
  openBlockMenuAt(store, controller.editor.state, block, clientX, clientY);
3722
3686
  hidden.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX, clientY }));
3723
3687
  };
3724
- return chunk2QKFRKOP_cjs.listen(root, { mousedown: onMouseDown, contextmenu: onContextMenu });
3688
+ return chunkN25EH2GZ_cjs.listen(root, { pointerdown: onPointerDown, mousedown: onMouseDown, contextmenu: onContextMenu });
3725
3689
  }, [controller, store, trigger]);
3726
3690
  }
3727
3691
 
@@ -3745,6 +3709,8 @@ var nameIn = (names, key) => typeof key === "string" && Object.hasOwn(names, key
3745
3709
  function blockTypeLabel(labels, block, definitions, extensionNames = {}) {
3746
3710
  const { kind, level } = block.attrs;
3747
3711
  if (block.type === "MARKDOWN" && kind === "heading") return labels.heading(typeof level === "number" ? level : 1);
3712
+ const own = hostLabel(definitions, block.type);
3713
+ if (own !== void 0) return own;
3748
3714
  const name = block.type === "MARKDOWN" ? nameIn(KIND_NAMES, kind) : nameIn(TYPE_NAMES, block.type);
3749
3715
  if (name !== void 0) return labels.blockNames[name];
3750
3716
  return nameIn(extensionNames, block.type) ?? chunkA6PGQVTR_cjs.blockByType(definitions, block.type)?.label ?? labels.unsupportedBlock;
@@ -3754,10 +3720,10 @@ var ExtensionBlockNames = React7.createContext({});
3754
3720
  // src/gutter/use-menu-blocks.ts
3755
3721
  function useLiveBlocks(controller, blockIds) {
3756
3722
  const select = React7.useCallback(
3757
- (state) => blockIds.flatMap((id) => chunkI7X7B3WD_cjs.blockById(state.doc, id) ?? []),
3723
+ (state) => blockIds.flatMap((id) => chunk4JDRDPLC_cjs.blockById(state.doc, id) ?? []),
3758
3724
  [blockIds]
3759
3725
  );
3760
- return chunk2QKFRKOP_cjs.useEditorState(controller, select, chunkI7X7B3WD_cjs.sameItems);
3726
+ return chunkN25EH2GZ_cjs.useEditorState(controller, select, chunk4JDRDPLC_cjs.sameItems);
3761
3727
  }
3762
3728
  function useMenuBlocks(controller, store, menu) {
3763
3729
  const labels = useLabels();
@@ -3807,15 +3773,81 @@ function BlockContextMenu({ controller, store, actions }) {
3807
3773
  );
3808
3774
  }
3809
3775
 
3776
+ // src/gutter/block-menu-items.ts
3777
+ var rangeOf = (blockIds) => chunkZ5EEDCRI_cjs.blockRangeSelection(blockIds[0], blockIds[blockIds.length - 1]);
3778
+ function menuActions(core, extra = []) {
3779
+ return [...core.filter((action) => !action.destructive), ...extra, ...core.filter((action) => action.destructive)];
3780
+ }
3781
+ function markdownOf(editor, blockIds, resolveAssetUrl) {
3782
+ const { doc } = editor.state;
3783
+ const range = chunkZ5EEDCRI_cjs.selectedBlockRange(doc, rangeOf(blockIds));
3784
+ if (range === null) return "";
3785
+ const blocks = editor.codec.blocksFromDoc(chunk4JDRDPLC_cjs.createDoc(doc.blocks.slice(range.start, range.end)));
3786
+ return chunkZ5EEDCRI_cjs.toMarkdown(blocks, { schema: editor.schema, resolveAssetUrl, comments: false });
3787
+ }
3788
+ var applies = (command) => (state, blockIds) => blockIds.every((id) => state.doc.index.has(id)) && chunkZ5EEDCRI_cjs.runCommands(state, [chunkZ5EEDCRI_cjs.setSelection(rangeOf(blockIds)), command]).ok;
3789
+ function coreBlockMenuActions(labels, options) {
3790
+ const copy = (controller, blockIds, text2) => {
3791
+ void copyText(text2, controller.root?.ownerDocument).then((copied) => {
3792
+ (copied ? options.onCopied : options.onCopyFailed)?.();
3793
+ });
3794
+ runOnBlocks(controller, blockIds, noChange);
3795
+ };
3796
+ return [
3797
+ {
3798
+ id: "duplicate",
3799
+ label: labels.duplicate,
3800
+ icon: CopyPlus,
3801
+ shortcut: "Mod-d",
3802
+ run: (controller, blockIds) => runOnBlocks(controller, blockIds, chunkZ5EEDCRI_cjs.duplicateBlocks())
3803
+ },
3804
+ {
3805
+ id: "copy",
3806
+ label: labels.copy,
3807
+ icon: Copy,
3808
+ run: (controller, blockIds) => copy(controller, blockIds, markdownOf(controller.editor, blockIds, options.resolveAssetUrl))
3809
+ },
3810
+ {
3811
+ id: "copy-link",
3812
+ label: labels.copyLink,
3813
+ icon: Link,
3814
+ run: (controller, blockIds) => copy(controller, blockIds, options.blockLink(blockIds[0]))
3815
+ },
3816
+ {
3817
+ id: "move-up",
3818
+ label: labels.moveUp,
3819
+ icon: ArrowUp,
3820
+ shortcut: "Mod-Shift-ArrowUp",
3821
+ run: (controller, blockIds) => runOnBlocks(controller, blockIds, chunkZ5EEDCRI_cjs.moveBlocksUp),
3822
+ available: applies(chunkZ5EEDCRI_cjs.moveBlocksUp)
3823
+ },
3824
+ {
3825
+ id: "move-down",
3826
+ label: labels.moveDown,
3827
+ icon: ArrowDown,
3828
+ shortcut: "Mod-Shift-ArrowDown",
3829
+ run: (controller, blockIds) => runOnBlocks(controller, blockIds, chunkZ5EEDCRI_cjs.moveBlocksDown),
3830
+ available: applies(chunkZ5EEDCRI_cjs.moveBlocksDown)
3831
+ },
3832
+ {
3833
+ id: "delete",
3834
+ label: labels.delete,
3835
+ icon: Trash,
3836
+ destructive: true,
3837
+ run: (controller, blockIds) => runOnBlocks(controller, blockIds, chunkZ5EEDCRI_cjs.deleteBlocks)
3838
+ }
3839
+ ];
3840
+ }
3841
+
3810
3842
  // src/gutter/block-menu-keymap.ts
3811
3843
  function blockMenuKeymap(store) {
3812
3844
  const openForHead = (state, dispatch) => {
3813
- const head = chunkFK7EBZPX_cjs.headBlockId(state.selection);
3845
+ const head = chunkZ5EEDCRI_cjs.headBlockId(state.selection);
3814
3846
  if (head === null || !state.doc.index.has(head)) return false;
3815
3847
  if (dispatch) openBlockMenu(store, state, head);
3816
3848
  return true;
3817
3849
  };
3818
- return chunkFK7EBZPX_cjs.defineExtension({ name: "block-menu", keymap: { "Mod-/": openForHead } });
3850
+ return chunkZ5EEDCRI_cjs.defineExtension({ name: "block-menu", keymap: { "Mod-/": openForHead } });
3819
3851
  }
3820
3852
  var UNIQUE_OR_FOCUSABLE = ["id", "data-block-id", "contenteditable", "tabindex"];
3821
3853
  function copyOf(shell) {
@@ -3880,14 +3912,28 @@ var DragOverlay = React7.forwardRef(function DragOverlay2(_props, ref) {
3880
3912
  ] });
3881
3913
  });
3882
3914
 
3915
+ // src/canvas/removed-blocks.ts
3916
+ function blockRemovals(tr) {
3917
+ return tr.steps.flatMap((step, at) => {
3918
+ if (step.stepType !== "removeBlocks") return [];
3919
+ const { index, count } = step.toJSON();
3920
+ return typeof index === "number" && typeof count === "number" ? [{ step: at, index, count }] : [];
3921
+ });
3922
+ }
3923
+
3883
3924
  // src/canvas/structure-announcement.ts
3884
3925
  var DELETION_STEPS = /* @__PURE__ */ new Set(["removeBlocks", "setIndent"]);
3885
3926
  function drawnPosition(doc, blockId) {
3886
- const index = doc.index.get(blockId) ?? 0;
3887
- return [chunk2QKFRKOP_cjs.drawnBlockCount(doc, 0, index) + 1, chunk2QKFRKOP_cjs.drawnBlockCount(doc)];
3927
+ const index = doc.index.get(blockId);
3928
+ return index === void 0 ? null : [chunkN25EH2GZ_cjs.drawnBlockCount(doc, 0, index) + 1, chunkN25EH2GZ_cjs.drawnBlockCount(doc)];
3888
3929
  }
3889
3930
  function blockMovedMessage(doc, blockId, labels) {
3890
- return labels.blockMoved(...drawnPosition(doc, blockId));
3931
+ const position = drawnPosition(doc, blockId);
3932
+ return position && labels.blockMoved(...position);
3933
+ }
3934
+ function announceBlockMoved(controller, blockId, labels) {
3935
+ const message = blockMovedMessage(controller.editor.state.doc, blockId, labels);
3936
+ if (message !== null && controller.announcer.current()?.message !== message) controller.announce(message);
3891
3937
  }
3892
3938
  function movedBlock(tr) {
3893
3939
  const at = tr.steps.findIndex((step) => step.stepType === "moveBlocks");
@@ -3897,71 +3943,111 @@ function movedBlock(tr) {
3897
3943
  }
3898
3944
  function deletedCount(tr) {
3899
3945
  if (!tr.steps.every((step) => DELETION_STEPS.has(step.stepType))) return 0;
3900
- return tr.steps.reduce((deleted, step, i) => {
3901
- const { index, count } = step.toJSON();
3902
- if (step.stepType !== "removeBlocks" || typeof index !== "number" || typeof count !== "number") return deleted;
3903
- return deleted + chunk2QKFRKOP_cjs.drawnBlockCount(tr.docs[i], index, index + count);
3904
- }, 0);
3946
+ return blockRemovals(tr).reduce(
3947
+ (deleted, { step, index, count }) => deleted + chunkN25EH2GZ_cjs.drawnBlockCount(tr.docs[step], index, index + count),
3948
+ 0
3949
+ );
3905
3950
  }
3906
- function insertedBlock(tr) {
3907
- if (tr.getMeta(chunkFK7EBZPX_cjs.LINE_SPLIT) === true || tr.steps.some((step) => step.stepType === "removeBlocks")) return null;
3951
+ function insertedIds(tr) {
3908
3952
  const inserted = tr.steps.flatMap((step) => {
3909
3953
  const { blocks } = step.toJSON();
3910
3954
  return step.stepType === "insertBlocks" && Array.isArray(blocks) ? blocks : [];
3911
3955
  });
3912
- return inserted.find((block) => tr.doc.index.has(block.id))?.id ?? null;
3956
+ return new Set(inserted.map(({ id }) => id));
3957
+ }
3958
+ function isCaretLine(tr, block) {
3959
+ const { selection } = tr;
3960
+ const empty = chunkZ5EEDCRI_cjs.isParagraph(block) && chunkZ5EEDCRI_cjs.regionLength(block, chunkZ5EEDCRI_cjs.primaryRegion(tr.before.schema, block) ?? "") === 0;
3961
+ return empty && selection?.kind === "text" && selection.head.block === block.id;
3962
+ }
3963
+ function insertedBlocks(tr) {
3964
+ if (tr.getMeta(chunkZ5EEDCRI_cjs.LINE_SPLIT) === true || tr.steps.some((step) => step.stepType === "removeBlocks")) return [];
3965
+ const inserted = insertedIds(tr);
3966
+ const { doc } = tr;
3967
+ const drawn = doc.blocks.filter((block, index) => inserted.has(block.id) && chunkN25EH2GZ_cjs.drawnBlockCount(doc, index, index + 1));
3968
+ const last2 = drawn.at(-1);
3969
+ if (drawn.length > 1 && last2 !== void 0 && isCaretLine(tr, last2)) drawn.pop();
3970
+ return drawn.map((block) => block.id);
3971
+ }
3972
+ function insertedMessage(tr, labels) {
3973
+ const [first, ...more] = insertedBlocks(tr);
3974
+ const position = first === void 0 ? null : drawnPosition(tr.doc, first);
3975
+ if (position === null) return null;
3976
+ if (more.length === 0) return labels.blockInserted(...position);
3977
+ const blocksInserted = labels.blocksInserted ?? englishLabels.blocksInserted;
3978
+ return blocksInserted(more.length + 1, ...position);
3913
3979
  }
3914
3980
  function structureAnnouncement(tr, labels, undoKeys) {
3915
- if (tr.origin !== "command" || tr.getMeta(chunkFK7EBZPX_cjs.LINE_JOIN) === true) return null;
3981
+ if (tr.origin !== "command" || tr.getMeta(chunkZ5EEDCRI_cjs.LINE_JOIN) === true) return null;
3916
3982
  const moved = movedBlock(tr);
3917
3983
  if (moved !== null) return blockMovedMessage(tr.doc, moved, labels);
3918
3984
  const deleted = deletedCount(tr);
3919
3985
  if (deleted > 0) return `${labels.blockDeleted(deleted)}. ${labels.undoHint(undoKeys)}`;
3920
- const inserted = insertedBlock(tr);
3921
- return inserted === null ? null : labels.blockInserted(...drawnPosition(tr.doc, inserted));
3986
+ return insertedMessage(tr, labels);
3922
3987
  }
3923
3988
 
3924
3989
  // src/drag/drag-layout.ts
3925
- var INDENT_STEP_REM = 1.5;
3926
3990
  function rootOrigin(root) {
3927
3991
  const box = root.getBoundingClientRect();
3928
3992
  return { x: box.left + root.clientLeft - root.scrollLeft, y: box.top + root.clientTop - root.scrollTop };
3929
3993
  }
3930
3994
  var spanBetween = (a, b) => ({ left: Math.min(a, b), width: Math.abs(b - a) });
3931
- function indentStep(doc) {
3932
- return (parseFloat(getComputedStyle(doc.documentElement).fontSize) || 16) * INDENT_STEP_REM;
3995
+ var leadingMargin = (element, rtl) => parseFloat(getComputedStyle(element)[rtl ? "marginRight" : "marginLeft"]) || 0;
3996
+ function indentStep(shell, rtl) {
3997
+ const copies = [0, 1].map((depth) => {
3998
+ const copy = shell.cloneNode(false);
3999
+ copy.removeAttribute("id");
4000
+ copy.removeAttribute("data-block-id");
4001
+ copy.style.setProperty("--quoin-indent", String(depth));
4002
+ copy.style.display = "none";
4003
+ return copy;
4004
+ });
4005
+ shell.after(...copies);
4006
+ const [flat, nested] = copies.map((copy) => leadingMargin(copy, rtl));
4007
+ for (const copy of copies) copy.remove();
4008
+ return nested > flat ? nested - flat : parseFloat(getComputedStyle(shell).fontSize) || 16;
3933
4009
  }
3934
4010
  function inlineEdges(shell, box, rtl) {
3935
- const style = getComputedStyle(shell);
3936
- if (rtl) return { start: box.right + (parseFloat(style.marginRight) || 0), end: box.left };
3937
- return { start: box.left - (parseFloat(style.marginLeft) || 0), end: box.right };
4011
+ const margin = leadingMargin(shell, rtl);
4012
+ return rtl ? { start: box.right + margin, end: box.left } : { start: box.left - margin, end: box.right };
3938
4013
  }
3939
4014
  function measure(controller, root, moved, rtl) {
3940
4015
  const { doc } = controller.editor.state;
3941
4016
  const origin = rootOrigin(root);
3942
- return chunk2QKFRKOP_cjs.visibleBlockIndexes(doc).flatMap((index) => {
3943
- const { id, indent: indent3 } = doc.blocks[index];
3944
- const shell = controller.blockElement(id);
4017
+ return chunkN25EH2GZ_cjs.visibleBlockIndexes(doc).flatMap((index) => {
4018
+ const block = doc.blocks[index];
4019
+ const shell = controller.blockElement(block.id);
3945
4020
  if (shell === void 0) return [];
3946
4021
  const box = shell.getBoundingClientRect();
3947
4022
  const { start, end } = inlineEdges(shell, box, rtl);
3948
- const [top, bottom] = [box.top - origin.y, box.bottom - origin.y];
3949
- return [{ id, top, bottom, start: start - origin.x, end: end - origin.x, indent: indent3, dragged: moved.has(id), shell }];
4023
+ return [
4024
+ {
4025
+ id: block.id,
4026
+ top: box.top - origin.y,
4027
+ bottom: box.bottom - origin.y,
4028
+ start: start - origin.x,
4029
+ end: end - origin.x,
4030
+ indent: block.indent,
4031
+ dragged: moved.has(block.id),
4032
+ hidesNested: chunkZ5EEDCRI_cjs.isClosedToggle(block),
4033
+ shell
4034
+ }
4035
+ ];
3950
4036
  });
3951
4037
  }
3952
4038
  var furthestEnd = (blocks, rtl) => blocks.reduce((end, block) => rtl ? Math.min(end, block.end) : Math.max(end, block.end), blocks[0].end);
3953
4039
  function measureDrag(controller, root, ids) {
3954
4040
  const { doc } = controller.editor.state;
3955
- const direction = chunk2QKFRKOP_cjs.inlineDirection(root);
4041
+ const direction = chunkN25EH2GZ_cjs.inlineDirection(root);
3956
4042
  const rtl = direction === "rtl";
3957
- const moved = new Set(chunkFK7EBZPX_cjs.movedBlockIds(doc, ids));
4043
+ const moved = new Set(chunkZ5EEDCRI_cjs.movedBlockIds(doc, ids));
3958
4044
  const blocks = measure(controller, root, moved, rtl);
3959
4045
  const dragged = blocks.filter((block) => block.dragged);
3960
4046
  if (dragged.length === 0) return null;
3961
4047
  const [first] = dragged;
3962
4048
  const origin = rootOrigin(root);
3963
4049
  const viewport = root.ownerDocument.defaultView?.innerHeight ?? Infinity;
3964
- const step = indentStep(root.ownerDocument);
4050
+ const step = indentStep(first.shell, rtl);
3965
4051
  const rest = doc.blocks.filter((block) => !moved.has(block.id));
3966
4052
  const restIndex = new Map(rest.map((block, index) => [block.id, index]));
3967
4053
  const staying = blocks.filter((block) => !block.dragged);
@@ -3992,6 +4078,7 @@ function nearestGap(blocks, pointerY) {
3992
4078
  return nearest2;
3993
4079
  }
3994
4080
  function computeDropTarget({ pointerX, pointerY, blocks, indentStep: indentStep2, direction }) {
4081
+ if (blocks.length === 0) return null;
3995
4082
  const staying = blocks.filter((block) => !block.dragged);
3996
4083
  if (staying.length === 0) return { index: 0, indent: 0, lineTop: blocks[0].top, lineStart: blocks[0].start };
3997
4084
  const index = nearestGap(staying, pointerY);
@@ -3999,7 +4086,8 @@ function computeDropTarget({ pointerX, pointerY, blocks, indentStep: indentStep2
3999
4086
  if (index === 0) return { index, indent: 0, lineTop, lineStart: staying[0].start };
4000
4087
  const above = staying[index - 1];
4001
4088
  const step = direction === "rtl" ? -indentStep2 : indentStep2;
4002
- const indent3 = Math.min(Math.max(Math.round((pointerX - above.start) / step), 0), above.indent + 1);
4089
+ const deepest = above.hidesNested ? above.indent : above.indent + 1;
4090
+ const indent3 = Math.min(Math.max(Math.round((pointerX - above.start) / step), 0), deepest);
4003
4091
  return { index, indent: indent3, lineTop, lineStart: above.start + indent3 * step };
4004
4092
  }
4005
4093
 
@@ -4009,12 +4097,12 @@ var SCROLL_EDGE = 48;
4009
4097
  var sameTarget = (a, b) => a?.index === b.index && a.indent === b.indent;
4010
4098
  function draggedBlocks(controller, blockId) {
4011
4099
  const { state } = controller.editor;
4012
- return state.doc.index.has(blockId) ? menuTargets(state, blockId) : [];
4100
+ return state.doc.index.has(blockId) ? handleTargets(state, blockId) : [];
4013
4101
  }
4014
4102
  function followPress(press, root, view) {
4015
4103
  const { controller, store, overlay, handle: handle2, pointerId } = press;
4016
4104
  const doc = view.document;
4017
- const scroll = chunk2QKFRKOP_cjs.createAutoScroll(root, follow, SCROLL_EDGE);
4105
+ const scroll = chunkN25EH2GZ_cjs.createAutoScroll(root, follow, SCROLL_EDGE);
4018
4106
  let pointer = { x: press.x, y: press.y };
4019
4107
  let active = null;
4020
4108
  let unsubscribe = () => {
@@ -4042,6 +4130,7 @@ function followPress(press, root, view) {
4042
4130
  const { indentStep: indentStep2, blocks, direction } = layout;
4043
4131
  const target = computeDropTarget({ pointerX: layout.draggedStart + dx, pointerY, blocks, indentStep: indentStep2, direction });
4044
4132
  overlay.follow(dx, pointer.y - press.y);
4133
+ if (target === null) return;
4045
4134
  overlay.mark({ top: target.lineTop, ...spanBetween(target.lineStart, layout.end) });
4046
4135
  if (!sameTarget(active.target, target)) {
4047
4136
  store.set({ drag: { blockIds: ids, targetIndent: target.indent } });
@@ -4052,6 +4141,7 @@ function followPress(press, root, view) {
4052
4141
  if (done) return;
4053
4142
  done = true;
4054
4143
  unlistenDocument();
4144
+ unlistenScroll();
4055
4145
  unlistenBlur();
4056
4146
  unlistenKeys();
4057
4147
  scroll.stop();
@@ -4067,7 +4157,7 @@ function followPress(press, root, view) {
4067
4157
  finish();
4068
4158
  if (dropped === null || dropped.target === null || controller.readOnly) return;
4069
4159
  const { ids, layout, target } = dropped;
4070
- if (!controller.run(chunkFK7EBZPX_cjs.moveBlocksTo(ids, layout.toIndex(target.index), target.indent))) return;
4160
+ if (!controller.run(chunkZ5EEDCRI_cjs.moveBlocksTo(ids, layout.toIndex(target.index), target.indent))) return;
4071
4161
  controller.focus();
4072
4162
  press.onMoved(ids[0]);
4073
4163
  }
@@ -4089,9 +4179,10 @@ function followPress(press, root, view) {
4089
4179
  event.stopPropagation();
4090
4180
  finish();
4091
4181
  }
4092
- const unlistenDocument = chunk2QKFRKOP_cjs.listen(doc, { pointermove: onPointerMove, pointerup: onPointerUp, pointercancel: finish });
4093
- const unlistenBlur = chunk2QKFRKOP_cjs.listen(view, { blur: finish });
4094
- const unlistenKeys = chunk2QKFRKOP_cjs.listen(view, { keydown: onKeyDown }, true);
4182
+ const unlistenDocument = chunkN25EH2GZ_cjs.listen(doc, { pointermove: onPointerMove, pointerup: onPointerUp, pointercancel: finish });
4183
+ const unlistenScroll = chunkN25EH2GZ_cjs.listen(doc, { scroll: follow }, true);
4184
+ const unlistenBlur = chunkN25EH2GZ_cjs.listen(view, { blur: finish });
4185
+ const unlistenKeys = chunkN25EH2GZ_cjs.listen(view, { keydown: onKeyDown }, true);
4095
4186
  return finish;
4096
4187
  }
4097
4188
  function pressDragHandle(press) {
@@ -4126,9 +4217,9 @@ function useBlockDrag(controller, store, blockId, overlay) {
4126
4217
  onDragStart: () => {
4127
4218
  dragged.current = true;
4128
4219
  },
4129
- // The canvas has already said where a drop moved the block, and the announcer says the same words for the
4130
- // same state once, so this is heard only for a drop that nested the block in place.
4131
- onMoved: (movedId) => controller.announce(blockMovedMessage(controller.editor.state.doc, movedId, labels))
4220
+ // The canvas has already said where a drop moved the block, so this is heard only for a drop that nested the
4221
+ // block in place.
4222
+ onMoved: (movedId) => announceBlockMoved(controller, movedId, labels)
4132
4223
  });
4133
4224
  },
4134
4225
  takeClick(event) {
@@ -4138,17 +4229,44 @@ function useBlockDrag(controller, store, blockId, overlay) {
4138
4229
  }
4139
4230
  };
4140
4231
  }
4232
+ var listOf = (query) => typeof window.matchMedia === "function" ? window.matchMedia(query) : null;
4233
+ var readOnServer = () => false;
4234
+ function useMediaQuery(query, unsupported) {
4235
+ const subscribe3 = React7.useCallback(
4236
+ (notify) => {
4237
+ const list2 = listOf(query);
4238
+ list2?.addEventListener("change", notify);
4239
+ return () => list2?.removeEventListener("change", notify);
4240
+ },
4241
+ [query]
4242
+ );
4243
+ const read2 = () => listOf(query)?.matches ?? unsupported;
4244
+ return React7.useSyncExternalStore(subscribe3, read2, readOnServer);
4245
+ }
4246
+
4247
+ // src/mobile/use-coarse-pointer.ts
4248
+ var useCoarsePointer = () => useMediaQuery("(pointer: coarse)", false);
4141
4249
 
4142
4250
  // src/gutter/add-block-below.ts
4251
+ var paragraphAfter = (blockId) => (state, dispatch) => {
4252
+ const { doc, schema } = state;
4253
+ const index = chunk4JDRDPLC_cjs.requireBlockIndex(doc, blockId);
4254
+ const paragraph = chunkZ5EEDCRI_cjs.createBlock(schema, "MARKDOWN", { kind: "paragraph" }, doc.blocks[index].indent);
4255
+ const tr = new chunkZ5EEDCRI_cjs.Transaction(state).step(chunkZ5EEDCRI_cjs.insertBlocksStep(chunkZ5EEDCRI_cjs.descendantsEnd(doc, index), [paragraph]));
4256
+ dispatch?.(tr.setSelection(chunkZ5EEDCRI_cjs.startOf(tr.doc, schema, paragraph.id)));
4257
+ return true;
4258
+ };
4143
4259
  function addBlockBelow(controller, blockId) {
4144
4260
  const { state } = controller.editor;
4145
4261
  const { doc, schema } = state;
4146
- const block = chunkI7X7B3WD_cjs.blockById(doc, blockId);
4147
- if (block === void 0) return false;
4148
- const empty = chunkFK7EBZPX_cjs.isParagraph(block) && chunkFK7EBZPX_cjs.regionLength(block, chunkFK7EBZPX_cjs.primaryRegion(schema, block) ?? "") === 0;
4149
- const fill = empty ? chunkFK7EBZPX_cjs.setSelection(chunkFK7EBZPX_cjs.startOf(doc, schema, blockId)) : chunkFK7EBZPX_cjs.insertBlockAfter("MARKDOWN", { kind: "paragraph" });
4150
- const slash = chunkFK7EBZPX_cjs.insertText("/", { origin: "command" });
4151
- const { ok, tr } = chunkFK7EBZPX_cjs.runCommands(state, [chunkFK7EBZPX_cjs.setSelection(chunkFK7EBZPX_cjs.blockRangeSelection(blockId)), fill, slash]);
4262
+ const index = doc.index.get(blockId);
4263
+ if (index === void 0) return false;
4264
+ const block = doc.blocks[index];
4265
+ const alone = chunkZ5EEDCRI_cjs.descendantsEnd(doc, index) === index + 1;
4266
+ const empty = alone && chunkZ5EEDCRI_cjs.isParagraph(block) && chunkZ5EEDCRI_cjs.regionLength(block, chunkZ5EEDCRI_cjs.primaryRegion(schema, block) ?? "") === 0;
4267
+ const fill = empty ? chunkZ5EEDCRI_cjs.setSelection(chunkZ5EEDCRI_cjs.startOf(doc, schema, blockId)) : paragraphAfter(blockId);
4268
+ const slash = chunkZ5EEDCRI_cjs.insertText("/", { origin: "command" });
4269
+ const { ok, tr } = chunkZ5EEDCRI_cjs.runCommands(state, [chunkZ5EEDCRI_cjs.setSelection(chunkZ5EEDCRI_cjs.blockRangeSelection(blockId)), fill, slash]);
4152
4270
  if (ok && tr !== null) controller.dispatch(tr);
4153
4271
  controller.focus();
4154
4272
  return ok;
@@ -4197,6 +4315,20 @@ function BlockMenu({ controller, store, actions, onOpen, children }) {
4197
4315
  );
4198
4316
  }
4199
4317
 
4318
+ // src/ui/watch-direction.ts
4319
+ var TURNS_ROUND = { attributeFilter: ["dir", "class", "style"] };
4320
+ function watchDirection(element, onChange) {
4321
+ let current = chunkN25EH2GZ_cjs.inlineDirection(element);
4322
+ const observer = new MutationObserver(() => {
4323
+ const next = chunkN25EH2GZ_cjs.inlineDirection(element);
4324
+ if (next === current) return;
4325
+ current = next;
4326
+ onChange(next);
4327
+ });
4328
+ for (let node = element; node !== null; node = node.parentElement) observer.observe(node, TURNS_ROUND);
4329
+ return () => observer.disconnect();
4330
+ }
4331
+
4200
4332
  // src/gutter/use-gutter-position.ts
4201
4333
  var GAP = 4;
4202
4334
  var VOID_LINE = 24;
@@ -4220,13 +4352,13 @@ function place2(controller, root, gutter, block) {
4220
4352
  const line = firstLine(shell);
4221
4353
  const top = line.top + (line.height - gutter.offsetHeight) / 2 - rootBox.top - root.clientTop + root.scrollTop;
4222
4354
  const shellBox = shell.getBoundingClientRect();
4223
- const beside = chunk2QKFRKOP_cjs.inlineDirection(root) === "rtl" ? shellBox.right + GAP : shellBox.left - gutter.offsetWidth - GAP;
4355
+ const beside = chunkN25EH2GZ_cjs.inlineDirection(root) === "rtl" ? shellBox.right + GAP : shellBox.left - gutter.offsetWidth - GAP;
4224
4356
  const left = beside - rootBox.left - root.clientLeft + root.scrollLeft;
4225
4357
  gutter.style.transform = `translate(${left}px, ${top}px)`;
4226
4358
  gutter.style.visibility = "";
4227
4359
  }
4228
4360
  function useGutterPosition(controller, gutter, block) {
4229
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
4361
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
4230
4362
  const element = gutter.current;
4231
4363
  const root = controller.root;
4232
4364
  const view = root?.ownerDocument.defaultView;
@@ -4239,18 +4371,20 @@ function useGutterPosition(controller, gutter, block) {
4239
4371
  frame = view.requestAnimationFrame(update);
4240
4372
  };
4241
4373
  const unsubscribe = controller.editor.subscribe(later);
4374
+ const unwatch = watchDirection(root, later);
4242
4375
  const observer = new ResizeObserver(later);
4243
4376
  observer.observe(root);
4244
4377
  view.addEventListener("resize", later);
4245
4378
  return () => {
4246
4379
  view.cancelAnimationFrame(frame);
4247
4380
  unsubscribe();
4381
+ unwatch();
4248
4382
  observer.disconnect();
4249
4383
  view.removeEventListener("resize", later);
4250
4384
  };
4251
4385
  }, [controller, gutter, block]);
4252
4386
  }
4253
- var headOf = (state) => chunkFK7EBZPX_cjs.headBlockId(state.selection);
4387
+ var headOf = (state) => chunkZ5EEDCRI_cjs.headBlockId(state.selection);
4254
4388
  function blockAcross(controller, root, y) {
4255
4389
  const box = root.getBoundingClientRect();
4256
4390
  const probe = root.ownerDocument.elementFromPoint?.(box.left + box.width / 2, y);
@@ -4258,7 +4392,7 @@ function blockAcross(controller, root, y) {
4258
4392
  }
4259
4393
  function useGutterTarget(controller, store, gutter) {
4260
4394
  const hovered = useCanvasUi(store, (state) => state.hoveredBlock);
4261
- const head = chunk2QKFRKOP_cjs.useEditorState(controller, headOf);
4395
+ const head = chunkN25EH2GZ_cjs.useEditorState(controller, headOf);
4262
4396
  const [focusWithin, setFocusWithin] = React7.useState(false);
4263
4397
  React7.useEffect(() => {
4264
4398
  const root = controller.root;
@@ -4276,7 +4410,7 @@ function useGutterTarget(controller, store, gutter) {
4276
4410
  const onFocusIn = () => setFocusWithin(true);
4277
4411
  const onFocusOut = (event) => setFocusWithin(root.contains(event.relatedTarget));
4278
4412
  setFocusWithin(root.contains(root.ownerDocument.activeElement));
4279
- return chunk2QKFRKOP_cjs.listen(root, { pointermove: onPointerMove, pointerleave: onPointerLeave, focusin: onFocusIn, focusout: onFocusOut });
4413
+ return chunkN25EH2GZ_cjs.listen(root, { pointermove: onPointerMove, pointerleave: onPointerLeave, focusin: onFocusIn, focusout: onFocusOut });
4280
4414
  }, [controller, store, gutter]);
4281
4415
  const fallback = focusWithin ? head : null;
4282
4416
  const target = hovered ?? fallback;
@@ -4286,6 +4420,7 @@ var GUTTER = "absolute top-0 left-0 z-10 flex items-center gap-0.5 pointer-coars
4286
4420
  var ADD_BELOW = "text-muted-foreground pointer-coarse:hidden";
4287
4421
  function Gutter({ controller, store, actions }) {
4288
4422
  const labels = useLabels();
4423
+ const coarse = useCoarsePointer();
4289
4424
  const element = React7.useRef(null);
4290
4425
  const overlay = React7.useRef(null);
4291
4426
  const menuOpenAtPress = React7.useRef(false);
@@ -4331,7 +4466,7 @@ function Gutter({ controller, store, actions }) {
4331
4466
  variant: "ghost",
4332
4467
  size: null,
4333
4468
  tabIndex: -1,
4334
- "aria-label": labels.dragHandle,
4469
+ "aria-label": coarse ? labels.blockOptions ?? englishLabels.blockOptions : labels.dragHandle,
4335
4470
  className: cn("text-muted-foreground h-6 w-4 cursor-grab rounded-md", SQUARE_TAP_TARGET_FLOOR),
4336
4471
  onPointerDown: (event) => {
4337
4472
  menuOpenAtPress.current = event.button === 0 && store.get().blockMenu?.anchor === "handle";
@@ -4353,17 +4488,6 @@ function Gutter({ controller, store, actions }) {
4353
4488
  ] });
4354
4489
  }
4355
4490
 
4356
- // src/i18n/resolve-labels.ts
4357
- var isRecord = (value) => typeof value === "object" && value !== null;
4358
- function ontoRecords(overrides) {
4359
- const taken = Object.entries(overrides).map(([key, value]) => {
4360
- const fallback = englishLabels[key];
4361
- return [key, isRecord(value) && isRecord(fallback) ? { ...fallback, ...value } : value];
4362
- });
4363
- return Object.fromEntries(taken);
4364
- }
4365
- var resolveLabels = (overrides) => chunkZWTIRGY5_cjs.mergeLabels(englishLabels, overrides && ontoRecords(overrides));
4366
-
4367
4491
  // src/menus/floating/range-rect.ts
4368
4492
  var TEXT_NODE = 3;
4369
4493
  function lineBox(rect, host) {
@@ -4377,7 +4501,7 @@ function pointInHost(host, { node, offset }) {
4377
4501
  return host.contains(node) ? { node, offset: Math.min(offset, nodeLength(node)) } : null;
4378
4502
  }
4379
4503
  function textRect(host, { from, to }) {
4380
- const map = chunk2QKFRKOP_cjs.domMapOf(host);
4504
+ const map = chunkN25EH2GZ_cjs.domMapOf(host);
4381
4505
  const range = host.ownerDocument.createRange();
4382
4506
  if (map === void 0 || typeof range.getBoundingClientRect !== "function") return null;
4383
4507
  const start = pointInHost(host, map.toDom(from, 1));
@@ -4390,7 +4514,7 @@ function textRect(host, { from, to }) {
4390
4514
  }
4391
4515
  function rangeRect(controller, range) {
4392
4516
  const host = controller.registry.get(range.block, range.region);
4393
- if (host === void 0 || !host.isConnected || controller.composingKey === chunk2QKFRKOP_cjs.hostKey(range.block, range.region)) {
4517
+ if (host === void 0 || !host.isConnected || controller.composingKey === chunkN25EH2GZ_cjs.hostKey(range.block, range.region)) {
4394
4518
  return null;
4395
4519
  }
4396
4520
  const rect = textRect(host, range);
@@ -4406,7 +4530,7 @@ function union(a, b) {
4406
4530
  function selectionRect(controller) {
4407
4531
  const { state } = controller.editor;
4408
4532
  if (state.selection?.kind !== "text") return null;
4409
- const ranges = chunkFK7EBZPX_cjs.markRanges(state);
4533
+ const ranges = chunkZ5EEDCRI_cjs.markRanges(state);
4410
4534
  if (ranges.length === 0) return null;
4411
4535
  const first = rangeRect(controller, ranges[0]);
4412
4536
  if (ranges.length === 1) return first;
@@ -4417,7 +4541,7 @@ function selectionRect(controller) {
4417
4541
  // src/menus/floating/use-selection-anchor.ts
4418
4542
  var ROOM_ABOVE = 56;
4419
4543
  function selectionHost(controller) {
4420
- const [first] = chunkFK7EBZPX_cjs.markRanges(controller.editor.state);
4544
+ const [first] = chunkZ5EEDCRI_cjs.markRanges(controller.editor.state);
4421
4545
  return (first && controller.registry.get(first.block, first.region)) ?? controller.root ?? void 0;
4422
4546
  }
4423
4547
  function useSelectionAnchor(controller, state) {
@@ -4431,7 +4555,7 @@ function useSelectionAnchor(controller, state) {
4431
4555
  [controller, state]
4432
4556
  );
4433
4557
  const [side, setSide] = React7.useState("top");
4434
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
4558
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
4435
4559
  const rect = selectionRect(controller);
4436
4560
  if (rect) setSide(rect.top < ROOM_ABOVE ? "bottom" : "top");
4437
4561
  }, [controller, state]);
@@ -4466,9 +4590,9 @@ var ICONS = { left: AlignLeft, center: AlignCenter, right: AlignRight, justify:
4466
4590
  var ALIGNS2 = Object.keys(ICONS);
4467
4591
  var LABEL_KEYS = { left: "alignLeft", center: "alignCenter", right: "alignRight", justify: "alignJustify" };
4468
4592
  function sharedAlign(state) {
4469
- const blocks = chunkFK7EBZPX_cjs.selectedBlockIds(state.doc, state.selection).map((id) => chunkI7X7B3WD_cjs.blockById(state.doc, id)).filter((block) => block?.type === "MARKDOWN");
4593
+ const blocks = chunkZ5EEDCRI_cjs.selectedBlockIds(state.doc, state.selection).map((id) => chunk4JDRDPLC_cjs.blockById(state.doc, id)).filter((block) => block?.type === "MARKDOWN");
4470
4594
  if (blocks.length === 0) return null;
4471
- const aligns = new Set(blocks.map((block) => chunkI7X7B3WD_cjs.alignOrUndefined(block?.attrs.align) ?? "left"));
4595
+ const aligns = new Set(blocks.map((block) => chunk4JDRDPLC_cjs.alignOrUndefined(block?.attrs.align) ?? "left"));
4472
4596
  return aligns.size === 1 ? [...aligns][0] : "";
4473
4597
  }
4474
4598
  function AlignDropdown({ controller, state }) {
@@ -4477,16 +4601,49 @@ function AlignDropdown({ controller, state }) {
4477
4601
  if (value === null) return null;
4478
4602
  const Icon2 = value === "" ? AlignLeft : ICONS[value];
4479
4603
  const trigger = /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": labels.align, children: /* @__PURE__ */ jsxRuntime.jsx(Icon2, {}) });
4480
- return /* @__PURE__ */ jsxRuntime.jsx(ToolbarMenu, { controller, trigger, children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuRadioGroup, { value, onValueChange: (align) => controller.run(chunkFK7EBZPX_cjs.setTextAlign(align)), children: ALIGNS2.map((align) => /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuRadioItem, { value: align, indicator: /* @__PURE__ */ jsxRuntime.jsx(Check, { className: "size-4" }), children: [
4604
+ return /* @__PURE__ */ jsxRuntime.jsx(ToolbarMenu, { controller, trigger, children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuRadioGroup, { value, onValueChange: (align) => controller.run(chunkZ5EEDCRI_cjs.setTextAlign(align)), children: ALIGNS2.map((align) => /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuRadioItem, { value: align, indicator: /* @__PURE__ */ jsxRuntime.jsx(Check, { className: "size-4" }), children: [
4481
4605
  /* @__PURE__ */ jsxRuntime.jsx(MenuIcon, { icon: ICONS[align] }),
4482
4606
  labels[LABEL_KEYS[align]]
4483
4607
  ] }, align)) }) });
4484
4608
  }
4609
+
4610
+ // src/toolbar/toolbar-items.ts
4611
+ function markItem(mark, title, icon) {
4612
+ return {
4613
+ id: mark,
4614
+ title,
4615
+ icon,
4616
+ isActive: (state) => chunkZ5EEDCRI_cjs.isActive(state, mark),
4617
+ run: (controller) => void controller.run(chunkZ5EEDCRI_cjs.toggleMark(mark))
4618
+ };
4619
+ }
4620
+ var linkItem = (labels, openLink) => ({
4621
+ id: "link",
4622
+ title: labels.link,
4623
+ icon: Link,
4624
+ opensDialog: true,
4625
+ run: openLink
4626
+ });
4627
+
4628
+ // src/menus/bubble/bubble-items.ts
4629
+ function bubbleItems(labels, openLink) {
4630
+ return {
4631
+ inline: [
4632
+ markItem("bold", labels.bold, Bold),
4633
+ markItem("italic", labels.italic, Italic),
4634
+ markItem("underline", labels.underline, Underline),
4635
+ markItem("strike", labels.strike, Strikethrough),
4636
+ markItem("code", labels.code, Code),
4637
+ linkItem(labels, openLink)
4638
+ ],
4639
+ scripts: [markItem("sub", labels.subscript, Subscript), markItem("sup", labels.superscript, Superscript)]
4640
+ };
4641
+ }
4485
4642
  var MARKS = { color: "color", background: "highlight" };
4486
4643
  function selectionColor(state, mark) {
4487
4644
  const names = /* @__PURE__ */ new Set();
4488
- for (const range of chunkFK7EBZPX_cjs.markRanges(state)) {
4489
- for (const node of chunkFK7EBZPX_cjs.rangeContent(state, range)) {
4645
+ for (const range of chunkZ5EEDCRI_cjs.markRanges(state)) {
4646
+ for (const node of chunkZ5EEDCRI_cjs.rangeContent(state, range)) {
4490
4647
  const found = node.marks.find((candidate) => candidate.type === mark);
4491
4648
  names.add(found === void 0 ? "default" : found.attrs?.color);
4492
4649
  }
@@ -4495,7 +4652,7 @@ function selectionColor(state, mark) {
4495
4652
  return names.size === 1 && typeof only === "string" ? only : "";
4496
4653
  }
4497
4654
  function colorCommand(key, name) {
4498
- return name === "default" ? chunkFK7EBZPX_cjs.unsetMark(MARKS[key]) : chunkFK7EBZPX_cjs.setMark(MARKS[key], { color: name });
4655
+ return name === "default" ? chunkZ5EEDCRI_cjs.unsetMark(MARKS[key]) : chunkZ5EEDCRI_cjs.setMark(MARKS[key], { color: name });
4499
4656
  }
4500
4657
  function ColorPopover({ controller, state }) {
4501
4658
  const labels = useLabels();
@@ -4509,15 +4666,32 @@ function TurnIntoDropdown({ controller, state }) {
4509
4666
  labels.turnInto,
4510
4667
  /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
4511
4668
  ] });
4512
- return /* @__PURE__ */ jsxRuntime.jsx(ToolbarMenu, { controller, trigger, children: /* @__PURE__ */ jsxRuntime.jsx(TurnIntoItems, { kit: DROPDOWN_KIT, controller, blockIds: chunkFK7EBZPX_cjs.selectedBlockIds(state.doc, state.selection) }) });
4669
+ return /* @__PURE__ */ jsxRuntime.jsx(ToolbarMenu, { controller, trigger, children: /* @__PURE__ */ jsxRuntime.jsx(TurnIntoItems, { kit: DROPDOWN_KIT, controller, blockIds: chunkZ5EEDCRI_cjs.selectedBlockIds(state.doc, state.selection) }) });
4670
+ }
4671
+ function useFocusBackToText(controller, holds = isBubblePart) {
4672
+ const latest = React7.useRef(holds);
4673
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
4674
+ latest.current = holds;
4675
+ });
4676
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(
4677
+ () => () => {
4678
+ const doc = controller.root?.ownerDocument;
4679
+ if (!doc || !latest.current(doc.activeElement, controller)) return;
4680
+ queueMicrotask(() => {
4681
+ if (doc.activeElement === null || doc.activeElement === doc.body) controller.focus();
4682
+ });
4683
+ },
4684
+ [controller]
4685
+ );
4513
4686
  }
4514
4687
  var DIVIDER = "mx-0.5 h-5 w-px shrink-0 bg-border";
4515
4688
  function BubbleToolbar({ controller, labels, store, extraItems }) {
4516
- const state = chunk2QKFRKOP_cjs.useEditorState(controller, chunk2QKFRKOP_cjs.wholeState);
4689
+ const state = chunkN25EH2GZ_cjs.useEditorState(controller, chunkN25EH2GZ_cjs.wholeState);
4517
4690
  const { anchor, side } = useSelectionAnchor(controller, state);
4518
4691
  const roving = useRovingFocus();
4519
4692
  const items = React7.useMemo(() => bubbleItems(labels, () => store.set({ linkPopover: { open: true } })), [labels, store]);
4520
4693
  useFocusKey(controller, roving.focusCurrent);
4694
+ useFocusBackToText(controller);
4521
4695
  const onKeyDown = (event) => {
4522
4696
  if (!roving.owns(event.target)) return;
4523
4697
  if (event.key === "Escape" || event.key === "Tab" && !event.altKey && !event.ctrlKey && !event.metaKey) {
@@ -4556,29 +4730,15 @@ function BubbleToolbar({ controller, labels, store, extraItems }) {
4556
4730
  )
4557
4731
  ] });
4558
4732
  }
4559
- var listOf = (query) => typeof window.matchMedia === "function" ? window.matchMedia(query) : null;
4560
- var readOnServer = () => false;
4561
- function useMediaQuery(query, unsupported) {
4562
- const subscribe3 = React7.useCallback(
4563
- (notify) => {
4564
- const list2 = listOf(query);
4565
- list2?.addEventListener("change", notify);
4566
- return () => list2?.removeEventListener("change", notify);
4567
- },
4568
- [query]
4569
- );
4570
- const read2 = () => listOf(query)?.matches ?? unsupported;
4571
- return React7.useSyncExternalStore(subscribe3, read2, readOnServer);
4572
- }
4573
4733
 
4574
4734
  // src/menus/bubble/use-fine-pointer.ts
4575
4735
  var useFinePointer = () => useMediaQuery("(pointer: fine)", true);
4576
4736
 
4577
4737
  // src/menus/bubble/use-bubble-visibility.ts
4578
- var hasTextRange = (state) => state.selection?.kind === "text" && !chunkFK7EBZPX_cjs.isCollapsed(state.selection) && chunkFK7EBZPX_cjs.markRanges(state).length > 0;
4738
+ var hasTextRange = (state) => state.selection?.kind === "text" && !chunkZ5EEDCRI_cjs.isCollapsed(state.selection) && chunkZ5EEDCRI_cjs.markRanges(state).length > 0;
4579
4739
  function useBubbleVisibility(controller) {
4580
- const range = chunk2QKFRKOP_cjs.useEditorState(controller, hasTextRange);
4581
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
4740
+ const range = chunkN25EH2GZ_cjs.useEditorState(controller, hasTextRange);
4741
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
4582
4742
  const fine = useFinePointer();
4583
4743
  const settled = useSelectionSettled(controller);
4584
4744
  return range && !readOnly && fine && settled;
@@ -4595,16 +4755,16 @@ function BubbleOverlay({ controller }) {
4595
4755
  return /* @__PURE__ */ jsxRuntime.jsx(BubbleToolbar, { controller, labels, store, extraItems });
4596
4756
  }
4597
4757
  function bubbleExtension() {
4598
- return chunkFK7EBZPX_cjs.defineExtension({ name: "bubble", ui: { overlays: [BubbleOverlay] } });
4758
+ return chunkZ5EEDCRI_cjs.defineExtension({ name: "bubble", ui: { overlays: [BubbleOverlay] } });
4599
4759
  }
4600
4760
 
4601
4761
  // src/menus/link/link-at-selection.ts
4602
4762
  function linkAround(state, from, to = from) {
4603
4763
  if (from.block !== to.block || from.region !== to.region) return null;
4604
- const inline = chunk2QKFRKOP_cjs.regionInline(state, from.block, from.region) ?? [];
4605
- const extent = chunkFK7EBZPX_cjs.markExtent(inline, from.offset, "link");
4764
+ const inline = chunkN25EH2GZ_cjs.regionInline(state, from.block, from.region) ?? [];
4765
+ const extent = chunkZ5EEDCRI_cjs.markExtent(inline, from.offset, "link");
4606
4766
  if (extent === null || to.offset > extent.to) return null;
4607
- const attrs = chunkI7X7B3WD_cjs.sliceInline(inline, extent.from, extent.to)[0]?.marks.find((mark) => mark.type === "link")?.attrs;
4767
+ const attrs = chunk4JDRDPLC_cjs.sliceInline(inline, extent.from, extent.to)[0]?.marks.find((mark) => mark.type === "link")?.attrs;
4608
4768
  if (attrs?.href === void 0) return null;
4609
4769
  const range = { block: from.block, region: from.region, ...extent };
4610
4770
  return { href: attrs.href, title: attrs.title || void 0, range };
@@ -4612,9 +4772,15 @@ function linkAround(state, from, to = from) {
4612
4772
  function linkAtSelection(state) {
4613
4773
  const { selection } = state;
4614
4774
  if (selection?.kind !== "text") return null;
4615
- const { from, to } = chunkFK7EBZPX_cjs.orderedRange(state.doc, selection.anchor, selection.head);
4775
+ const { from, to } = chunkZ5EEDCRI_cjs.orderedRange(state.doc, selection.anchor, selection.head);
4616
4776
  return linkAround(state, from, to);
4617
4777
  }
4778
+ function sameLink(a, b) {
4779
+ if (a === null || b === null) return a === b;
4780
+ const [x, y] = [a.range, b.range];
4781
+ const sameRange2 = x.block === y.block && x.region === y.region && x.from === y.from && x.to === y.to;
4782
+ return sameRange2 && a.href === b.href && a.title === b.title;
4783
+ }
4618
4784
 
4619
4785
  // src/menus/link/link-element.ts
4620
4786
  var LINK = "a[data-quoin-link]";
@@ -4624,18 +4790,23 @@ function linkElementAt(controller, target) {
4624
4790
  }
4625
4791
  function linkAtElement(controller, state, element) {
4626
4792
  const host = controller.registry.locate(element);
4627
- const offset = host === null ? null : chunk2QKFRKOP_cjs.domMapOf(host.element)?.fromDom(element, 0) ?? null;
4793
+ const offset = host === null ? null : chunkN25EH2GZ_cjs.domMapOf(host.element)?.fromDom(element, 0) ?? null;
4628
4794
  if (host === null || offset === null) return null;
4629
4795
  return linkAround(state, { block: host.block, region: host.region, offset });
4630
4796
  }
4631
4797
  var sameStart = (a, b) => a.block === b.block && a.region === b.region && a.from === b.from;
4632
- function sameLink(controller, a, b) {
4798
+ function sameLink2(controller, a, b) {
4633
4799
  if (a === b || a === null || b === null) return a === b;
4634
4800
  const { state } = controller.editor;
4635
4801
  const first = linkAtElement(controller, state, a)?.range;
4636
4802
  const second = linkAtElement(controller, state, b)?.range;
4637
4803
  return first !== void 0 && second !== void 0 && sameStart(first, second);
4638
4804
  }
4805
+ function OpenLinkButton({ href, label }) {
4806
+ const safe = chunk4JDRDPLC_cjs.sanitizeHref(href);
4807
+ if (!safe) return null;
4808
+ return /* @__PURE__ */ jsxRuntime.jsx(Button, { asChild: true, variant: "ghost", size: "icon-sm", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: safe, target: "_blank", rel: "noopener noreferrer", "aria-label": label, children: /* @__PURE__ */ jsxRuntime.jsx(ExternalLink, {}) }) });
4809
+ }
4639
4810
 
4640
4811
  // src/menus/link/truncate-middle.ts
4641
4812
  var ELLIPSIS = "\u2026";
@@ -4650,7 +4821,7 @@ function truncateMiddle(text2, max) {
4650
4821
  // src/menus/link/unlink-range.ts
4651
4822
  function unlinkRange({ block, region, from, to }) {
4652
4823
  return (state, dispatch) => {
4653
- const tr = new chunkFK7EBZPX_cjs.Transaction(state).step(chunkFK7EBZPX_cjs.removeMarkStep(block, region, from, to, "link"));
4824
+ const tr = new chunkZ5EEDCRI_cjs.Transaction(state).step(chunkZ5EEDCRI_cjs.removeMarkStep(block, region, from, to, "link"));
4654
4825
  if (!tr.docChanged) return false;
4655
4826
  dispatch?.(tr);
4656
4827
  return true;
@@ -4672,12 +4843,12 @@ function useRestedValue(value, delay) {
4672
4843
  // src/menus/link/use-caret-in-link.ts
4673
4844
  function caretLinkKey(state) {
4674
4845
  const { selection } = state;
4675
- if (selection?.kind !== "text" || !chunkFK7EBZPX_cjs.isCollapsed(selection)) return null;
4846
+ if (selection?.kind !== "text" || !chunkZ5EEDCRI_cjs.isCollapsed(selection)) return null;
4676
4847
  const link = linkAtSelection(state);
4677
4848
  return link && [link.range.block, link.range.region, link.range.from, link.href].join("\0");
4678
4849
  }
4679
4850
  function useCaretInLink(controller, delay) {
4680
- const key = chunk2QKFRKOP_cjs.useEditorState(controller, caretLinkKey);
4851
+ const key = chunkN25EH2GZ_cjs.useEditorState(controller, caretLinkKey);
4681
4852
  const settled = useSelectionSettled(controller);
4682
4853
  return useRestedValue(settled ? key : null, delay) !== null;
4683
4854
  }
@@ -4696,7 +4867,7 @@ function usePointerLink(controller, card, delay) {
4696
4867
  const onCard = !!card.current?.contains(event.target);
4697
4868
  const next = onCard ? shown.current : linkElementAt(controller, event.target);
4698
4869
  clearTimeout(timer.current);
4699
- if (!sameLink(controller, next, shown.current)) timer.current = setTimeout(() => show(next), delay);
4870
+ if (!sameLink2(controller, next, shown.current)) timer.current = setTimeout(() => show(next), delay);
4700
4871
  };
4701
4872
  document.addEventListener("pointerover", onOver);
4702
4873
  return () => {
@@ -4707,15 +4878,16 @@ function usePointerLink(controller, card, delay) {
4707
4878
  return { element, dismiss: React7.useCallback(() => show(null), [show]) };
4708
4879
  }
4709
4880
  var REST_MS = 300;
4881
+ var OPEN_POPOVER = "Mod-k";
4710
4882
  var URL_LENGTH = 48;
4711
4883
  var CARD = "flex w-auto items-center gap-0.5 py-1 ps-3 pe-1 shadow-md";
4712
4884
  var ADDRESS = "me-1 whitespace-nowrap text-sm text-muted-foreground";
4713
4885
  function LinkCard({ controller, labels, store, cardRef, element, onUnlinked }) {
4714
- const state = chunk2QKFRKOP_cjs.useEditorState(controller, chunk2QKFRKOP_cjs.wholeState);
4715
- const link = React7.useMemo(
4716
- () => element === null ? linkAtSelection(state) : linkAtElement(controller, state, element),
4717
- [controller, state, element]
4886
+ const select = React7.useCallback(
4887
+ (state) => element === null ? linkAtSelection(state) : linkAtElement(controller, state, element),
4888
+ [controller, element]
4718
4889
  );
4890
+ const link = chunkN25EH2GZ_cjs.useEditorState(controller, select, sameLink);
4719
4891
  const anchor = React7.useMemo(() => {
4720
4892
  if (link === null) return { current: null };
4721
4893
  const { range } = link;
@@ -4724,12 +4896,11 @@ function LinkCard({ controller, labels, store, cardRef, element, onUnlinked }) {
4724
4896
  }, [controller, link]);
4725
4897
  if (link === null) return null;
4726
4898
  const { block, region, from, to } = link.range;
4727
- const href = chunkI7X7B3WD_cjs.sanitizeHref(link.href);
4728
4899
  const address = truncateMiddle(link.href, URL_LENGTH);
4729
4900
  const edit2 = () => {
4730
4901
  if (element !== null) {
4731
- const selection = chunkFK7EBZPX_cjs.textSelection({ block, region, offset: from }, { block, region, offset: to });
4732
- chunk2QKFRKOP_cjs.dispatchSelection(controller, selection);
4902
+ const selection = chunkZ5EEDCRI_cjs.textSelection({ block, region, offset: from }, { block, region, offset: to });
4903
+ chunkN25EH2GZ_cjs.dispatchSelection(controller, selection);
4733
4904
  }
4734
4905
  store.set({ linkPopover: { open: true } });
4735
4906
  };
@@ -4743,6 +4914,7 @@ function LinkCard({ controller, labels, store, cardRef, element, onUnlinked }) {
4743
4914
  PopoverContent,
4744
4915
  {
4745
4916
  ref: cardRef,
4917
+ role: "group",
4746
4918
  "aria-label": labels.link,
4747
4919
  side: "bottom",
4748
4920
  align: "start",
@@ -4753,8 +4925,19 @@ function LinkCard({ controller, labels, store, cardRef, element, onUnlinked }) {
4753
4925
  onMouseDown: keepFocus,
4754
4926
  children: [
4755
4927
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: ADDRESS, title: address === link.href ? void 0 : link.href, children: address }),
4756
- href !== null && /* @__PURE__ */ jsxRuntime.jsx(Button, { asChild: true, variant: "ghost", size: "icon-sm", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": labels.linkOpen, children: /* @__PURE__ */ jsxRuntime.jsx(ExternalLink, {}) }) }),
4757
- /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": labels.linkEdit, onClick: edit2, children: /* @__PURE__ */ jsxRuntime.jsx(Pencil, {}) }),
4928
+ /* @__PURE__ */ jsxRuntime.jsx(OpenLinkButton, { href: link.href, label: labels.linkOpen }),
4929
+ /* @__PURE__ */ jsxRuntime.jsx(
4930
+ Button,
4931
+ {
4932
+ type: "button",
4933
+ variant: "ghost",
4934
+ size: "icon-sm",
4935
+ "aria-label": labels.linkEdit,
4936
+ "aria-keyshortcuts": shortcutHint(OPEN_POPOVER, controller.isMac, labels).aria,
4937
+ onClick: edit2,
4938
+ children: /* @__PURE__ */ jsxRuntime.jsx(Pencil, {})
4939
+ }
4940
+ ),
4758
4941
  /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": labels.linkRemove, onClick: unlink, children: /* @__PURE__ */ jsxRuntime.jsx(Unlink, {}) })
4759
4942
  ]
4760
4943
  }
@@ -4771,12 +4954,12 @@ function LinkHoverCard(props) {
4771
4954
 
4772
4955
  // src/menus/link/apply-link.ts
4773
4956
  function insertLinkedText(value, href, caret) {
4774
- const safe = chunkI7X7B3WD_cjs.sanitizeHref(href);
4957
+ const safe = chunk4JDRDPLC_cjs.sanitizeHref(href);
4775
4958
  return (state, dispatch) => {
4776
4959
  if (!safe) return false;
4777
4960
  const link = { type: "link", attrs: { href: safe } };
4778
- const marks = chunkI7X7B3WD_cjs.addMark(chunkFK7EBZPX_cjs.caretMarks(state.doc, state.schema, state.storedMarks, caret), link);
4779
- const tr = new chunkFK7EBZPX_cjs.Transaction(state).step(chunkFK7EBZPX_cjs.replaceInlineStep(caret.block, caret.region, caret.offset, caret.offset, [chunkI7X7B3WD_cjs.text(value, marks)])).setSelection(chunkFK7EBZPX_cjs.textSelection({ ...caret, offset: caret.offset + value.length }));
4961
+ const marks = chunk4JDRDPLC_cjs.addMark(chunkZ5EEDCRI_cjs.caretMarks(state.doc, state.schema, state.storedMarks, caret), link);
4962
+ const tr = new chunkZ5EEDCRI_cjs.Transaction(state).step(chunkZ5EEDCRI_cjs.replaceInlineStep(caret.block, caret.region, caret.offset, caret.offset, [chunk4JDRDPLC_cjs.text(value, marks)])).setSelection(chunkZ5EEDCRI_cjs.textSelection({ ...caret, offset: caret.offset + value.length }));
4780
4963
  dispatch?.(tr);
4781
4964
  return true;
4782
4965
  };
@@ -4785,21 +4968,22 @@ function applyLink(href, value) {
4785
4968
  return (state, dispatch) => {
4786
4969
  const { selection } = state;
4787
4970
  const link = linkAtSelection(state);
4788
- if (selection?.kind === "text" && chunkFK7EBZPX_cjs.isCollapsed(selection) && link === null) {
4971
+ if (selection?.kind === "text" && chunkZ5EEDCRI_cjs.isCollapsed(selection) && link === null) {
4789
4972
  return insertLinkedText(value, href, selection.head)(state, dispatch);
4790
4973
  }
4791
- return chunkFK7EBZPX_cjs.setLink(href, link?.title)(state, dispatch);
4974
+ return chunkZ5EEDCRI_cjs.setLink(href, link?.title)(state, dispatch);
4792
4975
  };
4793
4976
  }
4794
4977
  var CONTENT2 = "w-80 p-2 shadow-md";
4795
4978
  var FIELD2 = "h-8";
4796
4979
  var MESSAGE = "mt-1.5 px-1 text-sm text-destructive";
4980
+ var CONTROLS = "input, button, a[href]";
4797
4981
  function popoverAnchor(controller, link) {
4798
4982
  const { selection } = controller.editor.state;
4799
4983
  if (selection?.kind !== "text") return null;
4800
4984
  const { block, region, offset } = selection.head;
4801
4985
  const caret = link?.range ?? { block, region, from: offset, to: offset };
4802
- const collapsed = chunkFK7EBZPX_cjs.isCollapsed(selection);
4986
+ const collapsed = chunkZ5EEDCRI_cjs.isCollapsed(selection);
4803
4987
  return virtualAnchor(
4804
4988
  () => collapsed ? rangeRect(controller, caret) : selectionRect(controller),
4805
4989
  () => controller.registry.get(block, region)
@@ -4811,7 +4995,8 @@ function LinkPopover({ controller, labels, onClose }) {
4811
4995
  const [invalid, setInvalid] = React7.useState(false);
4812
4996
  const messageId = React7.useId();
4813
4997
  const anchor = React7.useMemo(() => ({ current: popoverAnchor(controller, link) }), [controller, link]);
4814
- const openHref = link && chunkI7X7B3WD_cjs.sanitizeHref(link.href);
4998
+ const content = React7.useRef(null);
4999
+ useFocusBackToText(controller, (focused) => focused !== null && content.current?.contains(focused) === true);
4815
5000
  const backToText = () => {
4816
5001
  onClose();
4817
5002
  controller.focus();
@@ -4823,12 +5008,23 @@ function LinkPopover({ controller, labels, onClose }) {
4823
5008
  const apply = (event) => {
4824
5009
  event.preventDefault();
4825
5010
  const href = linkAddress(address);
4826
- if (href === null) return setInvalid(true);
5011
+ if (href === null) {
5012
+ setInvalid(true);
5013
+ controller.announce(labels.invalidLink);
5014
+ return;
5015
+ }
4827
5016
  controller.run(applyLink(href, address.trim()));
4828
5017
  backToText();
4829
5018
  };
4830
5019
  const remove = () => {
4831
- controller.run(chunkFK7EBZPX_cjs.unsetLink);
5020
+ controller.run(chunkZ5EEDCRI_cjs.unsetLink);
5021
+ backToText();
5022
+ };
5023
+ const tabOut = (event) => {
5024
+ if (event.key !== "Tab" || event.ctrlKey || event.metaKey) return;
5025
+ const controls = [...event.currentTarget.querySelectorAll(CONTROLS)];
5026
+ if (event.target !== (event.shiftKey ? controls[0] : controls.at(-1))) return;
5027
+ event.preventDefault();
4832
5028
  backToText();
4833
5029
  };
4834
5030
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -4844,6 +5040,7 @@ function LinkPopover({ controller, labels, onClose }) {
4844
5040
  /* @__PURE__ */ jsxRuntime.jsxs(
4845
5041
  PopoverContent,
4846
5042
  {
5043
+ ref: content,
4847
5044
  "aria-label": labels.link,
4848
5045
  side: "bottom",
4849
5046
  align: "start",
@@ -4853,6 +5050,7 @@ function LinkPopover({ controller, labels, onClose }) {
4853
5050
  event.preventDefault();
4854
5051
  backToText();
4855
5052
  },
5053
+ onKeyDown: tabOut,
4856
5054
  children: [
4857
5055
  /* @__PURE__ */ jsxRuntime.jsxs("form", { noValidate: true, className: "flex items-center gap-0.5", onSubmit: apply, children: [
4858
5056
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4868,10 +5066,10 @@ function LinkPopover({ controller, labels, onClose }) {
4868
5066
  }
4869
5067
  ),
4870
5068
  /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", variant: "ghost", size: "icon-sm", "aria-label": labels.linkApply, children: /* @__PURE__ */ jsxRuntime.jsx(Check, {}) }),
4871
- openHref && /* @__PURE__ */ jsxRuntime.jsx(Button, { asChild: true, variant: "ghost", size: "icon-sm", children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: openHref, target: "_blank", rel: "noopener noreferrer", "aria-label": labels.linkOpen, children: /* @__PURE__ */ jsxRuntime.jsx(ExternalLink, {}) }) }),
5069
+ link !== null && /* @__PURE__ */ jsxRuntime.jsx(OpenLinkButton, { href: link.href, label: labels.linkOpen }),
4872
5070
  link !== null && /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": labels.linkRemove, onClick: remove, children: /* @__PURE__ */ jsxRuntime.jsx(Unlink, {}) })
4873
5071
  ] }),
4874
- invalid && /* @__PURE__ */ jsxRuntime.jsx("p", { id: messageId, role: "alert", className: MESSAGE, children: labels.invalidLink })
5072
+ invalid && /* @__PURE__ */ jsxRuntime.jsx("p", { id: messageId, className: MESSAGE, children: labels.invalidLink })
4875
5073
  ]
4876
5074
  }
4877
5075
  )
@@ -4896,7 +5094,7 @@ function LinkOverlay({ controller }) {
4896
5094
  const labels = useLabels();
4897
5095
  const store = useCanvasUiStore();
4898
5096
  const popoverOpen = useCanvasUi(store, isPopoverOpen);
4899
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
5097
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
4900
5098
  useLinkClicks(controller);
4901
5099
  React7.useEffect(() => {
4902
5100
  if (readOnly && store.get().linkPopover.open) store.set({ linkPopover: { open: false } });
@@ -4911,7 +5109,7 @@ function linkExtension(store) {
4911
5109
  if (dispatch) store.set({ linkPopover: { open: true } });
4912
5110
  return true;
4913
5111
  };
4914
- return chunkFK7EBZPX_cjs.defineExtension({ name: "link", keymap: { "Mod-k": openPopover }, ui: { overlays: [LinkOverlay] } });
5112
+ return chunkZ5EEDCRI_cjs.defineExtension({ name: "link", keymap: { "Mod-k": openPopover }, ui: { overlays: [LinkOverlay] } });
4915
5113
  }
4916
5114
 
4917
5115
  // src/menus/suggestion/find-trigger.ts
@@ -4919,7 +5117,7 @@ var DEFAULT_MAX_LENGTH = 40;
4919
5117
  var NO_TRIGGER_TYPES = /* @__PURE__ */ new Set(["CODE", "TABLE"]);
4920
5118
  var WHITESPACE2 = /\s/;
4921
5119
  var ATOM = "\uFFFC";
4922
- var hasCode = (inline) => inline.some((node) => chunkI7X7B3WD_cjs.hasMark(node.marks, "code"));
5120
+ var hasCode = (inline) => inline.some((node) => chunk4JDRDPLC_cjs.hasMark(node.marks, "code"));
4923
5121
  function triggerOffset(text2, start, config) {
4924
5122
  const { char, allowSpaces = false, startOfLine = false } = config;
4925
5123
  const earliest = Math.max(0, text2.length - (config.maxLength ?? DEFAULT_MAX_LENGTH) - 1);
@@ -4928,7 +5126,8 @@ function triggerOffset(text2, start, config) {
4928
5126
  if (character === "\n" || character === ATOM) return null;
4929
5127
  if (character === char) {
4930
5128
  const atRegionStart = start + i === 0;
4931
- if (atRegionStart || !startOfLine && i > 0 && WHITESPACE2.test(text2[i - 1])) return start + i;
5129
+ const opens2 = atRegionStart || !startOfLine && i > 0 && WHITESPACE2.test(text2[i - 1]);
5130
+ if (opens2) return WHITESPACE2.test(text2[i + 1] ?? "") ? null : start + i;
4932
5131
  }
4933
5132
  if (!allowSpaces && WHITESPACE2.test(character)) return null;
4934
5133
  }
@@ -4936,15 +5135,15 @@ function triggerOffset(text2, start, config) {
4936
5135
  }
4937
5136
  function findTrigger(state, config) {
4938
5137
  const { doc, selection } = state;
4939
- if (selection?.kind !== "text" || !chunkFK7EBZPX_cjs.isCollapsed(selection)) return null;
5138
+ if (selection?.kind !== "text" || !chunkZ5EEDCRI_cjs.isCollapsed(selection)) return null;
4940
5139
  const { block: blockId, region, offset: caret } = selection.head;
4941
- const block = chunkI7X7B3WD_cjs.blockById(doc, blockId);
5140
+ const block = chunk4JDRDPLC_cjs.blockById(doc, blockId);
4942
5141
  if (block === void 0 || NO_TRIGGER_TYPES.has(block.type) || !Object.hasOwn(block.regions, region)) return null;
4943
5142
  const inline = block.regions[region];
4944
5143
  const start = Math.max(0, caret - (config.maxLength ?? DEFAULT_MAX_LENGTH) - 2);
4945
- const from = triggerOffset(chunkI7X7B3WD_cjs.inlineText(chunkI7X7B3WD_cjs.sliceInline(inline, start, caret)), start, config);
4946
- if (from === null || hasCode(chunkI7X7B3WD_cjs.sliceInline(inline, from, caret))) return null;
4947
- const query = chunkI7X7B3WD_cjs.inlineText(chunkI7X7B3WD_cjs.sliceInline(inline, from + 1, caret));
5144
+ const from = triggerOffset(chunk4JDRDPLC_cjs.inlineText(chunk4JDRDPLC_cjs.sliceInline(inline, start, caret)), start, config);
5145
+ if (from === null || hasCode(chunk4JDRDPLC_cjs.sliceInline(inline, from, caret))) return null;
5146
+ const query = chunk4JDRDPLC_cjs.inlineText(chunk4JDRDPLC_cjs.sliceInline(inline, from + 1, caret));
4948
5147
  return { block: blockId, region, from, to: caret, query };
4949
5148
  }
4950
5149
 
@@ -4959,11 +5158,13 @@ function stillDismissed(tr, dismissed, found) {
4959
5158
  const same = found.block === at.block && found.region === at.region && found.from === at.offset;
4960
5159
  return same ? rangeOf2(found) : null;
4961
5160
  }
4962
- function nearest(state, found) {
4963
- if (found === null) return null;
5161
+ var opens = (found, { minLength = 0 }) => found !== null && found.query.length >= minLength;
5162
+ function nearest(state, found, config) {
5163
+ if (!opens(found, config)) return null;
4964
5164
  for (const { trigger } of state.plugins) {
4965
- const other = trigger && findTrigger(state, trigger);
4966
- if (other && other.from > found.from) return null;
5165
+ if (trigger === void 0) continue;
5166
+ const other = findTrigger(state, trigger);
5167
+ if (opens(other, trigger) && other.from > found.from) return null;
4967
5168
  }
4968
5169
  return found;
4969
5170
  }
@@ -4971,15 +5172,15 @@ function suggestionPlugin(key, config) {
4971
5172
  return {
4972
5173
  key,
4973
5174
  trigger: config,
4974
- init: (state) => ({ active: nearest(state, findTrigger(state, config)), dismissedAt: null }),
5175
+ init: (state) => ({ active: nearest(state, findTrigger(state, config), config), dismissedAt: null }),
4975
5176
  apply(tr, value, oldState, newState) {
4976
- const dismissing = tr.getMeta(DISMISS) === key.name;
4977
- if (!dismissing && !tr.docChanged && chunkFK7EBZPX_cjs.sameSelection(oldState.selection, newState.selection)) return value;
5177
+ const dismissing = tr.getMeta(DISMISS) !== void 0;
5178
+ if (!dismissing && !tr.docChanged && chunkZ5EEDCRI_cjs.sameSelection(oldState.selection, newState.selection)) return value;
4978
5179
  const found = findTrigger(newState, config);
4979
5180
  let dismissedAt = null;
4980
5181
  if (dismissing) dismissedAt = found && rangeOf2(found);
4981
5182
  else if (value.dismissedAt !== null) dismissedAt = stillDismissed(tr, value.dismissedAt, found);
4982
- const active = dismissedAt === null ? nearest(newState, found) : null;
5183
+ const active = dismissedAt === null ? nearest(newState, found, config) : null;
4983
5184
  const unchanged = sameTrigger(active, value.active) && sameRange(dismissedAt, value.dismissedAt);
4984
5185
  if (unchanged) return value;
4985
5186
  return {
@@ -4991,14 +5192,14 @@ function suggestionPlugin(key, config) {
4991
5192
  }
4992
5193
  function dismissSuggestion(key) {
4993
5194
  return (state, dispatch) => {
4994
- if (chunkFK7EBZPX_cjs.getPluginState(state, key).active === null) return false;
4995
- dispatch?.(new chunkFK7EBZPX_cjs.Transaction(state).setMeta(DISMISS, key.name));
5195
+ if (chunkZ5EEDCRI_cjs.getPluginState(state, key).active === null) return false;
5196
+ dispatch?.(new chunkZ5EEDCRI_cjs.Transaction(state).setMeta(DISMISS, key.name));
4996
5197
  return true;
4997
5198
  };
4998
5199
  }
4999
5200
 
5000
5201
  // src/menus/slash/slash-key.ts
5001
- var slashKey = chunkFK7EBZPX_cjs.createPluginKey("slash");
5202
+ var slashKey = chunkZ5EEDCRI_cjs.createPluginKey("slash");
5002
5203
 
5003
5204
  // src/menus/slash/slash-items.ts
5004
5205
  var DETAILS = {
@@ -5022,7 +5223,7 @@ function insertionItem(id, insertion, labels, definitions) {
5022
5223
  const { label, icon, type, attrs } = insertion;
5023
5224
  return {
5024
5225
  id,
5025
- title: label,
5226
+ title: hostLabel(definitions, type) ?? label,
5026
5227
  description: labels.slashDescriptions[id],
5027
5228
  icon,
5028
5229
  ...DETAILS[id],
@@ -5048,7 +5249,7 @@ function initialBlock({ schema }, definition, onError) {
5048
5249
  const data = chunkA6PGQVTR_cjs.seedData(definition, onError());
5049
5250
  if (data === null) return null;
5050
5251
  if (data === void 0) return { type };
5051
- return { type, ...chunkI7X7B3WD_cjs.specFor(schema, type).fromData(data, chunkI7X7B3WD_cjs.createBlockCodec(schema).context) };
5252
+ return { type, ...chunk4JDRDPLC_cjs.specFor(schema, type).fromData(data, chunk4JDRDPLC_cjs.createBlockCodec(schema).context) };
5052
5253
  }
5053
5254
  var refuse = () => false;
5054
5255
  function blockTypeSlashItems(definitions, onError = () => void 0) {
@@ -5076,43 +5277,69 @@ function useRangeAnchor(controller, range) {
5076
5277
  const [side, setSide] = React7.useState("bottom");
5077
5278
  const latest = React7.useRef(range);
5078
5279
  const start = range === null ? null : `${range.block} ${range.region} ${range.from}`;
5079
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5280
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5080
5281
  latest.current = range;
5081
5282
  });
5082
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5283
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5083
5284
  const rect = latest.current && rangeRect(controller, latest.current);
5084
5285
  const height = controller.root?.ownerDocument.defaultView?.innerHeight;
5085
5286
  if (rect && height !== void 0) setSide(sideFor(rect, height));
5086
5287
  }, [controller, start]);
5087
5288
  return { anchor, side };
5088
5289
  }
5290
+
5291
+ // src/to-error.ts
5292
+ var toError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
5089
5293
  var ADDED = ["aria-expanded", "aria-controls", "aria-autocomplete", "aria-activedescendant"];
5090
5294
  var SWAPPED = ["role", "aria-multiline"];
5091
5295
  function restore(host, name, value) {
5092
5296
  if (value === null) host.removeAttribute(name);
5093
5297
  else host.setAttribute(name, value);
5094
5298
  }
5095
- function openCombobox(host, listboxId) {
5096
- const saved = SWAPPED.map((name) => [name, host.getAttribute(name)]);
5299
+ var held = /* @__PURE__ */ new WeakMap();
5300
+ function makeCombobox(host, listboxId) {
5097
5301
  host.setAttribute("role", "combobox");
5098
5302
  host.removeAttribute("aria-multiline");
5099
5303
  host.setAttribute("aria-expanded", "true");
5100
5304
  host.setAttribute("aria-controls", listboxId);
5101
5305
  host.setAttribute("aria-autocomplete", "list");
5306
+ }
5307
+ function openCombobox(host, listboxId) {
5308
+ const saved = () => SWAPPED.map((name) => [name, host.getAttribute(name)]);
5309
+ const entry = held.get(host) ?? { saved: saved(), listboxes: [] };
5310
+ held.set(host, entry);
5311
+ entry.listboxes.push(listboxId);
5312
+ makeCombobox(host, listboxId);
5102
5313
  return () => {
5314
+ entry.listboxes.splice(entry.listboxes.lastIndexOf(listboxId), 1);
5315
+ const remaining = entry.listboxes.at(-1);
5316
+ if (remaining !== void 0) return makeCombobox(host, remaining);
5317
+ held.delete(host);
5103
5318
  for (const name of ADDED) host.removeAttribute(name);
5104
- for (const [name, value] of saved) restore(host, name, value);
5319
+ for (const [name, value] of entry.saved) restore(host, name, value);
5105
5320
  };
5106
5321
  }
5107
5322
  function useComboboxHost(controller, block, region, listboxId, activeOptionId) {
5108
- const host = React7.useRef(null);
5109
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5110
- host.current = block === null || region === null ? null : controller.registry.get(block, region) ?? null;
5111
- return host.current === null ? void 0 : openCombobox(host.current, listboxId);
5112
- }, [controller, block, region, listboxId]);
5113
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5114
- if (host.current !== null) restore(host.current, "aria-activedescendant", activeOptionId);
5115
- }, [block, region, activeOptionId]);
5323
+ const open = block !== null && region !== null;
5324
+ const docWhileOpen = React7.useCallback((state) => open ? state.doc : null, [open]);
5325
+ chunkN25EH2GZ_cjs.useEditorState(controller, docWhileOpen);
5326
+ const opened = React7.useRef(null);
5327
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5328
+ const host = open ? controller.registry.get(block, region) ?? null : null;
5329
+ const last2 = opened.current;
5330
+ if (last2?.host !== host || last2.listboxId !== listboxId) {
5331
+ last2?.close();
5332
+ opened.current = host && { host, listboxId, close: openCombobox(host, listboxId) };
5333
+ }
5334
+ if (opened.current !== null) restore(opened.current.host, "aria-activedescendant", activeOptionId);
5335
+ });
5336
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(
5337
+ () => () => {
5338
+ opened.current?.close();
5339
+ opened.current = null;
5340
+ },
5341
+ []
5342
+ );
5116
5343
  }
5117
5344
 
5118
5345
  // src/menus/suggestion/group-items.ts
@@ -5156,10 +5383,10 @@ function handle(event, menu) {
5156
5383
  }
5157
5384
  function useSuggestionKeys(controller, open, menu) {
5158
5385
  const latest = React7.useRef(menu);
5159
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5386
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5160
5387
  latest.current = menu;
5161
5388
  });
5162
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5389
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5163
5390
  if (!open) return;
5164
5391
  const intercept = (event) => {
5165
5392
  const handled = handle(event, latest.current);
@@ -5169,21 +5396,16 @@ function useSuggestionKeys(controller, open, menu) {
5169
5396
  return interceptKeys(controller, intercept);
5170
5397
  }, [controller, open]);
5171
5398
  }
5172
-
5173
- // src/to-error.ts
5174
- var toError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
5175
-
5176
- // src/menus/suggestion/use-suggestion-results.ts
5177
5399
  var FORGOTTEN = { source: null, query: null, items: [] };
5178
5400
  var CLOSED = { items: [], pending: false };
5179
5401
  var isPromise = (value) => typeof value?.then === "function";
5180
5402
  function useSuggestionResults(source, query, onError) {
5181
5403
  const [answer, setAnswer] = React7.useState(FORGOTTEN);
5182
5404
  const report = React7.useRef(onError);
5183
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5405
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5184
5406
  report.current = onError;
5185
5407
  });
5186
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5408
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5187
5409
  if (query === null) {
5188
5410
  setAnswer(FORGOTTEN);
5189
5411
  return;
@@ -5224,12 +5446,13 @@ function useSuggestion({
5224
5446
  itemKey: itemKey2,
5225
5447
  groupOf: groupOf2,
5226
5448
  onSelect,
5227
- emptyLabel
5449
+ emptyLabel,
5450
+ countLabel
5228
5451
  }) {
5229
5452
  const { labels, onError } = useQuoin();
5230
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
5231
- const selectTrigger = React7.useCallback((state) => chunkFK7EBZPX_cjs.getPluginState(state, pluginKey).active, [pluginKey]);
5232
- const active = chunk2QKFRKOP_cjs.useEditorState(controller, selectTrigger);
5453
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
5454
+ const selectTrigger = React7.useCallback((state) => chunkZ5EEDCRI_cjs.getPluginState(state, pluginKey).active, [pluginKey]);
5455
+ const active = chunkN25EH2GZ_cjs.useEditorState(controller, selectTrigger);
5233
5456
  const trigger = readOnly ? null : active;
5234
5457
  const results = useSuggestionResults(source, trigger?.query ?? null, onError);
5235
5458
  const sections = React7.useMemo(() => groupItems(results.items, groupOf2), [results.items, groupOf2]);
@@ -5251,20 +5474,32 @@ function useSuggestion({
5251
5474
  );
5252
5475
  const select = React7.useCallback(
5253
5476
  (index) => {
5254
- const current = chunkFK7EBZPX_cjs.getPluginState(controller.editor.state, pluginKey).active;
5255
- if (current !== null && index < ordered.length) onSelect(ordered[index], rangeOf2(current));
5477
+ const current = chunkZ5EEDCRI_cjs.getPluginState(controller.editor.state, pluginKey).active;
5478
+ if (current === null || index >= ordered.length) return;
5479
+ try {
5480
+ onSelect(ordered[index], rangeOf2(current));
5481
+ } catch (error) {
5482
+ onError(toError(error));
5483
+ }
5256
5484
  },
5257
- [controller, pluginKey, ordered, onSelect]
5485
+ [controller, pluginKey, ordered, onSelect, onError]
5258
5486
  );
5259
5487
  const dismiss = React7.useCallback(() => void controller.run(dismissSuggestion(pluginKey)), [controller, pluginKey]);
5260
5488
  useSuggestionKeys(controller, trigger !== null, { count: ordered.length, activeIndex, highlight, select, dismiss });
5261
5489
  useComboboxHost(controller, trigger?.block ?? null, trigger?.region ?? null, listboxId, activeId);
5262
5490
  const query = trigger?.query ?? null;
5263
5491
  const count = ordered.length;
5264
- const resultMessage = count === 0 ? emptyLabel : labels.resultCount(count);
5492
+ const resultMessage = count === 0 ? emptyLabel : (countLabel ?? labels.resultCount)(count);
5493
+ const said = React7.useRef(null);
5265
5494
  React7.useEffect(() => {
5266
5495
  if (query === null || results.pending) return;
5267
- const timer = setTimeout(() => controller.announce(resultMessage), SETTLE_MS);
5496
+ const timer = setTimeout(() => {
5497
+ const current = controller.announcer.current()?.message;
5498
+ const own = current === void 0 || current === said.current;
5499
+ const message = own ? resultMessage : `${current}. ${resultMessage}`;
5500
+ said.current = message;
5501
+ controller.announce(message);
5502
+ }, SETTLE_MS);
5268
5503
  return () => clearTimeout(timer);
5269
5504
  }, [controller, query, resultMessage, results.pending]);
5270
5505
  return { trigger, sections, pending: results.pending, activeIndex, listboxId, optionId, highlight, select, dismiss };
@@ -5299,7 +5534,7 @@ function SuggestionMenu(props) {
5299
5534
  const { trigger, sections, activeIndex, listboxId } = suggestion;
5300
5535
  const { anchor, side } = useRangeAnchor(controller, trigger);
5301
5536
  const listbox = React7.useRef(null);
5302
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
5537
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5303
5538
  listbox.current?.querySelector('[aria-selected="true"]')?.scrollIntoView?.({ block: "nearest" });
5304
5539
  }, [activeIndex, sections]);
5305
5540
  if (trigger === null) return null;
@@ -5358,14 +5593,14 @@ function SuggestionMenu(props) {
5358
5593
  // src/menus/suggestion/replace-trigger.ts
5359
5594
  function deleteTrigger({ block, region, from, to }) {
5360
5595
  return (state, dispatch) => {
5361
- const tr = new chunkFK7EBZPX_cjs.Transaction(state);
5362
- if (from >= to || !tr.maybeStep(chunkFK7EBZPX_cjs.replaceInlineStep(block, region, from, to, []))) return false;
5363
- dispatch?.(tr.setSelection(chunkFK7EBZPX_cjs.textSelection({ block, region, offset: from })));
5596
+ const tr = new chunkZ5EEDCRI_cjs.Transaction(state);
5597
+ if (from >= to || !tr.maybeStep(chunkZ5EEDCRI_cjs.replaceInlineStep(block, region, from, to, []))) return false;
5598
+ dispatch?.(tr.setSelection(chunkZ5EEDCRI_cjs.textSelection({ block, region, offset: from })));
5364
5599
  return true;
5365
5600
  };
5366
5601
  }
5367
5602
  function triggerReplacement(state, range, command) {
5368
- return chunkFK7EBZPX_cjs.runCommands(state, command === void 0 ? [deleteTrigger(range)] : [deleteTrigger(range), command]);
5603
+ return chunkZ5EEDCRI_cjs.runCommands(state, command === void 0 ? [deleteTrigger(range)] : [deleteTrigger(range), command]);
5369
5604
  }
5370
5605
  function replaceTrigger(controller, range, command) {
5371
5606
  const { ok, tr } = triggerReplacement(controller.editor.state, range, command);
@@ -5378,9 +5613,9 @@ var TEXT_STEPS = /* @__PURE__ */ new Set(["replaceInline", "addMark", "removeMar
5378
5613
  var WHITESPACE3 = /\s/;
5379
5614
  var changesBlocks = (tr) => tr !== null && tr.steps.some((step) => !TEXT_STEPS.has(step.stepType));
5380
5615
  function withSpaceBefore(state, range) {
5381
- const inline = chunk2QKFRKOP_cjs.regionInline(state, range.block, range.region);
5616
+ const inline = chunkN25EH2GZ_cjs.regionInline(state, range.block, range.region);
5382
5617
  if (inline === void 0 || range.from === 0) return null;
5383
- const text2 = chunkI7X7B3WD_cjs.inlineText(inline);
5618
+ const text2 = chunk4JDRDPLC_cjs.inlineText(inline);
5384
5619
  const after = text2.charAt(range.to);
5385
5620
  const spaced = text2.charAt(range.from - 1) === " " && (after === "" || WHITESPACE3.test(after));
5386
5621
  return spaced ? { ...range, from: range.from - 1 } : null;
@@ -5485,7 +5720,7 @@ function SlashOverlay({ controller }) {
5485
5720
  return /* @__PURE__ */ jsxRuntime.jsx(SlashMenu, { controller, labels, items });
5486
5721
  }
5487
5722
  function slashExtension() {
5488
- return chunkFK7EBZPX_cjs.defineExtension({
5723
+ return chunkZ5EEDCRI_cjs.defineExtension({
5489
5724
  name: "slash",
5490
5725
  plugins: [suggestionPlugin(slashKey, { char: "/" })],
5491
5726
  ui: { overlays: [SlashOverlay] }
@@ -5494,10 +5729,10 @@ function slashExtension() {
5494
5729
 
5495
5730
  // src/mobile/mobile-items.tsx
5496
5731
  function commandItem(id, title, icon, command) {
5497
- return { id, title, icon, run: (controller) => void controller.run(command) };
5732
+ return { id, title, icon, enabled: (state) => command(state), run: (controller) => void controller.run(command) };
5498
5733
  }
5499
5734
  function addBlockAtCaret(controller) {
5500
- const block = chunkFK7EBZPX_cjs.headBlockId(controller.editor.state.selection);
5735
+ const block = chunkZ5EEDCRI_cjs.headBlockId(controller.editor.state.selection);
5501
5736
  if (block !== null) addBlockBelow(controller, block);
5502
5737
  }
5503
5738
  function mobileItems(labels, openLink) {
@@ -5509,16 +5744,13 @@ function mobileItems(labels, openLink) {
5509
5744
  markItem("strike", labels.strike, Strikethrough),
5510
5745
  markItem("code", labels.code, Code),
5511
5746
  linkItem(labels, openLink),
5512
- commandItem("indent", labels.indent, ListIndentIncrease, chunkFK7EBZPX_cjs.indent),
5513
- commandItem("outdent", labels.outdent, ListIndentDecrease, chunkFK7EBZPX_cjs.outdent),
5514
- commandItem("undo", labels.undo, Undo2, chunkFK7EBZPX_cjs.undo),
5515
- commandItem("redo", labels.redo, Redo2, chunkFK7EBZPX_cjs.redo)
5747
+ commandItem("indent", labels.indent, ListIndentIncrease, chunkZ5EEDCRI_cjs.indent),
5748
+ commandItem("outdent", labels.outdent, ListIndentDecrease, chunkZ5EEDCRI_cjs.outdent),
5749
+ commandItem("undo", labels.undo, Undo2, chunkZ5EEDCRI_cjs.undo),
5750
+ commandItem("redo", labels.redo, Redo2, chunkZ5EEDCRI_cjs.redo)
5516
5751
  ]
5517
5752
  };
5518
5753
  }
5519
-
5520
- // src/mobile/use-coarse-pointer.ts
5521
- var useCoarsePointer = () => useMediaQuery("(pointer: coarse)", false);
5522
5754
  var FOCUS_EVENTS = ["focusin", "focusout"];
5523
5755
  function subscribe(notify) {
5524
5756
  for (const type of FOCUS_EVENTS) document.addEventListener(type, notify);
@@ -5531,6 +5763,7 @@ function useEditorFocus(controller) {
5531
5763
  const read2 = React7.useCallback(() => focusInEditor(controller), [controller]);
5532
5764
  return React7.useSyncExternalStore(subscribe, read2, NOT_FOCUSED);
5533
5765
  }
5766
+ var KEYS3 = ["offsetTop", "offsetLeft", "height", "width", "scale"];
5534
5767
  var EVENTS = ["resize", "scroll"];
5535
5768
  function subscribe2(notify) {
5536
5769
  const viewport = window.visualViewport;
@@ -5545,10 +5778,8 @@ var last = null;
5545
5778
  function read() {
5546
5779
  const viewport = window.visualViewport;
5547
5780
  if (!viewport) return null;
5548
- const { offsetTop, height, width } = viewport;
5549
- if (last?.offsetTop !== offsetTop || last.height !== height || last.width !== width) {
5550
- last = { offsetTop, height, width };
5551
- }
5781
+ const box = Object.fromEntries(KEYS3.map((key) => [key, viewport[key]]));
5782
+ if (last === null || KEYS3.some((key) => last?.[key] !== box[key])) last = box;
5552
5783
  return last;
5553
5784
  }
5554
5785
  var readOnServer2 = () => null;
@@ -5561,14 +5792,26 @@ var TOOLBAR = [
5561
5792
  ].join(" ");
5562
5793
  function placement(viewport) {
5563
5794
  if (viewport === null) return { bottom: 0 };
5564
- return { top: viewport.offsetTop + viewport.height, width: viewport.width, transform: "translateY(-100%)" };
5795
+ const { offsetLeft, offsetTop, height, width, scale } = viewport;
5796
+ const shift = "translateY(-100%)";
5797
+ return {
5798
+ top: offsetTop + height,
5799
+ left: offsetLeft,
5800
+ right: "auto",
5801
+ width: width * scale,
5802
+ transform: scale === 1 ? shift : `scale(${1 / scale}) ${shift}`,
5803
+ transformOrigin: "0 0"
5804
+ };
5565
5805
  }
5566
5806
  function EditingTools({ controller }) {
5567
5807
  const labels = useLabels();
5568
5808
  const store = useCanvasUiStore();
5569
- const state = chunk2QKFRKOP_cjs.useEditorState(controller, chunk2QKFRKOP_cjs.wholeState);
5809
+ const state = chunkN25EH2GZ_cjs.useEditorState(controller, chunkN25EH2GZ_cjs.wholeState);
5570
5810
  const viewport = useVisualViewport();
5571
5811
  const roving = useRovingFocus();
5812
+ const dir = reactDirection.useDirection();
5813
+ useFocusBackToText(controller);
5814
+ useFocusKey(controller, roving.focusCurrent);
5572
5815
  const items = React7.useMemo(() => mobileItems(labels, () => store.set({ linkPopover: { open: true } })), [labels, store]);
5573
5816
  const onKeyDown = (event) => {
5574
5817
  if (event.key === "Escape" && roving.owns(event.target)) {
@@ -5582,6 +5825,7 @@ function EditingTools({ controller }) {
5582
5825
  "div",
5583
5826
  {
5584
5827
  ref: roving.ref,
5828
+ dir,
5585
5829
  role: "toolbar",
5586
5830
  "aria-label": labels.mobileToolbar,
5587
5831
  className: TOOLBAR,
@@ -5628,12 +5872,14 @@ var DialogOverlay = /* @__PURE__ */ React7__namespace.forwardRef(function Dialog
5628
5872
  );
5629
5873
  });
5630
5874
  var DialogContent = /* @__PURE__ */ React7__namespace.forwardRef(function DialogContent2({ className, children, closeLabel = "Close", ...props }, ref) {
5875
+ const dir = reactDirection.useDirection();
5631
5876
  return /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
5632
5877
  /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
5633
5878
  /* @__PURE__ */ jsxRuntime.jsxs(
5634
5879
  DialogPrimitive__namespace.Content,
5635
5880
  {
5636
5881
  ref,
5882
+ dir,
5637
5883
  "data-slot": "dialog-content",
5638
5884
  className: cn(
5639
5885
  "quoin bg-popover fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
@@ -5646,7 +5892,7 @@ var DialogContent = /* @__PURE__ */ React7__namespace.forwardRef(function Dialog
5646
5892
  DialogPrimitive__namespace.Close,
5647
5893
  {
5648
5894
  className: cn(
5649
- "absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100",
5895
+ "absolute top-4 end-4 rounded-xs opacity-70 transition-opacity hover:opacity-100",
5650
5896
  FOCUS_RING,
5651
5897
  ICON_TAP_TARGET_FLOOR
5652
5898
  ),
@@ -5763,14 +6009,14 @@ function shortcutGroups(labels, isMac) {
5763
6009
  }
5764
6010
  ];
5765
6011
  }
5766
- var KEYS3 = "rounded border bg-muted px-1.5 py-0.5 font-sans text-xs whitespace-nowrap";
6012
+ var KEYS4 = "rounded border bg-muted px-1.5 py-0.5 font-sans text-xs whitespace-nowrap";
5767
6013
  function ShortcutSection({ group }) {
5768
6014
  const titleId = React7.useId();
5769
6015
  return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-labelledby": titleId, children: [
5770
6016
  /* @__PURE__ */ jsxRuntime.jsx("h3", { id: titleId, className: "mb-2 text-sm font-semibold", children: group.title }),
5771
6017
  /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "grid gap-1.5 text-sm", children: group.items.map(({ label, keys }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-4", children: [
5772
6018
  /* @__PURE__ */ jsxRuntime.jsx("dt", { children: label }),
5773
- /* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: KEYS3, children: keys }) })
6019
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: KEYS4, children: keys }) })
5774
6020
  ] }, `${label} ${keys}`)) })
5775
6021
  ] });
5776
6022
  }
@@ -5823,7 +6069,7 @@ function shortcutsExtension(store) {
5823
6069
  if (dispatch) store.set({ shortcutsOpen: true });
5824
6070
  return true;
5825
6071
  };
5826
- return chunkFK7EBZPX_cjs.defineExtension({
6072
+ return chunkZ5EEDCRI_cjs.defineExtension({
5827
6073
  name: "shortcuts",
5828
6074
  keymap: { [SHORTCUT]: open, "Mod-Shift-?": open },
5829
6075
  ui: { overlays: [ShortcutsOverlay] }
@@ -5831,9 +6077,11 @@ function shortcutsExtension(store) {
5831
6077
  }
5832
6078
  function useRootDirection(controller) {
5833
6079
  const [direction, setDirection] = React7.useState("ltr");
5834
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
6080
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
5835
6081
  const root = controller.root;
5836
- if (root !== null) setDirection(chunk2QKFRKOP_cjs.inlineDirection(root));
6082
+ if (root === null) return;
6083
+ setDirection(chunkN25EH2GZ_cjs.inlineDirection(root));
6084
+ return watchDirection(root, setDirection);
5837
6085
  }, [controller]);
5838
6086
  return direction;
5839
6087
  }
@@ -5865,16 +6113,16 @@ function writeBlockData(controller, blockId, data) {
5865
6113
  const index = doc.index.get(blockId);
5866
6114
  if (index === void 0) return;
5867
6115
  const block = doc.blocks[index];
5868
- const { attrs, regions } = chunkI7X7B3WD_cjs.specFor(schema, block.type).fromData(data, editor.codec.context);
5869
- if (chunkI7X7B3WD_cjs.sameValue(attrs, block.attrs) && chunkI7X7B3WD_cjs.sameValue(regions, block.regions)) return;
5870
- const tr = editor.createTransaction().step(chunkFK7EBZPX_cjs.replaceBlockStep({ ...block, attrs, regions }));
5871
- editor.dispatch(tr.setMeta(chunkFK7EBZPX_cjs.HISTORY_GROUP, `block-data:${blockId}`));
6116
+ const { attrs, regions } = chunk4JDRDPLC_cjs.specFor(schema, block.type).fromData(data, editor.codec.context);
6117
+ if (chunk4JDRDPLC_cjs.sameValue(attrs, block.attrs) && chunk4JDRDPLC_cjs.sameValue(regions, block.regions)) return;
6118
+ const tr = editor.createTransaction().step(chunkZ5EEDCRI_cjs.replaceBlockStep({ ...block, attrs, regions }));
6119
+ editor.dispatch(tr.setMeta(chunkZ5EEDCRI_cjs.HISTORY_GROUP, `block-data:${blockId}`));
5872
6120
  }
5873
6121
  function insertAfterBlock(controller, blockId, type, definitions, onError) {
5874
6122
  const editor = internal(controller);
5875
6123
  const data = chunkA6PGQVTR_cjs.seedData(chunkA6PGQVTR_cjs.blockByType(definitions, type), onError);
5876
6124
  if (data === null) return false;
5877
- const content = data && chunkI7X7B3WD_cjs.specFor(editor.state.schema, type).fromData(data, editor.codec.context);
6125
+ const content = data && chunk4JDRDPLC_cjs.specFor(editor.state.schema, type).fromData(data, editor.codec.context);
5878
6126
  controller.run(insertBlockToWrite({ type, ...content }, opensEditor(definitions, type), blockId));
5879
6127
  return true;
5880
6128
  }
@@ -5898,7 +6146,7 @@ function runHistoryKey(controller, event) {
5898
6146
  }
5899
6147
  function useBlockIndex(controller, blockId) {
5900
6148
  const select = React7.useCallback((state) => state.doc.index.get(blockId) ?? -1, [blockId]);
5901
- return chunk2QKFRKOP_cjs.useEditorState(controller, select);
6149
+ return chunkN25EH2GZ_cjs.useEditorState(controller, select);
5902
6150
  }
5903
6151
  var FOCUSABLE = [
5904
6152
  "input:not([disabled])",
@@ -5918,16 +6166,20 @@ function useEditingFocus(root, editing, stop) {
5918
6166
  (element.querySelector(FOCUSABLE) ?? element).focus();
5919
6167
  const doc = element.ownerDocument;
5920
6168
  const canvas = element.closest(CANVAS);
5921
- const leaveForCanvas = (event) => {
5922
- const target = event.target;
5923
- if (!element.contains(target) && canvas?.contains(target)) stop();
5924
- };
5925
6169
  const leaveUnlessInside = (event) => {
5926
6170
  if (!inside.current.has(event)) stop();
5927
6171
  };
5928
- const unlistenCapture = chunk2QKFRKOP_cjs.listen(doc, { pointerdown: leaveForCanvas }, true);
5929
- const unlisten = chunk2QKFRKOP_cjs.listen(doc, { pointerdown: leaveUnlessInside, focusin: leaveUnlessInside });
6172
+ let afterDispatch;
6173
+ const onPress = (event) => {
6174
+ const target = event.target;
6175
+ if (!element.contains(target) && canvas?.contains(target)) return stop();
6176
+ clearTimeout(afterDispatch);
6177
+ afterDispatch = setTimeout(() => leaveUnlessInside(event));
6178
+ };
6179
+ const unlistenCapture = chunkN25EH2GZ_cjs.listen(doc, { pointerdown: onPress }, true);
6180
+ const unlisten = chunkN25EH2GZ_cjs.listen(doc, { pointerdown: leaveUnlessInside, focusin: leaveUnlessInside });
5930
6181
  return () => {
6182
+ clearTimeout(afterDispatch);
5931
6183
  unlistenCapture();
5932
6184
  unlisten();
5933
6185
  };
@@ -5937,6 +6189,18 @@ function useEditingFocus(root, editing, stop) {
5937
6189
  return { onPointerDownCapture: mark, onFocusCapture: mark };
5938
6190
  }, []);
5939
6191
  }
6192
+ function useWhileMounted(callback) {
6193
+ const mounted = React7.useRef(false);
6194
+ React7.useEffect(() => {
6195
+ mounted.current = true;
6196
+ return () => {
6197
+ mounted.current = false;
6198
+ };
6199
+ }, []);
6200
+ return React7.useCallback(() => {
6201
+ if (mounted.current) callback();
6202
+ }, [callback]);
6203
+ }
5940
6204
  function useMissingPreviewWarning(readOnly, { type, preview }) {
5941
6205
  React7.useEffect(() => {
5942
6206
  if (!readOnly || preview !== void 0) return;
@@ -5949,14 +6213,15 @@ function VoidPreview({ definition: { preview: Preview }, name, ...props }) {
5949
6213
  if (Preview === void 0) return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-1 text-sm text-muted-foreground", children: name });
5950
6214
  return /* @__PURE__ */ jsxRuntime.jsx(Preview, { ...props });
5951
6215
  }
5952
- function VoidEditor({ Editor, controller, ...props }) {
6216
+ function VoidEditor({ Editor, controller, onDone, ...props }) {
5953
6217
  const index = useBlockIndex(controller, props.blockId);
5954
- return /* @__PURE__ */ jsxRuntime.jsx(Editor, { ...props, index });
6218
+ const done = useWhileMounted(onDone);
6219
+ return /* @__PURE__ */ jsxRuntime.jsx(Editor, { ...props, index, onDone: done });
5955
6220
  }
5956
6221
  function VoidBlock({ block, controller, definition }) {
5957
6222
  const { blockTypes, labels } = useQuoin();
5958
- const { report } = React7.useContext(chunk2QKFRKOP_cjs.BlockErrorContext);
5959
- const readOnly = chunk2QKFRKOP_cjs.useReadOnly(controller);
6223
+ const { report } = React7.useContext(chunkN25EH2GZ_cjs.BlockErrorContext);
6224
+ const readOnly = chunkN25EH2GZ_cjs.useReadOnly(controller);
5960
6225
  const { editor: Editor, alwaysEditing = false } = definition;
5961
6226
  const name = blockTypeLabel(labels, block, blockTypes);
5962
6227
  const blockId = block.id;
@@ -6020,7 +6285,7 @@ function VoidBlock({ block, controller, definition }) {
6020
6285
  role: "group",
6021
6286
  "aria-label": name,
6022
6287
  tabIndex: editorShown ? -1 : void 0,
6023
- ...{ [chunk2QKFRKOP_cjs.NATIVE_INTERACTION]: editorShown ? "" : void 0 },
6288
+ ...{ [chunkN25EH2GZ_cjs.NATIVE_INTERACTION]: editorShown ? "" : void 0 },
6024
6289
  onKeyDown,
6025
6290
  ...focusHandlers,
6026
6291
  onClick,
@@ -6091,6 +6356,11 @@ function extensionUi(extension) {
6091
6356
  }
6092
6357
  var list = (value) => Array.isArray(value) ? value : [];
6093
6358
  var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
6359
+ function doesSomething(item) {
6360
+ if (item.command !== void 0 || item.run !== void 0) return true;
6361
+ chunkA6PGQVTR_cjs.devWarn(`the slash command "${item.id}" has neither a command nor a run, so the slash menu leaves it out.`);
6362
+ return false;
6363
+ }
6094
6364
  function mergeRender(uis) {
6095
6365
  const given = uis.flatMap(({ render }) => render === void 0 ? [] : [render]);
6096
6366
  if (given.length === 0) return void 0;
@@ -6103,7 +6373,10 @@ function mergeRender(uis) {
6103
6373
  function collectUi(extensions, definitions, readOnly = false, onError) {
6104
6374
  const uis = extensions.map(extensionUi);
6105
6375
  return {
6106
- slashItems: [...uis.flatMap((ui) => list(ui.slashItems)), ...blockTypeSlashItems(definitions, onError)],
6376
+ slashItems: [
6377
+ ...uis.flatMap((ui) => list(ui.slashItems)).filter(doesSomething),
6378
+ ...blockTypeSlashItems(definitions, onError)
6379
+ ],
6107
6380
  bubbleItems: uis.flatMap((ui) => list(ui.bubbleItems)),
6108
6381
  blockMenuItems: uis.flatMap((ui) => list(ui.blockMenuItems)),
6109
6382
  renderers: Object.assign({}, canvasRenderers(definitions, readOnly), ...uis.map((ui) => record(ui.renderers))),
@@ -6118,12 +6391,12 @@ function collectUi(extensions, definitions, readOnly = false, onError) {
6118
6391
 
6119
6392
  // src/canvas/insert-uploads.ts
6120
6393
  function fileBlock(file, stored) {
6121
- if (isImageFile(file)) return { id: chunkI7X7B3WD_cjs.newId(), type: "IMAGE", attrs: { storageKey: stored.storageKey, alt: "" } };
6394
+ if (isImageFile(file)) return { id: chunk4JDRDPLC_cjs.newId(), type: "IMAGE", attrs: { storageKey: stored.storageKey, alt: "" } };
6122
6395
  return { type: "FILE", attrs: fileAttrs(file, stored) };
6123
6396
  }
6124
6397
  function anchorBlock(controller, at) {
6125
6398
  const { doc, selection } = controller.editor.state;
6126
- const candidates = [at?.block, chunkFK7EBZPX_cjs.headBlockId(selection), doc.blocks[doc.blocks.length - 1]?.id];
6399
+ const candidates = [at?.block, chunkZ5EEDCRI_cjs.headBlockId(selection), doc.blocks[doc.blocks.length - 1]?.id];
6127
6400
  return candidates.find((id) => typeof id === "string" && doc.index.has(id)) ?? null;
6128
6401
  }
6129
6402
  var writerOf = (controller) => ({
@@ -6132,16 +6405,19 @@ var writerOf = (controller) => ({
6132
6405
  });
6133
6406
  function movedOn(controller, before) {
6134
6407
  const now = writerOf(controller);
6135
- return now.focus !== before.focus || !chunkFK7EBZPX_cjs.sameSelection(now.selection, before.selection);
6408
+ return now.focus !== before.focus || !chunkZ5EEDCRI_cjs.sameSelection(now.selection, before.selection);
6136
6409
  }
6137
6410
  async function insertUploads(controller, files, at, { upload, resolveAssetUrl, onError, unavailable, failed }) {
6138
6411
  if (!upload) return onError(new Error(unavailable));
6412
+ controller.readSelectionFromDom();
6413
+ const rest = at === null ? fillSelectedBlock(controller, files) : files;
6414
+ if (rest.length === 0) return;
6139
6415
  const writer = writerOf(controller);
6140
- const outcomes = await Promise.allSettled(files.map(async (file) => upload(file, isImageFile(file) ? "image" : "file")));
6416
+ const outcomes = await Promise.allSettled(rest.map(async (file) => upload(file, isImageFile(file) ? "image" : "file")));
6141
6417
  const blocks = [];
6142
6418
  outcomes.forEach((outcome, index) => {
6143
- if (outcome.status === "rejected") onError(toError(outcome.reason));
6144
- else if (typeof outcome.value?.storageKey === "string") blocks.push(fileBlock(files[index], outcome.value));
6419
+ if (outcome.status === "rejected") onError(uploadError(outcome.reason, failed));
6420
+ else if (isStored(outcome.value)) blocks.push(fileBlock(rest[index], outcome.value));
6145
6421
  else onError(new Error(failed));
6146
6422
  });
6147
6423
  if (blocks.length === 0 || controller.root === null || controller.readOnly) return;
@@ -6160,15 +6436,15 @@ function holdsHead({ selection }, block) {
6160
6436
  }
6161
6437
  function markdownPlaceholder(state, block, labels) {
6162
6438
  const { kind, level } = block.attrs;
6163
- if (kind === "heading") return labels.placeholderHeading(chunkI7X7B3WD_cjs.headingLevel(level));
6439
+ if (kind === "heading") return labels.placeholderHeading(chunk4JDRDPLC_cjs.headingLevel(level));
6164
6440
  if (LIST_KINDS.has(kind)) return labels.placeholderList;
6165
6441
  if (kind === "quote") return labels.placeholderQuote;
6166
6442
  if (kind !== "paragraph") return void 0;
6167
6443
  return holdsHead(state, block) || state.doc.blocks.length === 1 ? labels.placeholderEmpty : void 0;
6168
6444
  }
6169
6445
  function placeholderFor(state, block, labels) {
6170
- const region = chunkFK7EBZPX_cjs.primaryRegion(state.schema, block);
6171
- if (region === null || chunkFK7EBZPX_cjs.regionLength(block, region) > 0) return void 0;
6446
+ const region = chunkZ5EEDCRI_cjs.primaryRegion(state.schema, block);
6447
+ if (region === null || chunkZ5EEDCRI_cjs.regionLength(block, region) > 0) return void 0;
6172
6448
  switch (block.type) {
6173
6449
  case "MARKDOWN":
6174
6450
  return markdownPlaceholder(state, block, labels);
@@ -6184,18 +6460,18 @@ function placeholderFor(state, block, labels) {
6184
6460
  // src/canvas/selected-block-label.ts
6185
6461
  var LONGEST = 80;
6186
6462
  function firstLine2(text2) {
6187
- const characters = [...text2.split("\n", 1)[0].trim()];
6188
- return characters.length > LONGEST ? `${characters.slice(0, LONGEST - 1).join("")}\u2026` : characters.join("");
6463
+ const line = text2.split("\n", 1)[0].trim();
6464
+ return chunkZ5EEDCRI_cjs.graphemePrefix(line, LONGEST) === line ? line : `${chunkZ5EEDCRI_cjs.graphemePrefix(line, LONGEST - 1)}\u2026`;
6189
6465
  }
6190
6466
  var selectedBlockLabel = (labels, definitions, extensionNames) => (block, schema) => {
6191
6467
  const { attrs } = block;
6192
- if (block.type === "IMAGE") return hasPicture(attrs) ? chunkI7X7B3WD_cjs.str(attrs.alt) || labels.imageNoAlt : labels.emptyImage;
6468
+ if (block.type === "IMAGE") return hasPicture(attrs) ? chunk4JDRDPLC_cjs.str(attrs.alt) || labels.imageNoAlt : labels.emptyImage;
6193
6469
  if (block.type === "CODE") {
6194
- const language2 = chunkI7X7B3WD_cjs.str(attrs.language);
6470
+ const language2 = chunk4JDRDPLC_cjs.str(attrs.language);
6195
6471
  return labels.codeBlockName(languageName(language2) ?? (language2 || labels.plainText));
6196
6472
  }
6197
6473
  if (block.type === "FILE" && hasFile(attrs)) return labels.fileBlockName(firstLine2(fileName(attrs)));
6198
- return firstLine2(chunkI7X7B3WD_cjs.blockPlainText(block, schema)) || blockTypeLabel(labels, block, definitions, extensionNames);
6474
+ return firstLine2(chunk4JDRDPLC_cjs.blockPlainText(block, schema)) || blockTypeLabel(labels, block, definitions, extensionNames);
6199
6475
  };
6200
6476
  function revealLinkedBlock(controller, hash) {
6201
6477
  const id = linkedBlockId(hash);
@@ -6203,7 +6479,7 @@ function revealLinkedBlock(controller, hash) {
6203
6479
  if (id === null || shell === void 0) return false;
6204
6480
  const { editor } = controller;
6205
6481
  const { doc, schema } = editor.state;
6206
- editor.dispatch(editor.createTransaction().setSelection(chunkFK7EBZPX_cjs.startOf(doc, schema, id)));
6482
+ editor.dispatch(editor.createTransaction().setSelection(chunkZ5EEDCRI_cjs.startOf(doc, schema, id)));
6207
6483
  shell.scrollIntoView?.({ block: "center" });
6208
6484
  return true;
6209
6485
  }
@@ -6220,8 +6496,8 @@ function useBlockLinkTarget(controller, focusOnMount) {
6220
6496
  }, [controller, focusOnMount]);
6221
6497
  }
6222
6498
  var WRITER_ORIGINS = /* @__PURE__ */ new Set(["command", "input", "paste"]);
6223
- var isBlockDeletion = (tr) => WRITER_ORIGINS.has(tr.origin) && tr.getMeta(chunkFK7EBZPX_cjs.LINE_JOIN) !== true;
6224
- var isEmptyText = (schema, block) => chunkFK7EBZPX_cjs.isTextFlow(schema, block) && chunkFK7EBZPX_cjs.regionLength(block, chunkFK7EBZPX_cjs.primaryRegion(schema, block) ?? "") === 0;
6499
+ var isBlockDeletion = (tr) => WRITER_ORIGINS.has(tr.origin) && tr.getMeta(chunkZ5EEDCRI_cjs.LINE_JOIN) !== true;
6500
+ var isEmptyText = (schema, block) => chunkZ5EEDCRI_cjs.isTextFlow(schema, block) && chunkZ5EEDCRI_cjs.regionLength(block, chunkZ5EEDCRI_cjs.primaryRegion(schema, block) ?? "") === 0;
6225
6501
  function filledLine({ origin, before }, schema) {
6226
6502
  const { selection } = before;
6227
6503
  if (origin !== "paste" || selection?.kind !== "text") return () => false;
@@ -6230,18 +6506,15 @@ function filledLine({ origin, before }, schema) {
6230
6506
  }
6231
6507
  function deletions(codec, tr) {
6232
6508
  const filled = filledLine(tr, tr.before.schema);
6233
- return tr.steps.flatMap((step, i) => {
6234
- if (step.stepType !== "removeBlocks") return [];
6235
- const { index, count } = step.toJSON();
6236
- if (typeof index !== "number" || typeof count !== "number") return [];
6237
- const { blocks } = tr.docs[i];
6238
- const first = codec.blocksFromDoc(chunkI7X7B3WD_cjs.createDoc(blocks.slice(0, index))).length;
6509
+ return blockRemovals(tr).flatMap(({ step, index, count }) => {
6510
+ const { blocks } = tr.docs[step];
6511
+ const first = codec.blocksFromDoc(chunk4JDRDPLC_cjs.createDoc(blocks.slice(0, index))).length;
6239
6512
  return blocks.slice(index, index + count).flatMap((block, offset) => filled(block) ? [] : [{ block: codec.toQuoin(block), index: first + offset }]);
6240
6513
  });
6241
6514
  }
6242
6515
  function useDeleteReporter(controller, onDelete) {
6243
6516
  const latest = React7.useRef(onDelete);
6244
- chunk2QKFRKOP_cjs.useIsomorphicLayoutEffect(() => {
6517
+ chunkN25EH2GZ_cjs.useIsomorphicLayoutEffect(() => {
6245
6518
  latest.current = onDelete;
6246
6519
  });
6247
6520
  React7.useEffect(() => {
@@ -6253,6 +6526,61 @@ function useDeleteReporter(controller, onDelete) {
6253
6526
  });
6254
6527
  }, [controller]);
6255
6528
  }
6529
+
6530
+ // src/canvas/upload-pasted-images.ts
6531
+ var FETCHABLE = /^https?:\/\//i;
6532
+ function pastedPictures(tr, state) {
6533
+ if (tr.getMeta(chunkZ5EEDCRI_cjs.PASTED_HTML) !== true) return [];
6534
+ return state.doc.blocks.flatMap(({ id, type, attrs }) => {
6535
+ const url = chunk4JDRDPLC_cjs.str(attrs.url);
6536
+ const pasted = type === "IMAGE" && FETCHABLE.test(url) && !chunk4JDRDPLC_cjs.str(attrs.storageKey) && !tr.before.doc.index.has(id);
6537
+ return pasted ? [{ id, url }] : [];
6538
+ });
6539
+ }
6540
+ async function pictureAt(url) {
6541
+ const response = await fetch(url).catch(() => null);
6542
+ const picture = response?.ok ? await response.blob().catch(() => null) : null;
6543
+ if (!picture?.type.startsWith("image/")) return null;
6544
+ return new File([picture], url.split(/[?#]/)[0].split("/").pop() || "image", { type: picture.type });
6545
+ }
6546
+ function uploadPastedImages(controller, tr, state, host) {
6547
+ for (const { id, url } of pastedPictures(tr, state)) {
6548
+ void pictureAt(url).then(async (file) => {
6549
+ if (file === null) return;
6550
+ await uploadImage(controller, id, file, host).catch(
6551
+ (cause) => host.onError(new Error(cause instanceof Error && cause.message || host.failed))
6552
+ );
6553
+ });
6554
+ }
6555
+ }
6556
+
6557
+ // src/i18n/resolve-labels.ts
6558
+ var isRecord = (value) => typeof value === "object" && value !== null;
6559
+ function ontoRecords(overrides) {
6560
+ const taken = Object.entries(overrides).map(([key, value]) => {
6561
+ const fallback = englishLabels[key];
6562
+ return [key, isRecord(value) && isRecord(fallback) ? { ...fallback, ...value } : value];
6563
+ });
6564
+ return Object.fromEntries(taken);
6565
+ }
6566
+ var resolveLabels = (overrides) => chunkZWTIRGY5_cjs.mergeLabels(englishLabels, overrides && ontoRecords(overrides));
6567
+
6568
+ // src/canvas/use-stable-labels.ts
6569
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6570
+ function sameEntries(a, b, deeper) {
6571
+ const keys = Object.keys(a);
6572
+ if (keys.length !== Object.keys(b).length) return false;
6573
+ return keys.every((key) => {
6574
+ const [x, y] = [a[key], b[key]];
6575
+ return Object.is(x, y) || deeper && isRecord2(x) && isRecord2(y) && sameEntries(x, y, false);
6576
+ });
6577
+ }
6578
+ var sameOverrides = (a, b) => a === b || a !== void 0 && b !== void 0 && sameEntries(a, b, true);
6579
+ function useStableLabels(overrides) {
6580
+ const [kept, setKept] = React7.useState(overrides);
6581
+ if (!sameOverrides(kept, overrides)) setKept(overrides);
6582
+ return React7.useMemo(() => resolveLabels(kept), [kept]);
6583
+ }
6256
6584
  function useStructureAnnouncements(controller, labels) {
6257
6585
  React7.useEffect(() => {
6258
6586
  const undoKeys = formatShortcut("Mod-z", { isMac: controller.isMac, labels });
@@ -6300,7 +6628,7 @@ var BlockCanvas = React7.forwardRef(function BlockCanvas2({
6300
6628
  const [voidEditing] = React7.useState(createVoidEditing);
6301
6629
  const [uiStore] = React7.useState(createCanvasUiStore);
6302
6630
  const [allExtensions] = React7.useState(() => [
6303
- ...chunkFK7EBZPX_cjs.coreExtensions(),
6631
+ ...chunkZ5EEDCRI_cjs.coreExtensions(),
6304
6632
  voidEditing.extension,
6305
6633
  blockMenuKeymap(uiStore),
6306
6634
  slashExtension(),
@@ -6309,7 +6637,7 @@ var BlockCanvas = React7.forwardRef(function BlockCanvas2({
6309
6637
  shortcutsExtension(uiStore),
6310
6638
  ...extensions ?? []
6311
6639
  ]);
6312
- const labels = React7.useMemo(() => resolveLabels(labelOverrides), [labelOverrides]);
6640
+ const labels = useStableLabels(labelOverrides);
6313
6641
  const latestOnError = React7.useRef(onError);
6314
6642
  latestOnError.current = onError;
6315
6643
  const ui = React7.useMemo(
@@ -6318,12 +6646,17 @@ var BlockCanvas = React7.forwardRef(function BlockCanvas2({
6318
6646
  );
6319
6647
  useDefinitionWarnings(blockTypes, readOnly);
6320
6648
  const [focusOnMount] = React7.useState(autoFocus);
6321
- const controller = chunk2QKFRKOP_cjs.useQuoinEditor({
6649
+ const controller = chunkN25EH2GZ_cjs.useQuoinEditor({
6322
6650
  blocks,
6323
6651
  onChange,
6324
6652
  readOnly,
6325
6653
  extensions: allExtensions,
6326
6654
  render: ui.render,
6655
+ onTransaction(tr, state) {
6656
+ if (!upload) return;
6657
+ const host = { upload, resolveAssetUrl: resolveAssetUrl ?? String, onError: onError ?? console.error };
6658
+ uploadPastedImages(controller, tr, state, { ...host, failed: labels.uploadFailed });
6659
+ },
6327
6660
  onFiles(files, at) {
6328
6661
  const report = onError ?? console.error;
6329
6662
  void insertUploads(controller, files, at, {
@@ -6340,7 +6673,9 @@ var BlockCanvas = React7.forwardRef(function BlockCanvas2({
6340
6673
  useStructureAnnouncements(controller, labels);
6341
6674
  const actions = React7.useMemo(() => {
6342
6675
  const onCopied = () => controller.announce(labels.copied);
6343
- return menuActions(coreBlockMenuActions(labels, { blockLink, onCopied, resolveAssetUrl }), ui.blockMenuItems);
6676
+ const onCopyFailed = () => controller.announce(labels.copyFailed ?? englishLabels.copyFailed);
6677
+ const core = coreBlockMenuActions(labels, { blockLink, onCopied, onCopyFailed, resolveAssetUrl });
6678
+ return menuActions(core, ui.blockMenuItems);
6344
6679
  }, [controller, labels, blockLink, resolveAssetUrl, ui.blockMenuItems]);
6345
6680
  const slashItems = React7.useMemo(
6346
6681
  () => [shortcutsSlashItem(labels, uiStore), ...ui.slashItems],
@@ -6366,7 +6701,7 @@ var BlockCanvas = React7.forwardRef(function BlockCanvas2({
6366
6701
  [controller, resolveAssetUrl]
6367
6702
  );
6368
6703
  return /* @__PURE__ */ jsxRuntime.jsx(reactDirection.DirectionProvider, { dir: direction, children: /* @__PURE__ */ jsxRuntime.jsx(QuoinProvider, { value: { blockTypes, upload, resolveAssetUrl, onError, highlight, controller, labels }, children: /* @__PURE__ */ jsxRuntime.jsx(VoidEditingContext.Provider, { value: voidEditing, children: /* @__PURE__ */ jsxRuntime.jsx(SlashItemsProvider, { value: slashItems, children: /* @__PURE__ */ jsxRuntime.jsx(BubbleItemsProvider, { value: ui.bubbleItems, children: /* @__PURE__ */ jsxRuntime.jsx(CanvasUiProvider, { value: uiStore, children: /* @__PURE__ */ jsxRuntime.jsxs(
6369
- chunk2QKFRKOP_cjs.EditorView,
6704
+ chunkN25EH2GZ_cjs.EditorView,
6370
6705
  {
6371
6706
  controller,
6372
6707
  renderers: ui.renderers,
@@ -6374,6 +6709,7 @@ var BlockCanvas = React7.forwardRef(function BlockCanvas2({
6374
6709
  id,
6375
6710
  "aria-label": ariaLabel ?? labels.document,
6376
6711
  hint: labels.editorHint,
6712
+ rendererLabels: labels,
6377
6713
  paragraphLabel: labels.paragraph,
6378
6714
  selectedContentLabel: labels.selectedContent,
6379
6715
  blockLabel,
@@ -6400,22 +6736,25 @@ function progressText(state, labels) {
6400
6736
  return "";
6401
6737
  }
6402
6738
  function UploadField({ category, label, accept, onUploaded }) {
6403
- const { upload, onError } = useQuoin();
6739
+ const { upload, onError, controller } = useQuoin();
6404
6740
  const labels = useLabels();
6405
6741
  const [state, setState] = React7__namespace.useState({ phase: "idle" });
6406
6742
  async function run(file) {
6407
6743
  if (!upload) return;
6408
6744
  setState({ phase: "busy" });
6745
+ controller?.announce(labels.uploading);
6409
6746
  let result;
6410
6747
  try {
6411
6748
  result = await upload(file, category);
6412
6749
  } catch (cause) {
6413
- setState({ phase: "failed", message: cause instanceof Error && cause.message || labels.uploadFailed });
6750
+ setState({ phase: "failed", message: uploadError(cause, labels.uploadFailed).message });
6414
6751
  return;
6415
6752
  }
6416
6753
  try {
6417
6754
  await onUploaded(result);
6418
- setState({ phase: "uploaded", name: result.filename || file.name });
6755
+ const name = result.filename || file.name;
6756
+ setState({ phase: "uploaded", name });
6757
+ controller?.announce(labels.uploaded(name));
6419
6758
  } catch (cause) {
6420
6759
  onError(toError(cause));
6421
6760
  setState({ phase: "idle" });
@@ -6434,7 +6773,7 @@ function UploadField({ category, label, accept, onUploaded }) {
6434
6773
  }
6435
6774
  ),
6436
6775
  !upload && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "note", className: "text-xs text-muted-foreground", children: labels.uploadsUnavailable }),
6437
- /* @__PURE__ */ jsxRuntime.jsx("span", { role: "status", className: progress === "" ? "sr-only" : "text-xs text-muted-foreground", children: progress }),
6776
+ /* @__PURE__ */ jsxRuntime.jsx("span", { role: controller ? void 0 : "status", className: progress === "" ? "sr-only" : "text-xs text-muted-foreground", children: progress }),
6438
6777
  state.phase === "failed" && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "text-xs text-destructive", children: state.message })
6439
6778
  ] });
6440
6779
  }
@@ -6567,7 +6906,7 @@ function useAutosave({
6567
6906
  const seen = seenRef.current;
6568
6907
  seenRef.current = deps;
6569
6908
  if (seen === null) return;
6570
- const unchanged = chunkI7X7B3WD_cjs.sameItems(seen, deps);
6909
+ const unchanged = chunk4JDRDPLC_cjs.sameItems(seen, deps);
6571
6910
  if (!unchanged) runner.markDirty();
6572
6911
  }, deps);
6573
6912
  React7__namespace.useEffect(() => {
@@ -6676,6 +7015,7 @@ exports.styleTargets = styleTargets;
6676
7015
  exports.suggestionPlugin = suggestionPlugin;
6677
7016
  exports.toError = toError;
6678
7017
  exports.useAutosave = useAutosave;
7018
+ exports.useClosing = useClosing;
6679
7019
  exports.useFocusKey = useFocusKey;
6680
7020
  exports.useLabels = useLabels;
6681
7021
  exports.useNodeKeys = useNodeKeys;
@@ -6685,3 +7025,4 @@ exports.useQuoin = useQuoin;
6685
7025
  exports.useRangeAnchor = useRangeAnchor;
6686
7026
  exports.useRovingFocus = useRovingFocus;
6687
7027
  exports.virtualAnchor = virtualAnchor;
7028
+ exports.watchDirection = watchDirection;