quoin-editor 0.4.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.md +251 -0
  2. package/MIGRATION.md +233 -0
  3. package/README.md +78 -716
  4. package/dist/ai.cjs +1148 -0
  5. package/dist/ai.cjs.map +1 -0
  6. package/dist/ai.d.cts +68 -0
  7. package/dist/ai.d.ts +68 -0
  8. package/dist/ai.js +1148 -0
  9. package/dist/ai.js.map +1 -0
  10. package/dist/catalogue.cjs +59 -0
  11. package/dist/catalogue.cjs.map +1 -0
  12. package/dist/catalogue.d.cts +39 -0
  13. package/dist/catalogue.d.ts +39 -0
  14. package/dist/catalogue.js +59 -0
  15. package/dist/catalogue.js.map +1 -0
  16. package/dist/chunk-26Q4LSIU.js +100 -0
  17. package/dist/chunk-26Q4LSIU.js.map +1 -0
  18. package/dist/chunk-56C7GCKO.cjs +1385 -0
  19. package/dist/chunk-56C7GCKO.cjs.map +1 -0
  20. package/dist/chunk-5M4QG3ZC.cjs +3303 -0
  21. package/dist/chunk-5M4QG3ZC.cjs.map +1 -0
  22. package/dist/chunk-5R3ISX4A.cjs +6643 -0
  23. package/dist/chunk-5R3ISX4A.cjs.map +1 -0
  24. package/dist/chunk-62WKUAQY.js +3335 -0
  25. package/dist/chunk-62WKUAQY.js.map +1 -0
  26. package/dist/chunk-6LMS5J3Y.js +320 -0
  27. package/dist/chunk-6LMS5J3Y.js.map +1 -0
  28. package/dist/chunk-6PKVJZQV.cjs +100 -0
  29. package/dist/chunk-6PKVJZQV.cjs.map +1 -0
  30. package/dist/chunk-6YSGUAVW.js +376 -0
  31. package/dist/chunk-6YSGUAVW.js.map +1 -0
  32. package/dist/chunk-75C6PX2L.cjs +239 -0
  33. package/dist/chunk-75C6PX2L.cjs.map +1 -0
  34. package/dist/chunk-A4IJSJKL.js +598 -0
  35. package/dist/chunk-A4IJSJKL.js.map +1 -0
  36. package/dist/chunk-BTBHHBVR.cjs +3335 -0
  37. package/dist/chunk-BTBHHBVR.cjs.map +1 -0
  38. package/dist/chunk-CLTHD5GO.cjs +34 -0
  39. package/dist/chunk-CLTHD5GO.cjs.map +1 -0
  40. package/dist/chunk-EH5KXQXY.cjs +62 -0
  41. package/dist/chunk-EH5KXQXY.cjs.map +1 -0
  42. package/dist/chunk-ESMAW4NK.cjs +184 -0
  43. package/dist/chunk-ESMAW4NK.cjs.map +1 -0
  44. package/dist/chunk-EYFCOV3T.cjs +30 -0
  45. package/dist/chunk-EYFCOV3T.cjs.map +1 -0
  46. package/dist/chunk-F5V3IEEE.cjs +326 -0
  47. package/dist/chunk-F5V3IEEE.cjs.map +1 -0
  48. package/dist/chunk-GPIDU3GN.js +239 -0
  49. package/dist/chunk-GPIDU3GN.js.map +1 -0
  50. package/dist/chunk-HOIDJEVI.cjs +4063 -0
  51. package/dist/chunk-HOIDJEVI.cjs.map +1 -0
  52. package/dist/chunk-JJ3RASQS.cjs +706 -0
  53. package/dist/chunk-JJ3RASQS.cjs.map +1 -0
  54. package/dist/chunk-LE3F7WIP.js +6643 -0
  55. package/dist/chunk-LE3F7WIP.js.map +1 -0
  56. package/dist/chunk-LE5XMIFJ.cjs +14 -0
  57. package/dist/chunk-LE5XMIFJ.cjs.map +1 -0
  58. package/dist/chunk-LMAEPFTX.js +30 -0
  59. package/dist/chunk-LMAEPFTX.js.map +1 -0
  60. package/dist/chunk-LV6EYPJO.js +34 -0
  61. package/dist/chunk-LV6EYPJO.js.map +1 -0
  62. package/dist/chunk-MLRAVUFG.cjs +376 -0
  63. package/dist/chunk-MLRAVUFG.cjs.map +1 -0
  64. package/dist/chunk-MQUCCSWG.js +14 -0
  65. package/dist/chunk-MQUCCSWG.js.map +1 -0
  66. package/dist/chunk-O4Z2WMFF.js +62 -0
  67. package/dist/chunk-O4Z2WMFF.js.map +1 -0
  68. package/dist/chunk-OCCKQHCY.js +25 -0
  69. package/dist/chunk-OCCKQHCY.js.map +1 -0
  70. package/dist/chunk-PGSS4IOS.js +706 -0
  71. package/dist/chunk-PGSS4IOS.js.map +1 -0
  72. package/dist/chunk-PIWRSX7U.js +562 -0
  73. package/dist/chunk-PIWRSX7U.js.map +1 -0
  74. package/dist/chunk-PX6LZMH4.cjs +598 -0
  75. package/dist/chunk-PX6LZMH4.cjs.map +1 -0
  76. package/dist/chunk-QXG3HWDS.js +184 -0
  77. package/dist/chunk-QXG3HWDS.js.map +1 -0
  78. package/dist/chunk-RQW3P3GH.js +504 -0
  79. package/dist/chunk-RQW3P3GH.js.map +1 -0
  80. package/dist/chunk-SSIRYUCK.js +3303 -0
  81. package/dist/chunk-SSIRYUCK.js.map +1 -0
  82. package/dist/chunk-UBNG5YCG.js +326 -0
  83. package/dist/chunk-UBNG5YCG.js.map +1 -0
  84. package/dist/chunk-UCXLRRWY.js +1385 -0
  85. package/dist/chunk-UCXLRRWY.js.map +1 -0
  86. package/dist/chunk-UDAVVEFC.cjs +562 -0
  87. package/dist/chunk-UDAVVEFC.cjs.map +1 -0
  88. package/dist/chunk-UPSLDTLT.js +4063 -0
  89. package/dist/chunk-UPSLDTLT.js.map +1 -0
  90. package/dist/chunk-UWNZG2SD.cjs +25 -0
  91. package/dist/chunk-UWNZG2SD.cjs.map +1 -0
  92. package/dist/chunk-V3KLMSRO.cjs +504 -0
  93. package/dist/chunk-V3KLMSRO.cjs.map +1 -0
  94. package/dist/chunk-VACTAZWS.cjs +249 -0
  95. package/dist/chunk-VACTAZWS.cjs.map +1 -0
  96. package/dist/chunk-WMN6566P.js +249 -0
  97. package/dist/chunk-WMN6566P.js.map +1 -0
  98. package/dist/chunk-XX5FTPJR.cjs +320 -0
  99. package/dist/chunk-XX5FTPJR.cjs.map +1 -0
  100. package/dist/comments.cjs +1008 -0
  101. package/dist/comments.cjs.map +1 -0
  102. package/dist/comments.d.cts +93 -0
  103. package/dist/comments.d.ts +93 -0
  104. package/dist/comments.js +1008 -0
  105. package/dist/comments.js.map +1 -0
  106. package/dist/diff.cjs +346 -0
  107. package/dist/diff.cjs.map +1 -0
  108. package/dist/diff.d.cts +67 -0
  109. package/dist/diff.d.ts +67 -0
  110. package/dist/diff.js +346 -0
  111. package/dist/diff.js.map +1 -0
  112. package/dist/docx.cjs +874 -0
  113. package/dist/docx.cjs.map +1 -0
  114. package/dist/docx.d.cts +33 -0
  115. package/dist/docx.d.ts +33 -0
  116. package/dist/docx.js +874 -0
  117. package/dist/docx.js.map +1 -0
  118. package/dist/embed-provider-jXwlRmnp.d.cts +19 -0
  119. package/dist/embed-provider-jXwlRmnp.d.ts +19 -0
  120. package/dist/embed.cjs +17 -0
  121. package/dist/embed.cjs.map +1 -0
  122. package/dist/embed.d.cts +56 -0
  123. package/dist/embed.d.ts +56 -0
  124. package/dist/embed.js +17 -0
  125. package/dist/embed.js.map +1 -0
  126. package/dist/emoji-data.cjs +1968 -0
  127. package/dist/emoji-data.cjs.map +1 -0
  128. package/dist/emoji-data.js +1968 -0
  129. package/dist/emoji-data.js.map +1 -0
  130. package/dist/emoji-extension-CmPlRssm.d.ts +46 -0
  131. package/dist/emoji-extension-zJ6F7hW8.d.cts +46 -0
  132. package/dist/emoji.cjs +18 -0
  133. package/dist/emoji.cjs.map +1 -0
  134. package/dist/emoji.d.cts +31 -0
  135. package/dist/emoji.d.ts +31 -0
  136. package/dist/emoji.js +18 -0
  137. package/dist/emoji.js.map +1 -0
  138. package/dist/engine.cjs +119 -0
  139. package/dist/engine.cjs.map +1 -0
  140. package/dist/engine.d.cts +144 -0
  141. package/dist/engine.d.ts +144 -0
  142. package/dist/engine.js +119 -0
  143. package/dist/engine.js.map +1 -0
  144. package/dist/find-extension-BcRyE9K4.d.cts +41 -0
  145. package/dist/find-extension-CmFehyul.d.ts +41 -0
  146. package/dist/find.cjs +20 -0
  147. package/dist/find.cjs.map +1 -0
  148. package/dist/find.d.cts +44 -0
  149. package/dist/find.d.ts +44 -0
  150. package/dist/find.js +20 -0
  151. package/dist/find.js.map +1 -0
  152. package/dist/headless.cjs +151 -518
  153. package/dist/headless.cjs.map +1 -1
  154. package/dist/headless.d.cts +102 -69
  155. package/dist/headless.d.ts +102 -69
  156. package/dist/headless.js +149 -467
  157. package/dist/headless.js.map +1 -1
  158. package/dist/index-CFPmCee4.d.cts +68 -0
  159. package/dist/index-DiCGV60d.d.ts +68 -0
  160. package/dist/index.cjs +183 -2753
  161. package/dist/index.cjs.map +1 -1
  162. package/dist/index.d.cts +345 -253
  163. package/dist/index.d.ts +345 -253
  164. package/dist/index.js +168 -2696
  165. package/dist/index.js.map +1 -1
  166. package/dist/math.cjs +18 -0
  167. package/dist/math.cjs.map +1 -0
  168. package/dist/math.d.cts +49 -0
  169. package/dist/math.d.ts +49 -0
  170. package/dist/math.js +18 -0
  171. package/dist/math.js.map +1 -0
  172. package/dist/mention.cjs +18 -0
  173. package/dist/mention.cjs.map +1 -0
  174. package/dist/mention.d.cts +50 -0
  175. package/dist/mention.d.ts +50 -0
  176. package/dist/mention.js +18 -0
  177. package/dist/mention.js.map +1 -0
  178. package/dist/presence.cjs +562 -0
  179. package/dist/presence.cjs.map +1 -0
  180. package/dist/presence.d.cts +30 -0
  181. package/dist/presence.d.ts +30 -0
  182. package/dist/presence.js +562 -0
  183. package/dist/presence.js.map +1 -0
  184. package/dist/registry-CWBOSAHm.d.cts +121 -0
  185. package/dist/registry-CWBOSAHm.d.ts +121 -0
  186. package/dist/stats-extension-DisOEbbI.d.ts +40 -0
  187. package/dist/stats-extension-Dt1lgP3m.d.cts +40 -0
  188. package/dist/stats.cjs +19 -0
  189. package/dist/stats.cjs.map +1 -0
  190. package/dist/stats.d.cts +19 -0
  191. package/dist/stats.d.ts +19 -0
  192. package/dist/stats.js +19 -0
  193. package/dist/stats.js.map +1 -0
  194. package/dist/styles.css +1 -1
  195. package/dist/table-extension-BTMJnT7J.d.ts +57 -0
  196. package/dist/table-extension-DxZz3jqp.d.cts +57 -0
  197. package/dist/table.cjs +50 -0
  198. package/dist/table.cjs.map +1 -0
  199. package/dist/table.d.cts +57 -0
  200. package/dist/table.d.ts +57 -0
  201. package/dist/table.js +50 -0
  202. package/dist/table.js.map +1 -0
  203. package/dist/to-html-C5t7xyaM.d.ts +21 -0
  204. package/dist/to-html-CNCITCln.d.cts +21 -0
  205. package/dist/toc-extension-CR9U-l6j.d.ts +25 -0
  206. package/dist/toc-extension-DGHywOBv.d.cts +25 -0
  207. package/dist/toc.cjs +18 -0
  208. package/dist/toc.cjs.map +1 -0
  209. package/dist/toc.d.cts +24 -0
  210. package/dist/toc.d.ts +24 -0
  211. package/dist/toc.js +18 -0
  212. package/dist/toc.js.map +1 -0
  213. package/dist/types-C6_QkXzy.d.ts +358 -0
  214. package/dist/types-CJ7Gq3GX.d.cts +358 -0
  215. package/dist/types-DULc8wwg.d.cts +120 -0
  216. package/dist/types-DULc8wwg.d.ts +120 -0
  217. package/dist/view-controller-BD1KwYfE.d.cts +105 -0
  218. package/dist/view-controller-y4cb60ue.d.ts +105 -0
  219. package/dist/view.cjs +23 -0
  220. package/dist/view.cjs.map +1 -0
  221. package/dist/view.d.cts +40 -0
  222. package/dist/view.d.ts +40 -0
  223. package/dist/view.js +23 -0
  224. package/dist/view.js.map +1 -0
  225. package/dist/yjs.cjs +864 -0
  226. package/dist/yjs.cjs.map +1 -0
  227. package/dist/yjs.d.cts +22 -0
  228. package/dist/yjs.d.ts +22 -0
  229. package/dist/yjs.js +864 -0
  230. package/dist/yjs.js.map +1 -0
  231. package/package.json +217 -18
@@ -0,0 +1,3303 @@
1
+ import {
2
+ Transaction,
3
+ blockRangeSelection,
4
+ commonPrefix,
5
+ commonSuffix,
6
+ createEditor,
7
+ deleteBackward,
8
+ deleteForward,
9
+ deleteSelection,
10
+ deleteToLineEnd,
11
+ deleteToLineStart,
12
+ deleteWordBackward,
13
+ deleteWordForward,
14
+ detectMac,
15
+ endOf,
16
+ headBlockId,
17
+ hidingToggle,
18
+ insertHardBreak,
19
+ insertText,
20
+ isClosedToggle,
21
+ isCollapsed,
22
+ isTextFlow,
23
+ mapSelection,
24
+ markRanges,
25
+ nextGraphemeOffset,
26
+ nodeSelection,
27
+ prevGraphemeOffset,
28
+ primaryRegion,
29
+ rangeContent,
30
+ redo,
31
+ regionLength,
32
+ replaceInlineStep,
33
+ runCommands,
34
+ sameSelection,
35
+ selectionRange,
36
+ setAttrs,
37
+ setSelection,
38
+ startOf,
39
+ textSelection,
40
+ toggleMark,
41
+ toggleTask,
42
+ undo,
43
+ undoInputRule
44
+ } from "./chunk-UPSLDTLT.js";
45
+ import {
46
+ alignOrUndefined,
47
+ blockById,
48
+ dataAttributes,
49
+ headingLevel,
50
+ inlineLength,
51
+ inlinePlainText,
52
+ inlineText,
53
+ markElement,
54
+ nodeLength,
55
+ optionalString,
56
+ orderedStart,
57
+ paletteOrUndefined,
58
+ requireBlockIndex,
59
+ sameAttrs,
60
+ sameItems,
61
+ sanitizeHref,
62
+ sliceInline,
63
+ writtenAttrs
64
+ } from "./chunk-62WKUAQY.js";
65
+
66
+ // src/view/react/use-editor-state.ts
67
+ import { useCallback, useRef, useSyncExternalStore } from "react";
68
+ var wholeState = (state) => state;
69
+ function useEditorState(controller, selector, isEqual = Object.is) {
70
+ const last = useRef(null);
71
+ const subscribe = useCallback(
72
+ (notify) => controller.editor.subscribe(() => notify()),
73
+ [controller]
74
+ );
75
+ const getSnapshot = useCallback(() => {
76
+ const { state } = controller.editor;
77
+ const previous = last.current;
78
+ if (previous !== null && previous.state === state && previous.selector === selector) return previous.value;
79
+ const next = selector(state);
80
+ const value = previous !== null && isEqual(previous.value, next) ? previous.value : next;
81
+ last.current = { state, selector, value };
82
+ return value;
83
+ }, [controller, selector, isEqual]);
84
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
85
+ }
86
+
87
+ // src/view/selection/selection-decorations.ts
88
+ var SELECTED = { class: "quoin-selected" };
89
+ function isCrossSelection(selection) {
90
+ if (selection?.kind !== "text") return false;
91
+ const { anchor, head } = selection;
92
+ return anchor.block !== head.block || anchor.region !== head.region;
93
+ }
94
+ function selectionDecorations(state, mode) {
95
+ if (mode !== "decorations" || !isCrossSelection(state.selection)) return [];
96
+ return markRanges(state).map(({ block, region, from, to }) => ({
97
+ kind: "inline",
98
+ block,
99
+ region,
100
+ from,
101
+ to,
102
+ attrs: SELECTED
103
+ }));
104
+ }
105
+
106
+ // src/view/dom/attributes.ts
107
+ var sortedEntries = (attrs) => dataAttributes(attrs, "");
108
+ function setAttributes(element, attrs, prefix = "") {
109
+ for (const [name, value] of writtenAttrs(dataAttributes(attrs, prefix))) element.setAttribute(name, value);
110
+ }
111
+
112
+ // src/view/dom/segments.ts
113
+ function decorationRanges(decorations, length) {
114
+ const ranges = [];
115
+ for (const decoration of decorations) {
116
+ if (decoration.kind !== "inline") continue;
117
+ const from = Math.max(0, decoration.from);
118
+ const to = Math.min(length, decoration.to);
119
+ if (from < to) ranges.push({ from, to, attrs: decoration.attrs });
120
+ }
121
+ return ranges;
122
+ }
123
+ function toSegments(inline, decorations) {
124
+ const length = inlineLength(inline);
125
+ const ranges = decorationRanges(decorations, length);
126
+ const widgets = decorations.filter((decoration) => decoration.kind === "widget").map((decoration) => ({ decoration, at: Math.min(Math.max(decoration.offset, 0), length) })).sort((a, b) => a.at - b.at || (a.decoration.side ?? 1) - (b.decoration.side ?? 1));
127
+ const edges = [...ranges.flatMap((range) => [range.from, range.to]), ...widgets.map((widget) => widget.at)];
128
+ const cuts = [...new Set(edges)].filter((point) => point > 0 && point < length).sort((a, b) => a - b);
129
+ const covering = (from, to) => ranges.filter((range) => range.from <= from && range.to >= to).map((range) => range.attrs);
130
+ const segments = [];
131
+ let nextWidget = 0;
132
+ let nextCut = 0;
133
+ const placeWidgets = (upTo) => {
134
+ for (; nextWidget < widgets.length && widgets[nextWidget].at <= upTo; nextWidget++) {
135
+ segments.push({ kind: "widget", decoration: widgets[nextWidget].decoration, from: widgets[nextWidget].at });
136
+ }
137
+ };
138
+ let start = 0;
139
+ for (const node of inline) {
140
+ const end = start + nodeLength(node);
141
+ if (node.kind === "atom") {
142
+ placeWidgets(start);
143
+ segments.push({ kind: "atom", node, decorations: covering(start, end), from: start });
144
+ } else if (end > start) {
145
+ let from = start;
146
+ while (from < end) {
147
+ while (nextCut < cuts.length && cuts[nextCut] <= from) nextCut++;
148
+ const to = nextCut < cuts.length && cuts[nextCut] < end ? cuts[nextCut] : end;
149
+ placeWidgets(from);
150
+ segments.push({
151
+ kind: "text",
152
+ text: node.text.slice(from - start, to - start),
153
+ marks: node.marks,
154
+ decorations: covering(from, to),
155
+ from
156
+ });
157
+ from = to;
158
+ }
159
+ }
160
+ start = end;
161
+ }
162
+ placeWidgets(length);
163
+ return segments;
164
+ }
165
+ var marksKey = (marks) => marks.map((mark2) => [mark2.type, sortedEntries(mark2.attrs)]);
166
+ var decorationsKey = (decorations) => decorations.map(sortedEntries);
167
+ function segmentSignature(segment) {
168
+ switch (segment.kind) {
169
+ case "text":
170
+ return JSON.stringify(["text", marksKey(segment.marks), decorationsKey(segment.decorations)]);
171
+ case "atom": {
172
+ const { node } = segment;
173
+ const attrs = sortedEntries(node.attrs);
174
+ return JSON.stringify(["atom", node.type, attrs, marksKey(node.marks), decorationsKey(segment.decorations)]);
175
+ }
176
+ case "widget": {
177
+ const { decoration } = segment;
178
+ return JSON.stringify(["widget", decoration.widget, decoration.key, sortedEntries(decoration.attrs)]);
179
+ }
180
+ }
181
+ }
182
+
183
+ // src/view/dom/mark-elements.ts
184
+ function create(doc, tag, attributes) {
185
+ const element = doc.createElement(tag);
186
+ for (const [name, value] of attributes) element.setAttribute(name, value);
187
+ return element;
188
+ }
189
+ function linkElement({ href = "", title } = {}, doc) {
190
+ const safe = sanitizeHref(href);
191
+ if (safe === null) return create(doc, "span", [["data-quoin-link", ""]]);
192
+ const anchor = create(doc, "a", [["href", safe], ["data-quoin-link", ""]]);
193
+ if (title) anchor.title = title;
194
+ return anchor;
195
+ }
196
+ function markElement2(mark2, doc) {
197
+ if (mark2.type === "link") return linkElement(mark2.attrs, doc);
198
+ const { tag, attrs } = markElement(mark2);
199
+ return create(doc, tag, writtenAttrs(attrs));
200
+ }
201
+
202
+ // src/view/dom/wrappers.ts
203
+ var created = /* @__PURE__ */ new WeakSet();
204
+ function decorationElement(attrs, doc) {
205
+ const element = doc.createElement("span");
206
+ element.setAttribute("data-quoin-deco", "");
207
+ setAttributes(element, attrs);
208
+ return element;
209
+ }
210
+ function wrapperDepth(marks, decorations) {
211
+ return marks.length + decorations.length;
212
+ }
213
+ function createWrappers(marks, decorations, doc) {
214
+ const wrappers = [
215
+ ...marks.map((mark2) => markElement2(mark2, doc)),
216
+ ...decorations.map((attrs) => decorationElement(attrs, doc))
217
+ ];
218
+ wrappers.forEach((wrapper, index) => {
219
+ created.add(wrapper);
220
+ if (index > 0) wrappers[index - 1].appendChild(wrapper);
221
+ });
222
+ return wrappers;
223
+ }
224
+ function unwrap(node, depth) {
225
+ const wrappers = [];
226
+ let content = node;
227
+ for (let level = 0; level < depth; level++) {
228
+ if (!content || !created.has(content)) return null;
229
+ if (level < depth - 1 && content.childNodes.length !== 1) return null;
230
+ wrappers.push(content);
231
+ content = content.firstChild;
232
+ }
233
+ return { wrappers, content };
234
+ }
235
+ function wrapperEntries(wrappers, from, to) {
236
+ return wrappers.map((node) => ({ node, from, to, kind: "wrapper" }));
237
+ }
238
+
239
+ // src/view/dom/atom-specs.ts
240
+ function defaultAtomText(node) {
241
+ if (node.type === "mention") return `@${node.attrs.label ?? ""}`;
242
+ return node.type === "math" ? node.attrs.latex ?? "" : node.type;
243
+ }
244
+ function mark(element, attribute, name) {
245
+ element.setAttribute("contenteditable", "false");
246
+ element.setAttribute(attribute, name);
247
+ return element;
248
+ }
249
+ function defaultAtom(node, doc, atomText) {
250
+ const element = mark(doc.createElement("span"), "data-quoin-atom", node.type);
251
+ setAttributes(element, node.attrs, "data-");
252
+ element.textContent = atomText(node);
253
+ return element;
254
+ }
255
+ var defaultWidget = (_decoration, doc) => doc.createElement("span");
256
+ function own(record, key) {
257
+ return record && Object.hasOwn(record, key) ? record[key] : void 0;
258
+ }
259
+ function atomSpec(segment, options, doc) {
260
+ const { node, decorations, from } = segment;
261
+ const depth = wrapperDepth(node.marks, decorations);
262
+ const soleContent = (wrappers) => wrappers.length === 0 || wrappers[wrappers.length - 1].childNodes.length === 1;
263
+ return {
264
+ sig: segmentSignature(segment),
265
+ create() {
266
+ const render = own(options.atoms, node.type);
267
+ const content = render ? mark(render(node, doc), "data-quoin-atom", node.type) : defaultAtom(node, doc, options.atomText ?? defaultAtomText);
268
+ const wrappers = createWrappers(node.marks, decorations, doc);
269
+ wrappers[wrappers.length - 1]?.appendChild(content);
270
+ return wrappers[0] ?? content;
271
+ },
272
+ refresh(top) {
273
+ const chain = unwrap(top, depth);
274
+ return chain !== null && soleContent(chain.wrappers);
275
+ },
276
+ describe(top, out) {
277
+ const { wrappers, content } = unwrap(top, depth);
278
+ out.push(...wrapperEntries(wrappers, from, from + 1), { node: content, from, to: from + 1, kind: "atom" });
279
+ }
280
+ };
281
+ }
282
+ function widgetSpec(segment, options, doc) {
283
+ const { decoration, from } = segment;
284
+ return {
285
+ sig: segmentSignature(segment),
286
+ create() {
287
+ const render = own(options.widgets, decoration.widget) ?? defaultWidget;
288
+ return mark(render(decoration, doc), "data-quoin-widget", decoration.widget);
289
+ },
290
+ describe: (node, out) => void out.push({ node, from, to: from, kind: "widget" })
291
+ };
292
+ }
293
+
294
+ // src/view/dom/dom-map.ts
295
+ var TEXT_NODE = 3;
296
+ var textLength = (node) => (node.textContent ?? "").length;
297
+ var indexIn = (node) => Array.prototype.indexOf.call(node.parentNode.childNodes, node);
298
+ var pointAt = (entry, shift) => ({
299
+ node: entry.node.parentNode,
300
+ offset: indexIn(entry.node) + shift
301
+ });
302
+ function textBefore(root, node, offset) {
303
+ let length = 0;
304
+ if (node.nodeType === TEXT_NODE) {
305
+ length = Math.min(offset, textLength(node));
306
+ } else {
307
+ let child = node.firstChild;
308
+ for (let index = 0; child && index < offset; index++, child = child.nextSibling) {
309
+ length += textLength(child);
310
+ }
311
+ }
312
+ for (let current = node; current !== root; current = current.parentNode) {
313
+ for (let sibling = current.previousSibling; sibling; sibling = sibling.previousSibling) {
314
+ length += textLength(sibling);
315
+ }
316
+ }
317
+ return length;
318
+ }
319
+ function createDomMap(host, length, entries) {
320
+ const byNode = new Map(entries.map((entry) => [entry.node, entry]));
321
+ const texts = entries.filter((entry) => entry.kind === "text");
322
+ const breaksAndAtoms = entries.filter((entry) => entry.kind === "break" || entry.kind === "atom");
323
+ const stops = entries.filter((entry) => entry.kind === "break" || entry.kind === "atom" || entry.kind === "filler");
324
+ function toDom(offset, assoc = 1) {
325
+ const at = Math.min(Math.max(offset, 0), length);
326
+ const after = texts.find((entry) => entry.from <= at && at < entry.to);
327
+ const before = texts.find((entry) => entry.from < at && at <= entry.to);
328
+ const text = assoc === 1 ? after ?? before : before ?? after;
329
+ if (text) return { node: text.node, offset: at - text.from };
330
+ const next = stops.find((entry) => entry.from >= at);
331
+ const previous = breaksAndAtoms.filter((entry) => entry.to <= at).pop();
332
+ const beforeNext = next && pointAt(next, 0);
333
+ const afterPrevious = previous && pointAt(previous, 1);
334
+ return (assoc === 1 ? beforeNext ?? afterPrevious : afterPrevious ?? beforeNext) ?? { node: host, offset: 0 };
335
+ }
336
+ function atChild(parent, offset, end) {
337
+ const children = parent.childNodes;
338
+ for (let index = offset; index < children.length; index++) {
339
+ const entry = byNode.get(children[index]);
340
+ if (entry) return entry.from;
341
+ }
342
+ return end;
343
+ }
344
+ function enclosingAtom(node) {
345
+ for (let current = node; current && current !== host; current = current.parentNode) {
346
+ const entry = byNode.get(current);
347
+ if (entry?.kind === "atom" || entry?.kind === "widget") return entry;
348
+ }
349
+ return void 0;
350
+ }
351
+ function fromDom(node, offset) {
352
+ if (!host.contains(node)) return null;
353
+ if (node === host) return atChild(host, offset, length);
354
+ const atom = enclosingAtom(node);
355
+ if (atom?.kind === "widget") return atom.from;
356
+ if (atom) {
357
+ const inside = textBefore(atom.node, node, offset);
358
+ return inside > 0 && inside * 2 >= textLength(atom.node) ? atom.to : atom.from;
359
+ }
360
+ const entry = byNode.get(node);
361
+ if (!entry) return fromDom(node.parentNode, indexIn(node));
362
+ if (node.nodeType === TEXT_NODE) return entry.from + Math.min(offset, entry.to - entry.from);
363
+ if (entry.kind === "break") return offset > 0 ? entry.to : entry.from;
364
+ return atChild(node, offset, entry.to);
365
+ }
366
+ return { length, toDom, fromDom };
367
+ }
368
+
369
+ // src/view/dom/reconcile.ts
370
+ var signatures = /* @__PURE__ */ new WeakMap();
371
+ function keeps(node, spec) {
372
+ return signatures.get(node) === spec.sig && (spec.refresh?.(node) ?? true);
373
+ }
374
+ function reconcileChildren(parent, specs) {
375
+ const old = Array.from(parent.childNodes);
376
+ let head = 0;
377
+ while (head < specs.length && head < old.length && keeps(old[head], specs[head])) head++;
378
+ let oldEnd = old.length;
379
+ let newEnd = specs.length;
380
+ while (newEnd > head && oldEnd > head && keeps(old[oldEnd - 1], specs[newEnd - 1])) {
381
+ oldEnd--;
382
+ newEnd--;
383
+ }
384
+ const anchor = old[oldEnd] ?? null;
385
+ for (let i = head; i < oldEnd; i++) parent.removeChild(old[i]);
386
+ for (let i = head; i < newEnd; i++) {
387
+ const node = specs[i].create();
388
+ signatures.set(node, specs[i].sig);
389
+ parent.insertBefore(node, anchor);
390
+ }
391
+ }
392
+
393
+ // src/view/dom/text-diff.ts
394
+ function textDiff(before, after, caretHint) {
395
+ if (before === after) return null;
396
+ const prefix = commonPrefix(before, after);
397
+ const suffix = commonSuffix(before, after);
398
+ const shared = Math.min(suffix, Math.min(before.length, after.length) - prefix);
399
+ const removed = before.length - prefix - shared;
400
+ const inserted = after.length - prefix - shared;
401
+ const earliest = prefix - (suffix - shared);
402
+ const from = caretHint === void 0 ? prefix : Math.min(Math.max(caretHint - inserted, earliest), prefix);
403
+ return { from, to: from + removed, insert: after.slice(from, from + inserted) };
404
+ }
405
+
406
+ // src/view/dom/text-specs.ts
407
+ var TEXT = "text";
408
+ var BREAK = "break";
409
+ function textPiece(doc, value, from) {
410
+ return {
411
+ sig: TEXT,
412
+ create: () => doc.createTextNode(value),
413
+ // Assigning data collapses a range inside the node to its start; an edit moves only the points after it.
414
+ refresh(node) {
415
+ const target = node;
416
+ const edit = textDiff(target.data, value);
417
+ if (edit !== null) target.replaceData(edit.from, edit.to - edit.from, edit.insert);
418
+ return true;
419
+ },
420
+ describe: (node, out) => void out.push({ node, from, to: from + value.length, kind: "text" })
421
+ };
422
+ }
423
+ function breakPiece(doc, from) {
424
+ return {
425
+ sig: BREAK,
426
+ create() {
427
+ const element = doc.createElement("br");
428
+ element.setAttribute("data-quoin-break", "");
429
+ return element;
430
+ },
431
+ describe: (node, out) => void out.push({ node, from, to: from + 1, kind: "break" })
432
+ };
433
+ }
434
+ function fillerSpec(doc, at) {
435
+ return {
436
+ sig: "filler",
437
+ create() {
438
+ const element = doc.createElement("br");
439
+ element.setAttribute("data-quoin-filler", "");
440
+ return element;
441
+ },
442
+ describe: (node, out) => void out.push({ node, from: at, to: at, kind: "filler" })
443
+ };
444
+ }
445
+ function textPieces(value, from, doc) {
446
+ const pieces = [];
447
+ let at = from;
448
+ value.split("\n").forEach((part, index) => {
449
+ if (index > 0) pieces.push(breakPiece(doc, at++));
450
+ if (part) {
451
+ pieces.push(textPiece(doc, part, at));
452
+ at += part.length;
453
+ }
454
+ });
455
+ return pieces;
456
+ }
457
+ function wrappedText(segment, doc) {
458
+ const { marks, decorations, text, from } = segment;
459
+ const depth = wrapperDepth(marks, decorations);
460
+ const pieces = textPieces(text, from, doc);
461
+ return {
462
+ sig: segmentSignature(segment),
463
+ create() {
464
+ const wrappers = createWrappers(marks, decorations, doc);
465
+ reconcileChildren(wrappers[depth - 1], pieces);
466
+ return wrappers[0];
467
+ },
468
+ refresh(node) {
469
+ const chain = unwrap(node, depth);
470
+ if (!chain) return false;
471
+ reconcileChildren(chain.wrappers[depth - 1], pieces);
472
+ return true;
473
+ },
474
+ describe(node, out) {
475
+ const { wrappers } = unwrap(node, depth);
476
+ out.push(...wrapperEntries(wrappers, from, from + text.length));
477
+ const children = Array.from(wrappers[depth - 1].childNodes);
478
+ pieces.forEach((piece, index) => piece.describe(children[index], out));
479
+ }
480
+ };
481
+ }
482
+ function textSpecs(segment, doc) {
483
+ return wrapperDepth(segment.marks, segment.decorations) === 0 ? textPieces(segment.text, segment.from, doc) : [wrappedText(segment, doc)];
484
+ }
485
+
486
+ // src/view/dom/render-inline.ts
487
+ var maps = /* @__PURE__ */ new WeakMap();
488
+ function domMapOf(host) {
489
+ return maps.get(host);
490
+ }
491
+ function specsFor(segment, options, doc) {
492
+ switch (segment.kind) {
493
+ case "text":
494
+ return textSpecs(segment, doc);
495
+ case "atom":
496
+ return [atomSpec(segment, options, doc)];
497
+ case "widget":
498
+ return [widgetSpec(segment, options, doc)];
499
+ }
500
+ }
501
+ function needsFiller(inline) {
502
+ const content = inlineText(inline);
503
+ return content === "" || content.endsWith("\n");
504
+ }
505
+ function renderInline(host, inline, decorations, options = {}) {
506
+ const doc = host.ownerDocument;
507
+ const length = inlineLength(inline);
508
+ const specs = toSegments(inline, decorations).flatMap((segment) => specsFor(segment, options, doc));
509
+ if (needsFiller(inline)) specs.push(fillerSpec(doc, length));
510
+ reconcileChildren(host, specs);
511
+ const entries = [];
512
+ const nodes = Array.from(host.childNodes);
513
+ specs.forEach((spec, index) => spec.describe(nodes[index], entries));
514
+ const map = createDomMap(host, length, entries);
515
+ maps.set(host, map);
516
+ return map;
517
+ }
518
+
519
+ // src/view/navigation/caret-from-point.ts
520
+ function caretFromPoint(doc, x, y) {
521
+ if (typeof doc.caretPositionFromPoint === "function") {
522
+ const position = doc.caretPositionFromPoint(x, y);
523
+ return position ? { node: position.offsetNode, offset: position.offset } : null;
524
+ }
525
+ if (typeof doc.caretRangeFromPoint === "function") {
526
+ const range = doc.caretRangeFromPoint(x, y);
527
+ return range ? { node: range.startContainer, offset: range.startOffset } : null;
528
+ }
529
+ return null;
530
+ }
531
+
532
+ // src/view/controller/host-registry.ts
533
+ function hostKey(block, region) {
534
+ return `${block}\0${region}`;
535
+ }
536
+ function createHostRegistry(onRegister) {
537
+ const byKey = /* @__PURE__ */ new Map();
538
+ const byElement = /* @__PURE__ */ new Map();
539
+ const releases = /* @__PURE__ */ new Map();
540
+ function forget(entry) {
541
+ const key = hostKey(entry.block, entry.region);
542
+ if (byKey.get(key) === entry) byKey.delete(key);
543
+ if (byElement.get(entry.element) === entry) byElement.delete(entry.element);
544
+ releases.get(entry)?.();
545
+ releases.delete(entry);
546
+ }
547
+ return {
548
+ register(block, region, element) {
549
+ const key = hostKey(block, region);
550
+ const entry = { block, region, element };
551
+ for (const previous of [byKey.get(key), byElement.get(element)]) if (previous) forget(previous);
552
+ byKey.set(key, entry);
553
+ byElement.set(element, entry);
554
+ if (onRegister) releases.set(entry, onRegister(element));
555
+ return () => forget(entry);
556
+ },
557
+ get: (block, region) => byKey.get(hostKey(block, region))?.element,
558
+ locate(node) {
559
+ for (let current = node; current; current = current.parentNode) {
560
+ const entry = byElement.get(current);
561
+ if (entry) return entry;
562
+ }
563
+ return null;
564
+ },
565
+ all: () => byKey.values()
566
+ };
567
+ }
568
+
569
+ // src/view/controller/dom-selection.ts
570
+ function domToPos(registry, node, offset) {
571
+ const host = registry.locate(node);
572
+ const mapped = host && domMapOf(host.element)?.fromDom(node, offset);
573
+ return host && typeof mapped === "number" ? { block: host.block, region: host.region, offset: mapped } : null;
574
+ }
575
+ function posToDom(registry, pos, assoc) {
576
+ const host = registry.get(pos.block, pos.region);
577
+ return (host && domMapOf(host)?.toDom(pos.offset, assoc)) ?? null;
578
+ }
579
+ function readSelection(registry, domSelection, blockAt2 = () => void 0) {
580
+ const anchorNode = domSelection?.anchorNode;
581
+ const focusNode = domSelection?.focusNode;
582
+ if (!domSelection || !anchorNode || !focusNode) return null;
583
+ const anchor = domToPos(registry, anchorNode, domSelection.anchorOffset);
584
+ const head = domToPos(registry, focusNode, domSelection.focusOffset);
585
+ if (anchor && head) return textSelection(anchor, head);
586
+ if (anchor || head || registry.locate(anchorNode) || registry.locate(focusNode)) return null;
587
+ const block = blockAt2(anchorNode);
588
+ return block !== void 0 && block === blockAt2(focusNode) ? nodeSelection(block) : null;
589
+ }
590
+ function selectBlock(domSelection, element) {
591
+ if (!element) return false;
592
+ if (!element.contains(element.ownerDocument.activeElement)) element.focus();
593
+ const { anchorNode, focusNode } = domSelection;
594
+ if (!anchorNode || !focusNode || !element.contains(anchorNode) || !element.contains(focusNode)) {
595
+ domSelection.collapse(element, 0);
596
+ }
597
+ return true;
598
+ }
599
+ function writeSelection(registry, selection, domSelection, blockElement) {
600
+ if (!selection || !domSelection) return false;
601
+ if (selection.kind === "node") return selectBlock(domSelection, blockElement(selection.block));
602
+ if (selection.kind !== "text") return false;
603
+ const { anchor, head } = selection;
604
+ if (anchor.block !== head.block || anchor.region !== head.region) return false;
605
+ if (sameSelection(readSelection(registry, domSelection), selection)) return true;
606
+ const base = posToDom(registry, anchor, anchor.offset < head.offset ? 1 : -1);
607
+ const extent = posToDom(registry, head, head.offset < anchor.offset ? 1 : -1);
608
+ if (!base || !extent) return false;
609
+ domSelection.setBaseAndExtent(base.node, base.offset, extent.node, extent.offset);
610
+ return true;
611
+ }
612
+
613
+ // src/view/dom/listen.ts
614
+ function listen(target, handlers, options) {
615
+ const entries = Object.entries(handlers);
616
+ for (const [type, handler] of entries) target.addEventListener(type, handler, options);
617
+ return () => {
618
+ for (const [type, handler] of entries) target.removeEventListener(type, handler, options);
619
+ };
620
+ }
621
+
622
+ // src/view/navigation/node-entry.ts
623
+ var NODE_ENTRY = "data-quoin-node-entry";
624
+ var entersAsNode = (controller, block) => controller.blockElement(block)?.querySelector(`[${NODE_ENTRY}]`) != null;
625
+
626
+ // src/view/controller/reader-toggles.ts
627
+ var NO_CHOICES = /* @__PURE__ */ new Map();
628
+ var stores = /* @__PURE__ */ new WeakMap();
629
+ var shownOpen = (block, choices) => choices.get(block.id) ?? block.attrs.open === true;
630
+ function createReaderToggles(controller) {
631
+ let choices = NO_CHOICES;
632
+ const listeners = /* @__PURE__ */ new Set();
633
+ const set = (next) => {
634
+ choices = next;
635
+ for (const listener of [...listeners]) listener();
636
+ };
637
+ controller.subscribeReadOnly(() => {
638
+ if (!controller.readOnly && choices.size > 0) set(NO_CHOICES);
639
+ });
640
+ return {
641
+ get choices() {
642
+ return choices;
643
+ },
644
+ flip(block) {
645
+ set(new Map(choices).set(block.id, !shownOpen(block, choices)));
646
+ },
647
+ subscribe(listener) {
648
+ listeners.add(listener);
649
+ return () => void listeners.delete(listener);
650
+ }
651
+ };
652
+ }
653
+ function readerToggles(controller) {
654
+ let store = stores.get(controller);
655
+ if (store === void 0) {
656
+ store = createReaderToggles(controller);
657
+ stores.set(controller, store);
658
+ }
659
+ return store;
660
+ }
661
+
662
+ // src/view/navigation/visible-blocks.ts
663
+ var known = /* @__PURE__ */ new WeakMap();
664
+ function hidersOf(doc) {
665
+ let hiders = known.get(doc);
666
+ if (hiders === void 0) {
667
+ const first = doc.blocks.findIndex(isClosedToggle);
668
+ hiders = doc.blocks.map((_, index) => first < 0 || index <= first ? null : hidingToggle(doc, index + 1));
669
+ known.set(doc, hiders);
670
+ }
671
+ return hiders;
672
+ }
673
+ var hidingToggleAt = (doc, index) => hidersOf(doc)[index] ?? null;
674
+ function isHiddenAt(doc, index, choices) {
675
+ if (choices.size === 0) return hidingToggleAt(doc, index) !== null;
676
+ const closed = (block) => block.type === "TOGGLE" && !shownOpen(block, choices);
677
+ return hidingToggle(doc, index + 1, closed) !== null;
678
+ }
679
+ var visibleBlockIndexes = (doc) => hidersOf(doc).flatMap((hider, index) => hider === null ? [index] : []);
680
+ function drawnBlockCount(doc, start = 0, end = doc.blocks.length) {
681
+ const hiders = hidersOf(doc);
682
+ let drawn = 0;
683
+ for (let index = Math.max(0, start); index < Math.min(end, hiders.length); index++) if (hiders[index] === null) drawn++;
684
+ return drawn;
685
+ }
686
+
687
+ // src/view/controller/dispatch-selection.ts
688
+ var dispatchSelection = (controller, selection) => controller.dispatch(controller.editor.createTransaction().setSelection(selection));
689
+
690
+ // src/view/navigation/neighbour-block.ts
691
+ var anyBlock = () => true;
692
+ function neighbourBlock(doc, from, direction, accept = anyBlock) {
693
+ const index = doc.index.get(from);
694
+ if (index === void 0) return null;
695
+ const visible = visibleBlockIndexes(doc);
696
+ const ahead = direction < 0 ? visible.filter((at) => at < index).reverse() : visible.filter((at) => at > index);
697
+ const found = ahead.find((at) => accept(doc.blocks[at]));
698
+ return found === void 0 ? null : doc.blocks[found];
699
+ }
700
+ function entryEdge(controller, block, direction) {
701
+ const { doc, schema } = controller.editor.state;
702
+ const edge = direction < 0 ? endOf(doc, schema, block) : startOf(doc, schema, block);
703
+ if (edge.kind !== "text") return edge;
704
+ const drawn = controller.registry.get(edge.head.block, edge.head.region) !== void 0;
705
+ return drawn && !entersAsNode(controller, block) ? edge : nodeSelection(block);
706
+ }
707
+ var ownNode = (controller, block, direction) => direction < 0 && entersAsNode(controller, block) ? nodeSelection(block) : null;
708
+ var holdsTextHead = (controller) => (block) => isTextFlow(controller.editor.state.schema, block) && entryEdge(controller, block.id, 1).kind === "text";
709
+ function neighbourHead(controller, from, direction) {
710
+ const target = neighbourBlock(controller.editor.state.doc, from, direction, holdsTextHead(controller));
711
+ const edge = target && entryEdge(controller, target.id, direction);
712
+ return edge?.kind === "text" ? edge.head : null;
713
+ }
714
+ function moveToNeighbour(controller, from, direction) {
715
+ const target = neighbourBlock(controller.editor.state.doc, from, direction);
716
+ if (target === null) return false;
717
+ dispatchSelection(controller, entryEdge(controller, target.id, direction));
718
+ return true;
719
+ }
720
+
721
+ // src/view/selection/scroll-container.ts
722
+ var SCROLLS = /* @__PURE__ */ new Set(["auto", "scroll"]);
723
+ var scrollsVertically = (element) => SCROLLS.has(getComputedStyle(element).overflowY) && element.scrollHeight > element.clientHeight;
724
+ function scrollContainerOf(element) {
725
+ const { body, documentElement } = element.ownerDocument;
726
+ for (let node = element; node !== null; node = node.parentElement) {
727
+ if (node === body || node === documentElement) return null;
728
+ if (scrollsVertically(node)) return node;
729
+ }
730
+ return null;
731
+ }
732
+
733
+ // src/view/selection/auto-scroll.ts
734
+ var EDGE = 40;
735
+ var MAX_STEP = 20;
736
+ function edgesOf(container, win) {
737
+ if (container === null) return { top: 0, bottom: win.innerHeight };
738
+ const top = container.getBoundingClientRect().top + container.clientTop;
739
+ return { top: Math.max(top, 0), bottom: Math.min(top + container.clientHeight, win.innerHeight) };
740
+ }
741
+ function createAutoScroll(element, onScroll, edge = EDGE) {
742
+ const win = element.ownerDocument.defaultView;
743
+ let container;
744
+ let delta = 0;
745
+ let frame = null;
746
+ const step = (depth) => Math.ceil(Math.min(depth, edge) / edge * MAX_STEP);
747
+ function tick() {
748
+ frame = null;
749
+ if (delta === 0) return;
750
+ (container ?? win).scrollBy(0, delta);
751
+ onScroll();
752
+ frame = win.requestAnimationFrame(tick);
753
+ }
754
+ return {
755
+ update(clientY) {
756
+ if (container === void 0) container = scrollContainerOf(element);
757
+ const { top, bottom } = edgesOf(container, win);
758
+ const [upper, lower] = [top + edge, bottom - edge];
759
+ delta = clientY < upper ? -step(upper - clientY) : clientY > lower ? step(clientY - lower) : 0;
760
+ if (delta !== 0 && frame === null) frame = win.requestAnimationFrame(tick);
761
+ },
762
+ stop() {
763
+ delta = 0;
764
+ container = void 0;
765
+ if (frame !== null) win.cancelAnimationFrame(frame);
766
+ frame = null;
767
+ }
768
+ };
769
+ }
770
+
771
+ // src/view/navigation/caret-in-host.ts
772
+ function caretInHost(registry, element, box, at, x, y) {
773
+ const point = caretFromPoint(element.ownerDocument, Math.min(Math.max(x, box.left), box.right - 1), y);
774
+ const pos = point && domToPos(registry, point.node, point.offset);
775
+ return pos && pos.block === at.block && pos.region === at.region ? pos : null;
776
+ }
777
+
778
+ // src/view/selection/pointer-targets.ts
779
+ function distance(rect, x, y) {
780
+ const dy = y < rect.top ? rect.top - y : Math.max(0, y - rect.bottom);
781
+ const dx = x < rect.left ? rect.left - x : Math.max(0, x - rect.right);
782
+ return [dy, dx];
783
+ }
784
+ function nearest(items, element, x, y) {
785
+ let best = null;
786
+ let bestY = Infinity;
787
+ let bestX = Infinity;
788
+ for (const item of items) {
789
+ const [dy, dx] = distance(element(item).getBoundingClientRect(), x, y);
790
+ if (dy < bestY || dy === bestY && dx < bestX) [best, bestY, bestX] = [item, dy, dx];
791
+ }
792
+ return best;
793
+ }
794
+ var elementAt = (controller, x, y) => controller.root?.ownerDocument.elementFromPoint(x, y) ?? null;
795
+ function hostAt(controller, x, y) {
796
+ const { registry } = controller;
797
+ return registry.locate(elementAt(controller, x, y)) ?? nearest(registry.all(), (entry) => entry.element, x, y);
798
+ }
799
+ function blockAt(controller, x, y) {
800
+ const element = elementAt(controller, x, y);
801
+ const under = element && controller.blockAt(element);
802
+ if (under !== void 0 && under !== null) return under;
803
+ const { doc } = controller.editor.state;
804
+ const drawn = visibleBlockIndexes(doc).flatMap((index) => {
805
+ const shell = controller.blockElement(doc.blocks[index].id);
806
+ return shell ? [{ id: doc.blocks[index].id, shell }] : [];
807
+ });
808
+ return nearest(drawn, (entry) => entry.shell, x, y)?.id ?? null;
809
+ }
810
+ function caretPosAt(controller, host, x, y) {
811
+ const { element, block, region } = host;
812
+ const at = (offset) => ({ block, region, offset });
813
+ const end = domMapOf(element)?.length ?? 0;
814
+ const box = element.getBoundingClientRect();
815
+ if (y < box.top) return at(0);
816
+ if (y >= box.bottom) return at(end);
817
+ return caretInHost(controller.registry, element, box, host, x, y) ?? at(x < box.left ? 0 : end);
818
+ }
819
+
820
+ // src/view/selection/native-interaction.ts
821
+ var NATIVE_INTERACTION = "data-quoin-native";
822
+
823
+ // src/view/selection/cross-block-pointer.ts
824
+ var DRAGGING = "data-quoin-dragging";
825
+ var DRAG_DISTANCE = 4;
826
+ var CONTROLS = `button, a, input, select, textarea, [${NATIVE_INTERACTION}]`;
827
+ var sameHost = (pos, host) => pos.block === host.block && pos.region === host.region;
828
+ function anchorAtCrossing(controller) {
829
+ const native = controller.domSelection();
830
+ const anchor = native?.anchorNode && domToPos(controller.registry, native.anchorNode, native.anchorOffset);
831
+ if (anchor) return anchor;
832
+ const { selection } = controller.editor.state;
833
+ return selection?.kind === "text" ? selection.anchor : null;
834
+ }
835
+ function createCrossBlockPointer(controller) {
836
+ return {
837
+ attach(root) {
838
+ const doc = root.ownerDocument;
839
+ const view = doc.defaultView;
840
+ let press = null;
841
+ let unfollow = () => {
842
+ };
843
+ let last = { x: 0, y: 0 };
844
+ let preventMouseDown = false;
845
+ const scroll = createAutoScroll(root, () => follow(last.x, last.y));
846
+ function select(selection) {
847
+ if (sameSelection(selection, controller.editor.state.selection)) return;
848
+ dispatchSelection(controller, selection);
849
+ }
850
+ function startDragging() {
851
+ controller.dragging = true;
852
+ root.setAttribute(DRAGGING, "");
853
+ }
854
+ function followText(current, x, y) {
855
+ const target = hostAt(controller, x, y);
856
+ if (target === null) return;
857
+ if (current.anchor === null) {
858
+ if (target.element === current.host.element) return;
859
+ current.anchor = anchorAtCrossing(controller);
860
+ if (current.anchor === null) return;
861
+ startDragging();
862
+ }
863
+ select(textSelection(current.anchor, caretPosAt(controller, target, x, y)));
864
+ }
865
+ function followBlocks(current, x, y) {
866
+ if (!controller.dragging && Math.hypot(x - current.x, y - current.y) < DRAG_DISTANCE) return;
867
+ const block = blockAt(controller, x, y);
868
+ if (block === null) return;
869
+ startDragging();
870
+ select(blockRangeSelection(current.start, block));
871
+ }
872
+ function follow(x, y) {
873
+ if (press?.kind === "text") followText(press, x, y);
874
+ else if (press?.kind === "blocks") followBlocks(press, x, y);
875
+ }
876
+ function release() {
877
+ unfollow();
878
+ scroll.stop();
879
+ press = null;
880
+ const dragged = controller.dragging;
881
+ controller.dragging = false;
882
+ root.removeAttribute(DRAGGING);
883
+ return dragged;
884
+ }
885
+ function end() {
886
+ if (release()) controller.focus();
887
+ }
888
+ function onPointerMove(event) {
889
+ if ((event.buttons & 1) === 0) return end();
890
+ last = { x: event.clientX, y: event.clientY };
891
+ follow(last.x, last.y);
892
+ if (controller.dragging) scroll.update(last.y);
893
+ }
894
+ function extendFrom(host, x, y) {
895
+ const { selection } = controller.editor.state;
896
+ if (selection?.kind !== "text") return;
897
+ if (sameHost(selection.anchor, host) && !isCrossSelection(selection)) return;
898
+ press = { kind: "text", host, anchor: selection.anchor };
899
+ preventMouseDown = true;
900
+ startDragging();
901
+ follow(x, y);
902
+ }
903
+ function onPointerDown(event) {
904
+ end();
905
+ preventMouseDown = false;
906
+ if (event.button !== 0 || event.pointerType === "touch") return;
907
+ const target = event.target;
908
+ const host = controller.registry.locate(target);
909
+ const block = host === null ? controller.blockAt(target) : void 0;
910
+ const control = target.closest(CONTROLS);
911
+ if (host !== null) {
912
+ press = { kind: "text", host, anchor: null };
913
+ if (event.shiftKey) extendFrom(host, event.clientX, event.clientY);
914
+ } else if (block !== void 0 && !(control && root.contains(control))) {
915
+ press = { kind: "blocks", start: block, x: event.clientX, y: event.clientY };
916
+ if (!event.shiftKey && entryEdge(controller, block, 1).kind === "node") {
917
+ controller.nodeReveal.pressed(block);
918
+ select(nodeSelection(block));
919
+ }
920
+ } else {
921
+ return;
922
+ }
923
+ const unfollowDocument = listen(doc, { pointermove: onPointerMove, pointerup: end, pointercancel: end });
924
+ const unfollowView = listen(view, { blur: end });
925
+ unfollow = () => {
926
+ unfollowDocument();
927
+ unfollowView();
928
+ };
929
+ }
930
+ function onMouseDown(event) {
931
+ if (preventMouseDown) event.preventDefault();
932
+ preventMouseDown = false;
933
+ }
934
+ const unlisten = listen(root, { pointerdown: onPointerDown, mousedown: onMouseDown });
935
+ return () => {
936
+ release();
937
+ unlisten();
938
+ };
939
+ }
940
+ };
941
+ }
942
+
943
+ // src/view/selection/paint-selection.ts
944
+ var NAME = "quoin-selection";
945
+ function coveredRanges(controller) {
946
+ return markRanges(controller.editor.state).flatMap(({ block, region, from, to }) => {
947
+ const host = controller.registry.get(block, region);
948
+ const map = host && domMapOf(host);
949
+ if (!host || !map) return [];
950
+ const start = map.toDom(from, 1);
951
+ const end = map.toDom(to, -1);
952
+ const range = host.ownerDocument.createRange();
953
+ range.setStart(start.node, start.offset);
954
+ range.setEnd(end.node, end.offset);
955
+ return [range];
956
+ });
957
+ }
958
+ function highlightPainter(highlights, Highlight) {
959
+ let painted = [];
960
+ function shared() {
961
+ const existing = highlights.get(NAME);
962
+ if (existing) return existing;
963
+ const created2 = new Highlight();
964
+ highlights.set(NAME, created2);
965
+ return created2;
966
+ }
967
+ function clear() {
968
+ if (painted.length === 0) return;
969
+ const highlight = shared();
970
+ for (const range of painted) highlight.delete(range);
971
+ painted = [];
972
+ }
973
+ return {
974
+ mode: "highlight",
975
+ paint(controller) {
976
+ clear();
977
+ if (!isCrossSelection(controller.editor.state.selection)) return;
978
+ painted = coveredRanges(controller);
979
+ const highlight = shared();
980
+ for (const range of painted) highlight.add(range);
981
+ },
982
+ clear
983
+ };
984
+ }
985
+ function decorationPainter() {
986
+ return { mode: "decorations", paint: (controller) => controller.refreshHosts(), clear: () => void 0 };
987
+ }
988
+ function createPainter(win) {
989
+ const { CSS: css, Highlight } = win;
990
+ return css?.highlights && Highlight ? highlightPainter(css.highlights, Highlight) : decorationPainter();
991
+ }
992
+
993
+ // src/view/selection/in-place.ts
994
+ function scrollOf(at) {
995
+ return "scrollY" in at ? { at, left: at.scrollX, top: at.scrollY } : { at, left: at.scrollLeft, top: at.scrollTop };
996
+ }
997
+ function inPlace(element, change) {
998
+ const scrollers = [];
999
+ for (let at = element; at !== null; at = at.parentElement) scrollers.push(at);
1000
+ const view = element.ownerDocument.defaultView;
1001
+ if (view !== null) scrollers.push(view);
1002
+ const before = scrollers.map(scrollOf);
1003
+ change();
1004
+ for (const { at, left, top } of before) {
1005
+ const now = scrollOf(at);
1006
+ if (now.left !== left || now.top !== top) at.scrollTo({ left, top, behavior: "instant" });
1007
+ }
1008
+ }
1009
+
1010
+ // src/view/selection/selection-sink.ts
1011
+ var wholeBlocks = (selection) => selection?.kind === "blocks" || selection?.kind === "node";
1012
+ function heldBySink(selection) {
1013
+ return wholeBlocks(selection) || isCrossSelection(selection);
1014
+ }
1015
+ var NO_TEXT = "\xA0";
1016
+ function selectionText(state) {
1017
+ const text = markRanges(state).map((range) => inlinePlainText(rangeContent(state, range), state.schema)).join("\n");
1018
+ return text === "" ? NO_TEXT : text;
1019
+ }
1020
+ var focusInPlace = (sink) => inPlace(sink, () => sink.focus({ preventScroll: true }));
1021
+ var focusing = null;
1022
+ var focusingSink = (controller) => focusing === controller;
1023
+ function focusSink(controller) {
1024
+ const { sink } = controller;
1025
+ if (sink === null) return;
1026
+ const text = selectionText(controller.editor.state);
1027
+ if (sink.textContent !== text) sink.textContent = text;
1028
+ if (sink.ownerDocument.activeElement !== sink) {
1029
+ const outer = focusing;
1030
+ focusing = controller;
1031
+ try {
1032
+ focusInPlace(sink);
1033
+ } finally {
1034
+ focusing = outer;
1035
+ }
1036
+ }
1037
+ sink.ownerDocument.getSelection()?.selectAllChildren(sink);
1038
+ }
1039
+ function selectedBlockName({ doc, schema, selection }, blockLabel) {
1040
+ const block = selection?.kind === "node" ? blockById(doc, selection.block) : void 0;
1041
+ return block === void 0 ? null : blockLabel?.(block, schema) ?? "";
1042
+ }
1043
+ function clearSink(controller) {
1044
+ const { sink } = controller;
1045
+ if (sink !== null && sink.textContent !== "") sink.textContent = "";
1046
+ }
1047
+
1048
+ // src/view/selection/cross-mode.ts
1049
+ var CROSS = "data-quoin-cross";
1050
+ function createCrossMode(controller) {
1051
+ let attached = null;
1052
+ let active = false;
1053
+ let synced = null;
1054
+ let frame = null;
1055
+ function sync() {
1056
+ const { state } = controller.editor;
1057
+ const cross = isCrossSelection(state.selection);
1058
+ synced = state;
1059
+ if (cross) {
1060
+ attached?.root.setAttribute(CROSS, "");
1061
+ attached?.painter.paint(controller);
1062
+ } else if (active) {
1063
+ attached?.root.removeAttribute(CROSS);
1064
+ attached?.painter.clear();
1065
+ }
1066
+ if (!heldBySink(state.selection)) clearSink(controller);
1067
+ active = cross;
1068
+ }
1069
+ function syncLater() {
1070
+ if (attached === null || frame !== null) return;
1071
+ frame = attached.view.requestAnimationFrame(() => {
1072
+ frame = null;
1073
+ if (controller.editor.state !== synced) sync();
1074
+ });
1075
+ }
1076
+ return {
1077
+ get active() {
1078
+ return active;
1079
+ },
1080
+ get mode() {
1081
+ return attached?.painter.mode ?? "highlight";
1082
+ },
1083
+ attach(root) {
1084
+ const view = root.ownerDocument.defaultView;
1085
+ const current = { root, view, painter: createPainter(view) };
1086
+ attached = current;
1087
+ const detachPointer = createCrossBlockPointer(controller).attach(root);
1088
+ sync();
1089
+ return () => {
1090
+ detachPointer();
1091
+ if (frame !== null) view.cancelAnimationFrame(frame);
1092
+ frame = null;
1093
+ current.painter.clear();
1094
+ root.removeAttribute(CROSS);
1095
+ if (attached === current) attached = null;
1096
+ active = false;
1097
+ };
1098
+ },
1099
+ sync,
1100
+ syncLater
1101
+ };
1102
+ }
1103
+
1104
+ // src/view/selection/node-reveal.ts
1105
+ var nodeOf = (selection) => selection?.kind === "node" ? selection.block : null;
1106
+ function createNodeReveal(element) {
1107
+ let shown = null;
1108
+ let pressed = null;
1109
+ let owed = null;
1110
+ return {
1111
+ pressed(block) {
1112
+ pressed = block;
1113
+ },
1114
+ follow(selection) {
1115
+ const block = nodeOf(selection);
1116
+ if (block !== shown) shown = null;
1117
+ if (block !== owed) owed = null;
1118
+ },
1119
+ settle(selection) {
1120
+ const block = nodeOf(selection);
1121
+ if (block !== shown) owed = block;
1122
+ shown = block;
1123
+ },
1124
+ show(selection, local) {
1125
+ const block = nodeOf(selection);
1126
+ if (block !== null && block !== pressed && (block !== shown || local && block === owed)) {
1127
+ element(block)?.scrollIntoView({ block: "nearest" });
1128
+ }
1129
+ if (local) owed = null;
1130
+ shown = block;
1131
+ pressed = null;
1132
+ }
1133
+ };
1134
+ }
1135
+
1136
+ // src/view/controller/announcer.ts
1137
+ var THROTTLE_MS = 500;
1138
+ function createAnnouncer(stateOf, throttleMs = THROTTLE_MS) {
1139
+ let spoken = null;
1140
+ const listeners = /* @__PURE__ */ new Set();
1141
+ return {
1142
+ announce(message) {
1143
+ const state = stateOf();
1144
+ const now = Date.now();
1145
+ if (spoken?.message === message && spoken.state === state && now - spoken.at < throttleMs) return;
1146
+ spoken = { message, state, serial: (spoken?.serial ?? 0) + 1, at: now };
1147
+ for (const listener of [...listeners]) listener();
1148
+ },
1149
+ current: () => spoken?.state === stateOf() ? spoken : null,
1150
+ subscribe(listener) {
1151
+ listeners.add(listener);
1152
+ return () => void listeners.delete(listener);
1153
+ }
1154
+ };
1155
+ }
1156
+
1157
+ // src/view/controller/element-slot.ts
1158
+ function createElementSlot(changed) {
1159
+ let current = null;
1160
+ return {
1161
+ get element() {
1162
+ return current;
1163
+ },
1164
+ register(element) {
1165
+ current = element;
1166
+ changed();
1167
+ return () => {
1168
+ if (current === element) current = null;
1169
+ changed();
1170
+ };
1171
+ }
1172
+ };
1173
+ }
1174
+
1175
+ // src/view/controller/selection-element.ts
1176
+ function elementFor({ doc }, selection, rendered) {
1177
+ const id = headBlockId(selection);
1178
+ const index = id === null ? void 0 : doc.index.get(id);
1179
+ if (id === null || index === void 0) return void 0;
1180
+ const hiding = hidingToggleAt(doc, index);
1181
+ if (hiding !== null) return rendered.block(doc.blocks[hiding].id);
1182
+ const host = selection?.kind === "text" ? rendered.hosts.get(id, selection.head.region) : void 0;
1183
+ return host ?? rendered.block(id);
1184
+ }
1185
+ function selectionElement(state, rendered) {
1186
+ const { doc, schema, selection } = state;
1187
+ if (doc.blocks.length === 0) return rendered.empty?.() ?? void 0;
1188
+ const sink = heldBySink(selection) ? rendered.sink?.() : null;
1189
+ if (sink) return sink;
1190
+ return elementFor(state, selection, rendered) ?? elementFor(state, startOf(doc, schema, doc.blocks[0].id), rendered);
1191
+ }
1192
+
1193
+ // src/view/controller/tab-stop.ts
1194
+ function createTabStop() {
1195
+ let current;
1196
+ return {
1197
+ move(next) {
1198
+ if (next === current) return;
1199
+ if (current) current.tabIndex = -1;
1200
+ if (next) next.tabIndex = 0;
1201
+ current = next;
1202
+ }
1203
+ };
1204
+ }
1205
+
1206
+ // src/view/controller/drawn-elements.ts
1207
+ function createDrawnElements(hosts, state) {
1208
+ const tabStop = createTabStop();
1209
+ const syncTabStop = () => tabStop.move(focusElement());
1210
+ const blocks = createHostRegistry(() => {
1211
+ syncTabStop();
1212
+ return syncTabStop;
1213
+ });
1214
+ const sink = createElementSlot(syncTabStop);
1215
+ const empty = createElementSlot(syncTabStop);
1216
+ const rendered = {
1217
+ hosts,
1218
+ block: (id) => blocks.get(id, ""),
1219
+ sink: () => sink.element,
1220
+ empty: () => empty.element
1221
+ };
1222
+ function focusElement() {
1223
+ return selectionElement(state(), rendered);
1224
+ }
1225
+ return {
1226
+ get sink() {
1227
+ return sink.element;
1228
+ },
1229
+ get emptyHost() {
1230
+ return empty.element;
1231
+ },
1232
+ registerBlock: (id, element) => blocks.register(id, "", element),
1233
+ block: rendered.block,
1234
+ blockAt: (node) => blocks.locate(node)?.block,
1235
+ registerSink: sink.register,
1236
+ registerEmptyHost: empty.register,
1237
+ isEditorElement: (element) => element === sink.element || element === empty.element || [hosts, blocks].some((registry) => registry.locate(element)?.element === element),
1238
+ focusElement,
1239
+ syncTabStop
1240
+ };
1241
+ }
1242
+
1243
+ // src/view/controller/focus-ownership.ts
1244
+ function hasFocus(controller, doc) {
1245
+ const active = doc?.activeElement ?? null;
1246
+ return active !== null && controller.isEditorElement(active);
1247
+ }
1248
+ function ownsFocus(controller, doc) {
1249
+ const active = doc.activeElement;
1250
+ return !active || active === doc.body || active === controller.root || hasFocus(controller, doc);
1251
+ }
1252
+
1253
+ // src/view/controller/focus-target.ts
1254
+ function focusTarget(state, target) {
1255
+ const { doc, schema, selection } = state;
1256
+ if (doc.blocks.length === 0) return null;
1257
+ if (target === "end") return endOf(doc, schema, doc.blocks[doc.blocks.length - 1].id);
1258
+ if (target === "start" || target === void 0 && selection === null) return startOf(doc, schema, doc.blocks[0].id);
1259
+ return target === void 0 ? selection : textSelection(target);
1260
+ }
1261
+
1262
+ // src/view/controller/host-decorations.ts
1263
+ var NO_DECORATIONS = [];
1264
+ function decorationsByHost(decorations) {
1265
+ const byHost = /* @__PURE__ */ new Map();
1266
+ for (const decoration of decorations) {
1267
+ const key = hostKey(decoration.block, decoration.kind === "block" ? "" : decoration.region);
1268
+ const group = byHost.get(key);
1269
+ if (group) group.push(decoration);
1270
+ else byHost.set(key, [decoration]);
1271
+ }
1272
+ return byHost;
1273
+ }
1274
+ function sameDecoration(a, b) {
1275
+ if (a.kind !== b.kind || a.block !== b.block || !sameAttrs(a.attrs, b.attrs)) return false;
1276
+ if (a.kind === "inline" && b.kind === "inline") {
1277
+ return a.region === b.region && a.from === b.from && a.to === b.to;
1278
+ }
1279
+ if (a.kind === "widget" && b.kind === "widget") {
1280
+ const samePlace = a.region === b.region && a.offset === b.offset && a.side === b.side;
1281
+ return samePlace && a.key === b.key && a.widget === b.widget;
1282
+ }
1283
+ return true;
1284
+ }
1285
+ function sameDecorations(a, b) {
1286
+ return a === b || sameItems(a, b, sameDecoration);
1287
+ }
1288
+ var byMode = { highlight: /* @__PURE__ */ new WeakMap(), decorations: /* @__PURE__ */ new WeakMap() };
1289
+ function hostDecorations(editor, block, region, mode = "highlight") {
1290
+ const { state } = editor;
1291
+ const byState = byMode[mode];
1292
+ let grouped = byState.get(state);
1293
+ if (grouped === void 0) {
1294
+ grouped = decorationsByHost([...editor.decorations(), ...selectionDecorations(state, mode)]);
1295
+ byState.set(state, grouped);
1296
+ }
1297
+ return grouped.get(hostKey(block, region)) ?? NO_DECORATIONS;
1298
+ }
1299
+
1300
+ // src/view/controller/region-inline.ts
1301
+ function regionInline(state, block, region) {
1302
+ const regions = blockById(state.doc, block)?.regions;
1303
+ return regions && Object.hasOwn(regions, region) ? regions[region] : void 0;
1304
+ }
1305
+
1306
+ // src/view/controller/host-renderer.ts
1307
+ function createHostRenderer(editor, options, decorationsOf, afterRender) {
1308
+ const rendered = /* @__PURE__ */ new WeakMap();
1309
+ function paint(host, always) {
1310
+ const inline = regionInline(editor.state, host.block, host.region);
1311
+ if (inline === void 0) return false;
1312
+ const decorations = decorationsOf(host.block, host.region);
1313
+ const last = rendered.get(host.element);
1314
+ const restyled = last?.inline === inline && !sameDecorations(last.decorations, decorations);
1315
+ if (!always && last?.inline === inline && !restyled) return false;
1316
+ renderInline(host.element, inline, decorations, options);
1317
+ afterRender(host.element);
1318
+ rendered.set(host.element, { inline, decorations });
1319
+ return restyled;
1320
+ }
1321
+ return {
1322
+ render: (host) => paint(host, true),
1323
+ renderIfChanged: (host) => void paint(host, false)
1324
+ };
1325
+ }
1326
+
1327
+ // src/view/controller/outside-changes.ts
1328
+ function createOutsideChanges(editorHasFocus, movedFrom) {
1329
+ let depth = 0;
1330
+ let focused = false;
1331
+ let moved = null;
1332
+ let settled = false;
1333
+ return {
1334
+ inside(change) {
1335
+ depth += 1;
1336
+ try {
1337
+ change();
1338
+ } finally {
1339
+ depth -= 1;
1340
+ }
1341
+ },
1342
+ committed(tr) {
1343
+ const outside = depth === 0 && editorHasFocus();
1344
+ focused ||= outside;
1345
+ if (tr?.selectionSet !== false) {
1346
+ moved = null;
1347
+ settled ||= depth === 0;
1348
+ return;
1349
+ }
1350
+ const from = moved ?? (outside && !settled ? movedFrom(tr.before.selection) : null);
1351
+ settled ||= outside;
1352
+ moved = from && mapSelection(from, (pos, assoc) => tr.mapPos(pos, assoc), tr.before.doc);
1353
+ },
1354
+ takeFocused() {
1355
+ const value = focused;
1356
+ focused = false;
1357
+ return value;
1358
+ },
1359
+ takeMoved() {
1360
+ const value = moved;
1361
+ moved = null;
1362
+ settled = false;
1363
+ return value;
1364
+ }
1365
+ };
1366
+ }
1367
+
1368
+ // src/view/controller/read-only.ts
1369
+ function createReadOnly(registry, initial) {
1370
+ let value = initial;
1371
+ const listeners = /* @__PURE__ */ new Set();
1372
+ return {
1373
+ current: () => value,
1374
+ set(readOnly) {
1375
+ const changed = readOnly !== value;
1376
+ value = readOnly;
1377
+ for (const { element } of registry.all()) element.contentEditable = readOnly ? "false" : "true";
1378
+ if (changed) for (const listener of [...listeners]) listener();
1379
+ },
1380
+ subscribe(listener) {
1381
+ listeners.add(listener);
1382
+ return () => void listeners.delete(listener);
1383
+ }
1384
+ };
1385
+ }
1386
+
1387
+ // src/view/clipboard/slice-marker.ts
1388
+ var QUOIN_SLICE_TYPE = "application/x-quoin+json";
1389
+ var MARKER = /data-quoin-slice=(["'])([A-Za-z0-9+/=]*)\1/;
1390
+ function encode(text) {
1391
+ let binary = "";
1392
+ for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte);
1393
+ return btoa(binary);
1394
+ }
1395
+ function decode(encoded) {
1396
+ const binary = atob(encoded);
1397
+ return new TextDecoder().decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
1398
+ }
1399
+ function wrapSliceHTML(html, json) {
1400
+ return `<div data-quoin-slice="${encode(json)}">${html}</div>`;
1401
+ }
1402
+ function readSliceHTML(html) {
1403
+ const encoded = MARKER.exec(html)?.[2];
1404
+ if (!encoded) return null;
1405
+ try {
1406
+ return decode(encoded);
1407
+ } catch {
1408
+ return null;
1409
+ }
1410
+ }
1411
+
1412
+ // src/view/clipboard/paste-transfer.ts
1413
+ function caretOf(controller) {
1414
+ const { selection } = controller.editor.state;
1415
+ return selection?.kind === "text" ? selection.head : null;
1416
+ }
1417
+ function transferFiles(transfer) {
1418
+ const files = Array.from(transfer.files);
1419
+ const textless = !transfer.getData("text/plain") && !transfer.getData("text/html");
1420
+ return files.length > 0 && textless ? files : null;
1421
+ }
1422
+ function pasteTransfer(controller, transfer) {
1423
+ const files = transferFiles(transfer);
1424
+ if (files !== null) {
1425
+ controller.onFiles?.(files, caretOf(controller));
1426
+ return;
1427
+ }
1428
+ const text = transfer.getData("text/plain");
1429
+ const html = transfer.getData("text/html");
1430
+ const json = transfer.getData(QUOIN_SLICE_TYPE) || readSliceHTML(html) || void 0;
1431
+ const data = { json, html: html || void 0, text: text || void 0 };
1432
+ if (controller.edit(() => controller.editor.paste(data))) controller.focus();
1433
+ }
1434
+
1435
+ // src/view/clipboard/clipboard-events.ts
1436
+ function handleCopy(controller, event, cut) {
1437
+ const { clipboardData } = event;
1438
+ if (!clipboardData) return;
1439
+ const copied = controller.editor.copy();
1440
+ if (copied === null) return;
1441
+ event.preventDefault();
1442
+ clipboardData.setData("text/plain", copied.text);
1443
+ clipboardData.setData("text/html", wrapSliceHTML(copied.html, copied.json));
1444
+ clipboardData.setData(QUOIN_SLICE_TYPE, copied.json);
1445
+ if (cut) controller.run(deleteSelection);
1446
+ }
1447
+ function handlePaste(controller, event) {
1448
+ if (!event.clipboardData) return;
1449
+ event.preventDefault();
1450
+ pasteTransfer(controller, event.clipboardData);
1451
+ }
1452
+
1453
+ // src/view/clipboard/drop-events.ts
1454
+ var DROPPABLE = ["Files", "text/plain", "text/html"];
1455
+ function dropPos(controller, x, y) {
1456
+ const host = hostAt(controller, x, y);
1457
+ return host && caretPosAt(controller, host, x, y);
1458
+ }
1459
+ function textlessBlockAt(controller, x, y) {
1460
+ const element = controller.root?.ownerDocument.elementFromPoint(x, y);
1461
+ const block = element ? controller.blockAt(element) : void 0;
1462
+ if (block === void 0 || controller.registry.locate(element ?? null) !== null) return null;
1463
+ const hasText = [...controller.registry.all()].some((host) => host.block === block);
1464
+ return hasText && !entersAsNode(controller, block) ? null : { block, region: "", offset: 0 };
1465
+ }
1466
+ function handleDrop(controller, event) {
1467
+ event.preventDefault();
1468
+ const { dataTransfer } = event;
1469
+ if (!dataTransfer) return;
1470
+ const files = transferFiles(dataTransfer);
1471
+ const onBlock2 = files && textlessBlockAt(controller, event.clientX, event.clientY);
1472
+ if (files && onBlock2) {
1473
+ controller.onFiles?.(files, onBlock2);
1474
+ return;
1475
+ }
1476
+ const pos = dropPos(controller, event.clientX, event.clientY);
1477
+ if (pos) controller.dispatch(controller.editor.createTransaction().setSelection(textSelection(pos)).setOrigin("drop"));
1478
+ pasteTransfer(controller, dataTransfer);
1479
+ }
1480
+ function handleDragOver(event) {
1481
+ const types = event.dataTransfer?.types ?? [];
1482
+ if (DROPPABLE.some((type) => types.includes(type))) event.preventDefault();
1483
+ }
1484
+ function handleDragStart(event) {
1485
+ event.preventDefault();
1486
+ }
1487
+
1488
+ // src/view/clipboard/attach-clipboard.ts
1489
+ function attachClipboard(controller, root, catchUp) {
1490
+ const inEmptyHost = (target) => target.closest?.("[data-quoin-empty]") != null;
1491
+ const inEditor = ({ target }) => controller.registry.locate(target) !== null || controller.isEditorElement(target) || inEmptyHost(target);
1492
+ const inField = ({ target }) => target?.closest?.("input, textarea, select") != null;
1493
+ const droppable = (event) => !controller.readOnly && !inField(event);
1494
+ const onCopy = (event) => {
1495
+ if (!inEditor(event)) return;
1496
+ catchUp();
1497
+ handleCopy(controller, event, false);
1498
+ };
1499
+ const onCut = (event) => {
1500
+ if (controller.readOnly || !inEditor(event)) return;
1501
+ catchUp();
1502
+ handleCopy(controller, event, true);
1503
+ };
1504
+ const onPaste = (event) => {
1505
+ if (controller.readOnly || !inEditor(event)) return;
1506
+ catchUp();
1507
+ handlePaste(controller, event);
1508
+ };
1509
+ const onDragStart = (event) => {
1510
+ if (controller.registry.locate(event.target) !== null) handleDragStart(event);
1511
+ };
1512
+ const onDragOver = (event) => {
1513
+ if (droppable(event)) handleDragOver(event);
1514
+ };
1515
+ const onDrop = (event) => {
1516
+ if (droppable(event)) handleDrop(controller, event);
1517
+ };
1518
+ const listeners = [
1519
+ ["copy", onCopy],
1520
+ ["cut", onCut],
1521
+ ["paste", onPaste],
1522
+ ["dragstart", onDragStart],
1523
+ ["dragover", onDragOver],
1524
+ ["drop", onDrop]
1525
+ ];
1526
+ for (const [type, listener] of listeners) root.addEventListener(type, listener);
1527
+ return () => {
1528
+ for (const [type, listener] of listeners) root.removeEventListener(type, listener);
1529
+ };
1530
+ }
1531
+
1532
+ // src/view/selection/sink-composition.ts
1533
+ var SINK_KEY = "sink";
1534
+ function textBetween(sink, start, startOffset, end, endOffset) {
1535
+ const range = sink.ownerDocument.createRange();
1536
+ range.setStart(start, startOffset);
1537
+ range.setEnd(end, endOffset);
1538
+ return range.toString();
1539
+ }
1540
+ function textOutsideSelection(sink) {
1541
+ const selection = sink.ownerDocument.getSelection();
1542
+ const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
1543
+ if (range === null || !sink.contains(range.startContainer) || !sink.contains(range.endContainer)) {
1544
+ return { before: "", after: "" };
1545
+ }
1546
+ return {
1547
+ before: textBetween(sink, sink, 0, range.startContainer, range.startOffset),
1548
+ after: textBetween(sink, range.endContainer, range.endOffset, sink, sink.childNodes.length)
1549
+ };
1550
+ }
1551
+ function composedText(sink, { before, after }) {
1552
+ let text = sink.textContent ?? "";
1553
+ if (text.startsWith(before)) text = text.slice(before.length);
1554
+ return text.endsWith(after) ? text.slice(0, text.length - after.length) : text;
1555
+ }
1556
+ function createSinkComposition(controller) {
1557
+ let outside = null;
1558
+ let stopWaiting = null;
1559
+ function settle() {
1560
+ stopWaiting?.();
1561
+ stopWaiting = null;
1562
+ const { sink } = controller;
1563
+ const ignored = wholeBlocks(controller.editor.state.selection);
1564
+ const text = sink !== null && outside !== null && !ignored ? composedText(sink, outside) : "";
1565
+ outside = null;
1566
+ controller.composingKey = null;
1567
+ clearSink(controller);
1568
+ if (text === "") controller.syncSelectionToDom();
1569
+ else controller.edit(() => controller.editor.insertText(text));
1570
+ }
1571
+ return {
1572
+ start() {
1573
+ stopWaiting?.();
1574
+ stopWaiting = null;
1575
+ if (outside === null && controller.sink !== null) outside = textOutsideSelection(controller.sink);
1576
+ controller.composingKey = SINK_KEY;
1577
+ },
1578
+ end() {
1579
+ const { sink } = controller;
1580
+ if (outside === null || stopWaiting !== null || sink === null) return;
1581
+ const onInput = (event) => {
1582
+ if (!event.isComposing) settle();
1583
+ };
1584
+ const timer = setTimeout(settle, 0);
1585
+ sink.addEventListener("input", onInput);
1586
+ stopWaiting = () => {
1587
+ clearTimeout(timer);
1588
+ sink.removeEventListener("input", onInput);
1589
+ };
1590
+ },
1591
+ release() {
1592
+ if (stopWaiting !== null) settle();
1593
+ else if (outside !== null) {
1594
+ outside = null;
1595
+ controller.composingKey = null;
1596
+ }
1597
+ }
1598
+ };
1599
+ }
1600
+
1601
+ // src/engine/commands/transpose-chars.ts
1602
+ var LINE_BREAK = "\n";
1603
+ var transposeChars = (state, dispatch) => {
1604
+ const { selection } = state;
1605
+ if (selection?.kind !== "text" || !isCollapsed(selection)) return false;
1606
+ const { head } = selection;
1607
+ const inline = blockById(state.doc, head.block)?.regions[head.region];
1608
+ if (inline === void 0) return false;
1609
+ const value = inlineText(inline);
1610
+ const atLineEnd = head.offset === value.length || value[head.offset] === LINE_BREAK;
1611
+ const end = atLineEnd ? head.offset : nextGraphemeOffset(value, head.offset);
1612
+ const middle = prevGraphemeOffset(value, end);
1613
+ const start = prevGraphemeOffset(value, middle);
1614
+ if (start === middle || value.slice(start, end).includes(LINE_BREAK)) return false;
1615
+ const swapped = [...sliceInline(inline, middle, end), ...sliceInline(inline, start, middle)];
1616
+ const tr = new Transaction(state).setOrigin("input");
1617
+ tr.step(replaceInlineStep(head.block, head.region, start, end, swapped));
1618
+ dispatch?.(tr.setSelection(textSelection({ ...head, offset: end })));
1619
+ return true;
1620
+ };
1621
+
1622
+ // src/view/input/replace-range.ts
1623
+ function replaceRange(editor, range, text, withRules) {
1624
+ const dispatch = (tr) => editor.dispatch(tr);
1625
+ dispatch(editor.createTransaction().setSelection(textSelection(range.from, range.to)).setOrigin("input"));
1626
+ if (text === "") return deleteSelection(editor.state, (tr) => dispatch(tr.setOrigin("input")));
1627
+ return withRules ? editor.insertText(text) : insertText(text, { origin: "input" })(editor.state, dispatch);
1628
+ }
1629
+
1630
+ // src/view/input/target-range.ts
1631
+ function targetRangeToModel(controller, event) {
1632
+ const range = typeof event.getTargetRanges === "function" ? event.getTargetRanges()[0] : void 0;
1633
+ if (range === void 0) return null;
1634
+ const from = domToPos(controller.registry, range.startContainer, range.startOffset);
1635
+ const to = domToPos(controller.registry, range.endContainer, range.endOffset);
1636
+ return from && to && { from, to };
1637
+ }
1638
+ var isEmpty = ({ from, to }) => sameSelection(textSelection(from), textSelection(to));
1639
+ function targetBeyondSelection(controller, event) {
1640
+ const range = targetRangeToModel(controller, event);
1641
+ if (range === null || isEmpty(range)) return null;
1642
+ const { doc, selection } = controller.editor.state;
1643
+ const selected = selectionRange(doc, selection);
1644
+ const same = selected !== null && sameSelection(textSelection(selected.from, selected.to), textSelection(range.from, range.to));
1645
+ return same ? null : range;
1646
+ }
1647
+
1648
+ // src/view/input/before-input.ts
1649
+ var ENTER = { key: "Enter", altKey: false, ctrlKey: false, metaKey: false, shiftKey: false };
1650
+ var deleteRange = (controller, range) => controller.edit(() => replaceRange(controller.editor, range, "", false));
1651
+ var run = (command) => (controller) => void controller.run(command);
1652
+ var targetOr = (command) => (controller, event) => {
1653
+ const range = targetBeyondSelection(controller, event);
1654
+ if (range === null) controller.run(command);
1655
+ else deleteRange(controller, range);
1656
+ };
1657
+ var leaveToHandlers = () => void 0;
1658
+ function insertTypedText(controller, event) {
1659
+ const text = event.data ?? event.dataTransfer?.getData("text/plain");
1660
+ if (!text) return;
1661
+ const range = targetBeyondSelection(controller, event);
1662
+ if (range === null) controller.edit(() => controller.editor.insertText(text));
1663
+ else controller.edit(() => replaceRange(controller.editor, range, text, false));
1664
+ }
1665
+ function deleteContentBackward(controller, event) {
1666
+ if (!controller.run(undoInputRule)) targetOr(deleteBackward)(controller, event);
1667
+ }
1668
+ function insertReplacementText(controller, event) {
1669
+ const text = event.dataTransfer?.getData("text/plain") || event.data;
1670
+ if (!text) return;
1671
+ const range = targetRangeToModel(controller, event);
1672
+ if (range === null) controller.run(insertText(text, { origin: "input" }));
1673
+ else controller.edit(() => replaceRange(controller.editor, range, text, false));
1674
+ }
1675
+ var actions = {
1676
+ insertText: insertTypedText,
1677
+ insertReplacementText,
1678
+ insertParagraph: (controller) => void controller.edit(() => controller.editor.handleKey(ENTER)),
1679
+ insertLineBreak: run(insertHardBreak),
1680
+ deleteContentBackward,
1681
+ deleteContentForward: targetOr(deleteForward),
1682
+ deleteWordBackward: targetOr(deleteWordBackward),
1683
+ deleteWordForward: targetOr(deleteWordForward),
1684
+ deleteSoftLineBackward: targetOr(deleteToLineStart),
1685
+ deleteHardLineBackward: targetOr(deleteToLineStart),
1686
+ deleteSoftLineForward: targetOr(deleteToLineEnd),
1687
+ deleteHardLineForward: targetOr(deleteToLineEnd),
1688
+ formatBold: run(toggleMark("bold")),
1689
+ formatItalic: run(toggleMark("italic")),
1690
+ formatUnderline: run(toggleMark("underline")),
1691
+ formatStrikeThrough: run(toggleMark("strike")),
1692
+ historyUndo: run(undo),
1693
+ historyRedo: run(redo),
1694
+ insertTranspose: run(transposeChars),
1695
+ insertFromPaste: leaveToHandlers,
1696
+ insertFromDrop: leaveToHandlers,
1697
+ deleteByCut: leaveToHandlers,
1698
+ deleteByDrag: leaveToHandlers
1699
+ };
1700
+ var isComposingInput = (controller, event) => event.isComposing || controller.composingKey !== null;
1701
+ var TYPED_INPUT = /* @__PURE__ */ new Set(["insertText", "insertReplacementText"]);
1702
+ function handleBeforeInput(controller, event) {
1703
+ const { inputType } = event;
1704
+ if (isComposingInput(controller, event)) return;
1705
+ if (!event.cancelable || !Object.hasOwn(actions, inputType)) return;
1706
+ event.preventDefault();
1707
+ if (TYPED_INPUT.has(inputType) && wholeBlocks(controller.editor.state.selection)) return;
1708
+ actions[inputType](controller, event);
1709
+ }
1710
+
1711
+ // src/view/dom/read-dom-text.ts
1712
+ var ELEMENT_NODE = 1;
1713
+ var TEXT_NODE2 = 3;
1714
+ var OBJECT_REPLACEMENT = "\uFFFC";
1715
+ var BLOCKS = /* @__PURE__ */ new Set(["div", "p"]);
1716
+ function kindOf(element) {
1717
+ if (element.hasAttribute("data-quoin-atom")) return "atom";
1718
+ if (element.hasAttribute("data-quoin-widget")) return "widget";
1719
+ if (element.localName === "br") {
1720
+ if (element.hasAttribute("data-quoin-break")) return "break";
1721
+ return element.hasAttribute("data-quoin-filler") ? "filler" : "br";
1722
+ }
1723
+ return BLOCKS.has(element.localName) ? "block" : "inline";
1724
+ }
1725
+ function collect(parent, out) {
1726
+ for (let node = parent.firstChild; node; node = node.nextSibling) {
1727
+ if (node.nodeType === TEXT_NODE2) {
1728
+ const data = node.data;
1729
+ if (data) out.push({ kind: "text", data });
1730
+ } else if (node.nodeType === ELEMENT_NODE) {
1731
+ const kind = kindOf(node);
1732
+ if (kind !== "widget" && kind !== "filler" && kind !== "inline") out.push({ kind });
1733
+ if (kind === "block" || kind === "inline") collect(node, out);
1734
+ }
1735
+ }
1736
+ }
1737
+ function readDomText(host) {
1738
+ const tokens = [];
1739
+ collect(host, tokens);
1740
+ let last = tokens.length - 1;
1741
+ while (last >= 0 && tokens[last].kind === "block") last--;
1742
+ let result = "";
1743
+ let atLineStart = true;
1744
+ tokens.forEach((token, index) => {
1745
+ switch (token.kind) {
1746
+ case "text":
1747
+ result += token.data;
1748
+ atLineStart = false;
1749
+ break;
1750
+ case "atom":
1751
+ result += OBJECT_REPLACEMENT;
1752
+ atLineStart = false;
1753
+ break;
1754
+ case "break":
1755
+ case "br":
1756
+ if (token.kind === "break" || index < last) {
1757
+ result += "\n";
1758
+ atLineStart = true;
1759
+ }
1760
+ break;
1761
+ case "block":
1762
+ if (!atLineStart) {
1763
+ result += "\n";
1764
+ atLineStart = true;
1765
+ }
1766
+ break;
1767
+ }
1768
+ });
1769
+ return result;
1770
+ }
1771
+
1772
+ // src/view/input/commit-empty-host.ts
1773
+ function commitEmptyHost(controller) {
1774
+ const host = controller.emptyHost;
1775
+ if (host === null) return;
1776
+ const text = readDomText(host);
1777
+ if (text !== "" && controller.edit(() => controller.editor.insertText(text))) controller.focus();
1778
+ else renderInline(host, [], []);
1779
+ }
1780
+
1781
+ // src/view/input/composition-snapshot.ts
1782
+ function composedEnd(controller, block, region) {
1783
+ const { selection } = controller.editor.state;
1784
+ if (selection?.kind !== "text") return void 0;
1785
+ const ends = [selection.anchor, selection.head];
1786
+ if (ends.some((pos) => pos.block !== block || pos.region !== region)) return void 0;
1787
+ return Math.max(...ends.map((pos) => pos.offset));
1788
+ }
1789
+ function snapshotComposition(controller, block, region) {
1790
+ const { editor } = controller;
1791
+ const element = controller.registry.get(block, region);
1792
+ const inline = regionInline(editor.state, block, region);
1793
+ if (element === void 0 || inline === void 0) return null;
1794
+ const text = inlineText(inline);
1795
+ const end = composedEnd(controller, block, region);
1796
+ const since = [];
1797
+ const release = editor.subscribe((_, tr) => void (tr && since.push(tr)));
1798
+ const mapped = (offset, assoc) => since.reduce((pos, tr) => tr.mapPos(pos, assoc), { block, region, offset });
1799
+ return {
1800
+ settle() {
1801
+ release();
1802
+ if (controller.registry.get(block, region) !== element) return false;
1803
+ const after = readDomText(element);
1804
+ const edit = textDiff(text, after, end === void 0 ? void 0 : end + after.length - text.length);
1805
+ if (edit === null) return false;
1806
+ const from = mapped(edit.from, edit.from === edit.to ? -1 : 1);
1807
+ const to = mapped(edit.to, -1);
1808
+ const spans = to.block === from.block && to.region === from.region && to.offset > from.offset;
1809
+ const range = { from, to: spans ? to : from };
1810
+ controller.edit(() => {
1811
+ const applied = replaceRange(editor, range, edit.insert, true);
1812
+ controller.renderHost(block, region);
1813
+ return applied;
1814
+ });
1815
+ return true;
1816
+ },
1817
+ release
1818
+ };
1819
+ }
1820
+
1821
+ // src/view/input/composition.ts
1822
+ var EMPTY_HOST = "empty";
1823
+ function targetAt(controller, node) {
1824
+ const entry = controller.registry.locate(node);
1825
+ if (entry !== null) return { block: entry.block, region: entry.region };
1826
+ return node !== null && controller.emptyHost?.contains(node) ? EMPTY_HOST : null;
1827
+ }
1828
+ function elementOf(controller, target) {
1829
+ if (target === EMPTY_HOST) return controller.emptyHost ?? void 0;
1830
+ return target ? controller.registry.get(target.block, target.region) : void 0;
1831
+ }
1832
+ function createCompositionHandlers(controller) {
1833
+ let active = false;
1834
+ let target = null;
1835
+ let snapshot = null;
1836
+ let stopWaiting = null;
1837
+ function settle() {
1838
+ stopWaiting?.();
1839
+ stopWaiting = null;
1840
+ active = false;
1841
+ controller.composingKey = null;
1842
+ const host = target;
1843
+ const started = snapshot;
1844
+ target = null;
1845
+ snapshot = null;
1846
+ if (host === EMPTY_HOST) commitEmptyHost(controller);
1847
+ else if (host !== null && !started?.settle()) {
1848
+ controller.renderHost(host.block, host.region);
1849
+ controller.syncSelectionToDom();
1850
+ }
1851
+ }
1852
+ function waitForInput(element) {
1853
+ const onInput = () => settle();
1854
+ const timer = setTimeout(settle, 0);
1855
+ element?.addEventListener("input", onInput);
1856
+ stopWaiting = () => {
1857
+ clearTimeout(timer);
1858
+ element?.removeEventListener("input", onInput);
1859
+ };
1860
+ }
1861
+ return {
1862
+ start() {
1863
+ if (stopWaiting !== null) settle();
1864
+ snapshot?.release();
1865
+ target = targetAt(controller, controller.domSelection()?.anchorNode ?? null);
1866
+ const host = target === EMPTY_HOST ? null : target;
1867
+ snapshot = host && snapshotComposition(controller, host.block, host.region);
1868
+ active = true;
1869
+ controller.composingKey = target === null || target === EMPTY_HOST ? target : hostKey(target.block, target.region);
1870
+ },
1871
+ end() {
1872
+ if (!active || stopWaiting !== null) return;
1873
+ waitForInput(elementOf(controller, target));
1874
+ },
1875
+ release() {
1876
+ if (stopWaiting !== null) settle();
1877
+ snapshot?.release();
1878
+ snapshot = null;
1879
+ target = null;
1880
+ if (active) controller.composingKey = null;
1881
+ active = false;
1882
+ },
1883
+ get active() {
1884
+ return active;
1885
+ }
1886
+ };
1887
+ }
1888
+
1889
+ // src/view/input/reconcile-host.ts
1890
+ function domCaretOffset(host) {
1891
+ const doc = host.ownerDocument;
1892
+ const selection = doc.getSelection();
1893
+ const node = selection?.focusNode;
1894
+ if (!selection || !node || !host.contains(node)) return void 0;
1895
+ const range = doc.createRange();
1896
+ range.setStart(host, 0);
1897
+ range.setEnd(node, selection.focusOffset);
1898
+ const before = doc.createElement("div");
1899
+ before.append(range.cloneContents());
1900
+ return readDomText(before).length;
1901
+ }
1902
+ function reconcileHost(controller, block, region, caretHint) {
1903
+ const element = controller.registry.get(block, region);
1904
+ const inline = regionInline(controller.editor.state, block, region);
1905
+ if (element === void 0 || inline === void 0) return false;
1906
+ const before = inlineText(inline);
1907
+ const after = readDomText(element);
1908
+ const edit = before === after ? null : textDiff(before, after, caretHint ?? domCaretOffset(element));
1909
+ if (edit === null) return false;
1910
+ const at = (offset) => ({ block, region, offset });
1911
+ controller.edit(() => {
1912
+ const applied = replaceRange(controller.editor, { from: at(edit.from), to: at(edit.to) }, edit.insert, true);
1913
+ controller.renderHost(block, region);
1914
+ return applied;
1915
+ });
1916
+ return true;
1917
+ }
1918
+
1919
+ // src/view/input/dom-input.ts
1920
+ var OBSERVED = { characterData: true, childList: true, subtree: true };
1921
+ function createDomInputFallback(controller) {
1922
+ const pending = /* @__PURE__ */ new Map();
1923
+ const observers = /* @__PURE__ */ new Map();
1924
+ let queued = false;
1925
+ function enqueue(node) {
1926
+ const entry = controller.registry.locate(node);
1927
+ if (entry === null || controller.composingKey !== null || controller.readOnly) return false;
1928
+ pending.set(hostKey(entry.block, entry.region), entry);
1929
+ return true;
1930
+ }
1931
+ function flush() {
1932
+ queued = false;
1933
+ for (const [host, observer] of observers) if (observer.takeRecords().length > 0) enqueue(host);
1934
+ for (const [key, { block, region }] of [...pending]) {
1935
+ pending.delete(key);
1936
+ if (controller.composingKey !== key) reconcileHost(controller, block, region);
1937
+ }
1938
+ }
1939
+ function schedule(node) {
1940
+ if (!enqueue(node) || queued) return;
1941
+ queued = true;
1942
+ queueMicrotask(() => {
1943
+ if (queued) flush();
1944
+ });
1945
+ }
1946
+ return {
1947
+ onInput(event) {
1948
+ const { target } = event;
1949
+ if (event.isComposing || event.defaultPrevented) return;
1950
+ if (target === null || target !== controller.emptyHost) schedule(target);
1951
+ else if (controller.composingKey === null && !controller.readOnly) commitEmptyHost(controller);
1952
+ },
1953
+ observe(host) {
1954
+ const observer = new MutationObserver(() => schedule(host));
1955
+ observer.observe(host, OBSERVED);
1956
+ observers.set(host, observer);
1957
+ return () => {
1958
+ observer.disconnect();
1959
+ if (observers.get(host) === observer) observers.delete(host);
1960
+ };
1961
+ },
1962
+ drain(host) {
1963
+ observers.get(host)?.takeRecords();
1964
+ },
1965
+ flush
1966
+ };
1967
+ }
1968
+
1969
+ // src/view/navigation/cross-moves.ts
1970
+ function moveHead(controller, { anchor }, head) {
1971
+ dispatchSelection(controller, textSelection(anchor, head));
1972
+ return true;
1973
+ }
1974
+ function blockOf(controller, { block }) {
1975
+ const { doc } = controller.editor.state;
1976
+ return doc.blocks[requireBlockIndex(doc, block)];
1977
+ }
1978
+ function collapseCross(controller, selection, direction) {
1979
+ const { from, to } = selectionRange(controller.editor.state.doc, selection);
1980
+ dispatchSelection(controller, textSelection(direction < 0 ? from : to));
1981
+ return true;
1982
+ }
1983
+ function crossHeadVertically(controller, selection, direction) {
1984
+ const { head } = selection;
1985
+ const edge = neighbourHead(controller, head.block, direction);
1986
+ if (edge !== null) {
1987
+ const end2 = regionLength(blockOf(controller, edge), edge.region);
1988
+ return moveHead(controller, selection, { ...edge, offset: Math.min(head.offset, end2) });
1989
+ }
1990
+ const end = regionLength(blockOf(controller, head), head.region);
1991
+ return moveHead(controller, selection, { ...head, offset: direction < 0 ? 0 : end });
1992
+ }
1993
+ function crossHeadHorizontally(controller, selection, direction) {
1994
+ const { head } = selection;
1995
+ const text = inlineText(blockOf(controller, head).regions[head.region] ?? []);
1996
+ if (direction < 0 ? head.offset > 0 : head.offset < text.length) {
1997
+ const offset = direction < 0 ? prevGraphemeOffset(text, head.offset) : nextGraphemeOffset(text, head.offset);
1998
+ return moveHead(controller, selection, { ...head, offset });
1999
+ }
2000
+ const edge = neighbourHead(controller, head.block, direction);
2001
+ return edge === null || moveHead(controller, selection, edge);
2002
+ }
2003
+
2004
+ // src/view/navigation/horizontal.ts
2005
+ function moveHorizontally(controller, direction, extend) {
2006
+ controller.goalX = null;
2007
+ const { editor } = controller;
2008
+ const { state } = editor;
2009
+ const { selection, doc } = state;
2010
+ if (selection?.kind === "node") return !extend && moveToNeighbour(controller, selection.block, direction);
2011
+ if (selection?.kind !== "text") return false;
2012
+ if (isCrossSelection(selection)) {
2013
+ const move = extend ? crossHeadHorizontally : collapseCross;
2014
+ return move(controller, selection, direction);
2015
+ }
2016
+ if (!extend && !isCollapsed(selection)) return false;
2017
+ const { head } = selection;
2018
+ const length = regionLength(doc.blocks[requireBlockIndex(doc, head.block)], head.region);
2019
+ if (direction < 0 ? head.offset > 0 : head.offset < length) return false;
2020
+ const own2 = extend ? null : ownNode(controller, head.block, direction);
2021
+ if (own2 !== null) {
2022
+ dispatchSelection(controller, own2);
2023
+ return true;
2024
+ }
2025
+ if (!extend) return moveToNeighbour(controller, head.block, direction);
2026
+ const edge = neighbourHead(controller, head.block, direction);
2027
+ if (edge === null) return false;
2028
+ dispatchSelection(controller, textSelection(selection.anchor, edge));
2029
+ return true;
2030
+ }
2031
+
2032
+ // src/view/navigation/line-edges.ts
2033
+ var TEXT_NODE3 = 3;
2034
+ var ELEMENT_NODE2 = 1;
2035
+ var hasSize = (rect) => rect !== void 0 && (rect.width > 0 || rect.height > 0);
2036
+ var edgeOf = (rect, right) => new DOMRect(right ? rect.right : rect.left, rect.top, 0, rect.height);
2037
+ function textCaretRect(text, offset) {
2038
+ const range = text.ownerDocument.createRange();
2039
+ range.setStart(text, offset);
2040
+ const rect = range.getClientRects()[0];
2041
+ return hasSize(rect) ? rect : null;
2042
+ }
2043
+ function besideNode(node, before) {
2044
+ if (node.nodeType === TEXT_NODE3) return textCaretRect(node, before ? 0 : node.length);
2045
+ if (node.nodeType !== ELEMENT_NODE2) return null;
2046
+ const rects = node.getClientRects();
2047
+ const rect = before ? rects[0] : rects[rects.length - 1];
2048
+ return hasSize(rect) ? edgeOf(rect, !before) : null;
2049
+ }
2050
+ function caretClientRect(point) {
2051
+ const { node, offset } = point;
2052
+ if (node.nodeType === TEXT_NODE3) return textCaretRect(node, offset);
2053
+ const after = node.childNodes[offset];
2054
+ const before = node.childNodes[offset - 1];
2055
+ return (after && besideNode(after, true)) ?? (before && besideNode(before, false)) ?? null;
2056
+ }
2057
+ function contentBox(host) {
2058
+ const box = host.getBoundingClientRect();
2059
+ const style = host.ownerDocument.defaultView.getComputedStyle(host);
2060
+ const left = box.left + host.clientLeft + parseFloat(style.paddingLeft);
2061
+ const top = box.top + host.clientTop + parseFloat(style.paddingTop);
2062
+ const width = host.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
2063
+ const height = host.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);
2064
+ return new DOMRect(left, top, width, height);
2065
+ }
2066
+ function lineEdgeRect(host, last) {
2067
+ const map = domMapOf(host);
2068
+ if (!map || map.length === 0) return contentBox(host);
2069
+ return caretClientRect(map.toDom(last ? map.length : 0, last ? -1 : 1));
2070
+ }
2071
+ function onEdgeLine(host, point, last) {
2072
+ if (domMapOf(host)?.length === 0) return true;
2073
+ const caret = caretClientRect(point);
2074
+ const edge = lineEdgeRect(host, last);
2075
+ if (!caret || !edge) return false;
2076
+ return Math.abs(caret.top - edge.top) < Math.max(caret.height, edge.height) / 2;
2077
+ }
2078
+ function isOnFirstLine(host, point) {
2079
+ return onEdgeLine(host, point, false);
2080
+ }
2081
+ function isOnLastLine(host, point) {
2082
+ return onEdgeLine(host, point, true);
2083
+ }
2084
+ function adjacentLineMiddle(host, caret, direction) {
2085
+ const range = host.ownerDocument.createRange();
2086
+ range.selectNodeContents(host);
2087
+ let nearest2 = null;
2088
+ for (const rect of Array.from(range.getClientRects())) {
2089
+ const middle = rect.top + rect.height / 2;
2090
+ const beyond = direction > 0 ? middle > caret.bottom : middle < caret.top;
2091
+ const closer = nearest2 === null || (direction > 0 ? middle < nearest2 : middle > nearest2);
2092
+ if (rect.height > 0 && beyond && closer) nearest2 = middle;
2093
+ }
2094
+ return nearest2;
2095
+ }
2096
+
2097
+ // src/view/navigation/stay-on-line.ts
2098
+ var onLine = (rect, y) => rect === null || rect.top <= y && y <= rect.bottom;
2099
+ function stayOnLine(pos, y, rectAt) {
2100
+ if (pos.offset === 0 || onLine(rectAt(pos), y)) return pos;
2101
+ const before = { ...pos, offset: pos.offset - 1 };
2102
+ return onLine(rectAt(before), y) ? before : pos;
2103
+ }
2104
+
2105
+ // src/view/navigation/vertical.ts
2106
+ function lineInView(host, last) {
2107
+ const line = lineEdgeRect(host, last);
2108
+ const view = host.ownerDocument.defaultView;
2109
+ if (!line || !view || line.top >= 0 && line.bottom <= view.innerHeight) return line;
2110
+ host.scrollIntoView({ block: "nearest" });
2111
+ return lineEdgeRect(host, last);
2112
+ }
2113
+ function caretAtGoal(controller, host, at, y) {
2114
+ const { goalX } = controller;
2115
+ const view = host.ownerDocument.defaultView;
2116
+ if (goalX === null || !view || y < 0 || y > view.innerHeight) return null;
2117
+ const pos = caretInHost(controller.registry, host, host.getBoundingClientRect(), at, goalX, y);
2118
+ if (!pos) return null;
2119
+ return stayOnLine(pos, y, (p) => {
2120
+ const dom = posToDom(controller.registry, p);
2121
+ return dom && caretClientRect(dom);
2122
+ });
2123
+ }
2124
+ function landAtGoal(controller, block, direction) {
2125
+ const { schema } = controller.editor.state;
2126
+ const region = isTextFlow(schema, block) ? primaryRegion(schema, block) : null;
2127
+ const host = region === null ? void 0 : controller.registry.get(block.id, region);
2128
+ if (!host || region === null || controller.goalX === null) return null;
2129
+ const line = lineInView(host, direction < 0);
2130
+ const pos = line && caretAtGoal(controller, host, { block: block.id, region }, line.top + line.height / 2);
2131
+ return pos ? textSelection(pos) : null;
2132
+ }
2133
+ function moveInHost(controller, host, head, caret, direction) {
2134
+ const { goalX } = controller;
2135
+ if (goalX === null || !caret || Math.abs(caret.left - goalX) < caret.height / 2) return null;
2136
+ const y = adjacentLineMiddle(host, caret, direction);
2137
+ const pos = y === null ? null : caretAtGoal(controller, host, head, y);
2138
+ return pos && pos.offset !== head.offset ? pos : null;
2139
+ }
2140
+ function landing(controller, { head }, direction, extend) {
2141
+ const { registry } = controller;
2142
+ const host = registry.get(head.block, head.region);
2143
+ const point = posToDom(registry, head);
2144
+ if (!host || !point) return null;
2145
+ const caret = caretClientRect(point);
2146
+ controller.goalX ??= caret?.left ?? null;
2147
+ if (!(direction < 0 ? isOnFirstLine(host, point) : isOnLastLine(host, point))) {
2148
+ const pos = moveInHost(controller, host, head, caret, direction);
2149
+ return pos && textSelection(pos);
2150
+ }
2151
+ const own2 = extend ? null : ownNode(controller, head.block, direction);
2152
+ if (own2 !== null) return own2;
2153
+ const accept = extend ? holdsTextHead(controller) : void 0;
2154
+ const target = neighbourBlock(controller.editor.state.doc, head.block, direction, accept);
2155
+ return target && (landAtGoal(controller, target, direction) ?? entryEdge(controller, target.id, direction));
2156
+ }
2157
+ function moveText(controller, selection, direction, extend) {
2158
+ const next = landing(controller, selection, direction, extend);
2159
+ if (next === null) {
2160
+ controller.nativeVerticalMove = true;
2161
+ return false;
2162
+ }
2163
+ const moved = extend && next.kind === "text" ? textSelection(selection.anchor, next.head) : next;
2164
+ dispatchSelection(controller, moved);
2165
+ return true;
2166
+ }
2167
+ function extendBlocks(controller, { anchor, head }, direction) {
2168
+ const next = neighbourBlock(controller.editor.state.doc, head, direction);
2169
+ if (next === null) return false;
2170
+ dispatchSelection(controller, blockRangeSelection(anchor, next.id));
2171
+ return true;
2172
+ }
2173
+ function moveVertically(controller, direction, extend) {
2174
+ const { selection } = controller.editor.state;
2175
+ if (selection?.kind === "node") return !extend && moveToNeighbour(controller, selection.block, direction);
2176
+ if (selection?.kind === "blocks") return extend && extendBlocks(controller, selection, direction);
2177
+ if (selection?.kind !== "text") return false;
2178
+ if (!isCrossSelection(selection)) return moveText(controller, selection, direction, extend);
2179
+ controller.goalX = null;
2180
+ return (extend ? crossHeadVertically : collapseCross)(controller, selection, direction);
2181
+ }
2182
+
2183
+ // src/view/input/flip-block.ts
2184
+ var isTask = ({ type, attrs }) => type === "MARKDOWN" && attrs.kind === "task";
2185
+ function flipCommand(block) {
2186
+ if (isTask(block)) return toggleTask;
2187
+ return block.type === "TOGGLE" ? setAttrs({ open: block.attrs.open !== true }) : null;
2188
+ }
2189
+ var toggleState = (open) => open ? "expanded" : "collapsed";
2190
+ function stateName(block) {
2191
+ if (isTask(block)) return block.attrs.checked === true ? "checked" : "unchecked";
2192
+ return toggleState(block.attrs.open === true);
2193
+ }
2194
+ function blockHoldingCaret({ doc, selection }) {
2195
+ if (selection?.kind !== "text" || selection.anchor.block !== selection.head.block) return null;
2196
+ return blockById(doc, selection.head.block) ?? null;
2197
+ }
2198
+ function flipForReader(controller, block) {
2199
+ if (block?.type !== "TOGGLE") return false;
2200
+ const toggles = readerToggles(controller);
2201
+ toggles.flip(block);
2202
+ controller.announce(toggleState(shownOpen(block, toggles.choices)));
2203
+ return true;
2204
+ }
2205
+ function flipSelectedBlock(controller) {
2206
+ const { editor } = controller;
2207
+ const block = blockHoldingCaret(editor.state);
2208
+ if (controller.readOnly) return flipForReader(controller, block);
2209
+ const command = block && flipCommand(block);
2210
+ if (!block || !command || !controller.run(command)) return false;
2211
+ const after = blockById(editor.state.doc, block.id);
2212
+ if (after) controller.announce(stateName(after));
2213
+ return true;
2214
+ }
2215
+
2216
+ // src/view/input/keydown.ts
2217
+ var ARROWS = {
2218
+ ArrowUp: [moveVertically, -1],
2219
+ ArrowDown: [moveVertically, 1],
2220
+ ArrowLeft: [moveHorizontally, -1],
2221
+ ArrowRight: [moveHorizontally, 1]
2222
+ };
2223
+ function isComposingKey(controller, event) {
2224
+ return event.isComposing || event.keyCode === 229 || controller.composingKey !== null;
2225
+ }
2226
+ var runKeymap = (controller, event) => controller.edit(() => controller.editor.handleKey(event));
2227
+ function arrow(controller, event) {
2228
+ if (!Object.hasOwn(ARROWS, event.key) || event.ctrlKey || event.metaKey) return false;
2229
+ const [move, direction] = ARROWS[event.key];
2230
+ return move(controller, direction, event.shiftKey);
2231
+ }
2232
+ function tab(controller, event) {
2233
+ const { selection } = controller.editor.state;
2234
+ if (selection === null || selection.kind === "blocks") return false;
2235
+ runKeymap(controller, event);
2236
+ return true;
2237
+ }
2238
+ function typeOverNode(controller, event) {
2239
+ const { key } = event;
2240
+ const node = controller.editor.state.selection?.kind === "node";
2241
+ if (!node || event.ctrlKey || event.metaKey || [...key].length !== 1) return false;
2242
+ return controller.edit(() => controller.editor.insertText(key));
2243
+ }
2244
+ function holdHistoryKeys(controller, event) {
2245
+ const kind = controller.editor.state.selection?.kind;
2246
+ if (kind !== "node" && kind !== "blocks") return false;
2247
+ return Object.hasOwn(ARROWS, event.key) && (event.metaKey || event.ctrlKey || event.altKey);
2248
+ }
2249
+ function flip(controller, event) {
2250
+ const mod = controller.isMac ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
2251
+ if (event.key !== "Enter" || !mod || event.shiftKey || event.altKey) return false;
2252
+ flipSelectedBlock(controller);
2253
+ return true;
2254
+ }
2255
+ function handle(controller, event) {
2256
+ if (arrow(controller, event)) return true;
2257
+ if (event.key === "Tab" && !event.ctrlKey && !event.metaKey && !event.altKey) return tab(controller, event);
2258
+ return runKeymap(controller, event) || flip(controller, event) || typeOverNode(controller, event) || holdHistoryKeys(controller, event);
2259
+ }
2260
+ function handleReaderKeydown(controller, event) {
2261
+ if (flip(controller, event)) event.preventDefault();
2262
+ }
2263
+ function handleKeydown(controller, event) {
2264
+ if (isComposingKey(controller, event)) return;
2265
+ controller.nativeVerticalMove = false;
2266
+ for (const intercept of controller.keyInterceptors) if (intercept(event)) return;
2267
+ if (handle(controller, event)) event.preventDefault();
2268
+ }
2269
+
2270
+ // src/view/input/attach-input.ts
2271
+ function attachInput(controller, root) {
2272
+ const composition = createCompositionHandlers(controller);
2273
+ const sinkComposition = createSinkComposition(controller);
2274
+ const fallback = createDomInputFallback(controller);
2275
+ const observed = /* @__PURE__ */ new Map();
2276
+ const inHost = (event) => controller.registry.locate(event.target) !== null;
2277
+ const inSink = (event) => controller.sink !== null && event.target === controller.sink;
2278
+ const inEmptyHost = (event) => controller.emptyHost !== null && event.target === controller.emptyHost;
2279
+ const catchUp = () => {
2280
+ fallback.flush();
2281
+ controller.readSelectionFromDom();
2282
+ };
2283
+ const onBeforeInput = (event) => {
2284
+ if (isComposingInput(controller, event) || controller.readOnly) return;
2285
+ if (!inHost(event) && !inSink(event) && !inEmptyHost(event)) return;
2286
+ catchUp();
2287
+ handleBeforeInput(controller, event);
2288
+ };
2289
+ const onKeyDown = (event) => {
2290
+ if (isComposingKey(controller, event) || !controller.isEditorElement(event.target)) return;
2291
+ catchUp();
2292
+ if (controller.readOnly) handleReaderKeydown(controller, event);
2293
+ else handleKeydown(controller, event);
2294
+ };
2295
+ const onInput = (event) => fallback.onInput(event);
2296
+ const onCompositionStart = (event) => {
2297
+ if (inHost(event) || inEmptyHost(event)) composition.start(event);
2298
+ else if (inSink(event)) sinkComposition.start();
2299
+ };
2300
+ const onCompositionEnd = (event) => {
2301
+ if (inSink(event)) sinkComposition.end();
2302
+ else composition.end(event);
2303
+ };
2304
+ const detachClipboard = attachClipboard(controller, root, catchUp);
2305
+ const unlisten = listen(root, {
2306
+ keydown: onKeyDown,
2307
+ beforeinput: onBeforeInput,
2308
+ input: onInput,
2309
+ compositionstart: onCompositionStart,
2310
+ compositionend: onCompositionEnd
2311
+ });
2312
+ function observe(host) {
2313
+ if (!observed.has(host)) observed.set(host, fallback.observe(host));
2314
+ }
2315
+ for (const { element } of controller.registry.all()) observe(element);
2316
+ return {
2317
+ observe,
2318
+ unobserve(host) {
2319
+ observed.get(host)?.();
2320
+ observed.delete(host);
2321
+ },
2322
+ drain: fallback.drain,
2323
+ detach() {
2324
+ composition.release();
2325
+ sinkComposition.release();
2326
+ detachClipboard();
2327
+ unlisten();
2328
+ for (const stop of observed.values()) stop();
2329
+ observed.clear();
2330
+ }
2331
+ };
2332
+ }
2333
+
2334
+ // src/view/controller/focus-return.ts
2335
+ function watchFocusReturn(root, handlers) {
2336
+ let pressed = false;
2337
+ const onPointerDown = () => {
2338
+ pressed = true;
2339
+ handlers.pressed();
2340
+ };
2341
+ const onFocusIn = (event) => {
2342
+ const byPress = pressed;
2343
+ pressed = false;
2344
+ if (!byPress) handlers.returned(event.target);
2345
+ };
2346
+ const onFocusOut = (event) => {
2347
+ if (!root.contains(event.relatedTarget)) pressed = false;
2348
+ };
2349
+ const unlistenPress = listen(root, { pointerdown: onPointerDown }, true);
2350
+ const unlistenFocus = listen(root, { focusin: onFocusIn, focusout: onFocusOut });
2351
+ return () => {
2352
+ unlistenPress();
2353
+ unlistenFocus();
2354
+ };
2355
+ }
2356
+
2357
+ // src/view/controller/selection-sync.ts
2358
+ var selectionPoints = (selection) => [
2359
+ selection?.anchorNode ?? null,
2360
+ selection?.anchorOffset ?? 0,
2361
+ selection?.focusNode ?? null,
2362
+ selection?.focusOffset ?? 0
2363
+ ];
2364
+ var samePoints = (a, b) => a.every((point, index) => point === b[index]);
2365
+ function createSelectionSync(doc, onChange) {
2366
+ let written = null;
2367
+ let expiry = null;
2368
+ function release() {
2369
+ written = null;
2370
+ if (expiry !== null) clearTimeout(expiry);
2371
+ expiry = null;
2372
+ }
2373
+ function handle2() {
2374
+ const suppressed = written;
2375
+ release();
2376
+ if (suppressed === null || !samePoints(suppressed, selectionPoints(doc.getSelection()))) onChange();
2377
+ }
2378
+ return {
2379
+ start: () => doc.addEventListener("selectionchange", handle2),
2380
+ stop() {
2381
+ doc.removeEventListener("selectionchange", handle2);
2382
+ release();
2383
+ },
2384
+ suppressNext() {
2385
+ written = selectionPoints(doc.getSelection());
2386
+ expiry ??= setTimeout(release, 0);
2387
+ }
2388
+ };
2389
+ }
2390
+
2391
+ // src/view/controller/root-binding.ts
2392
+ function bindRoot(controller, root, cross, committed) {
2393
+ const selectionSync = createSelectionSync(root.ownerDocument, () => controller.readSelectionFromDom());
2394
+ const input = attachInput(controller, root);
2395
+ const unsubscribe = controller.editor.subscribe(committed);
2396
+ const detachCross = cross.attach(root);
2397
+ const stopFocusReturn = watchFocusReturn(root, {
2398
+ pressed: () => controller.nativeVerticalMove = false,
2399
+ returned: (target) => {
2400
+ if (controller.isEditorElement(target)) controller.syncSelectionToDom();
2401
+ }
2402
+ });
2403
+ selectionSync.start();
2404
+ return {
2405
+ root,
2406
+ input,
2407
+ selectionSync,
2408
+ detach() {
2409
+ selectionSync.stop();
2410
+ input.detach();
2411
+ unsubscribe();
2412
+ detachCross();
2413
+ stopFocusReturn();
2414
+ }
2415
+ };
2416
+ }
2417
+
2418
+ // src/view/controller/selection-io.ts
2419
+ function movedSelection(controller, doc, model) {
2420
+ if (controller.composingKey !== null || controller.dragging || !doc || !ownsFocus(controller, doc)) return null;
2421
+ const selection = readSelection(controller.registry, doc.getSelection(), (node) => controller.blockAt(node));
2422
+ return selection === null || sameSelection(selection, model) ? null : selection;
2423
+ }
2424
+ function writeModelSelection(controller, doc, moved, local) {
2425
+ const domSelection = doc?.getSelection();
2426
+ if (focusingSink(controller) || controller.composingKey !== null || !doc || !domSelection || !ownsFocus(controller, doc)) {
2427
+ return;
2428
+ }
2429
+ const before = selectionPoints(domSelection);
2430
+ const { selection } = controller.editor.state;
2431
+ if (heldBySink(selection) && controller.sink !== null) focusSink(controller);
2432
+ else if (selection?.kind === "blocks") domSelection.removeAllRanges();
2433
+ else writeSelection(controller.registry, selection, domSelection, (id) => controller.blockElement(id));
2434
+ controller.nodeReveal.show(selection, local);
2435
+ if (!samePoints(before, selectionPoints(domSelection))) moved();
2436
+ }
2437
+
2438
+ // src/view/controller/view-controller.ts
2439
+ var runNow = (fn) => fn();
2440
+ var ViewController = class {
2441
+ editor;
2442
+ registry = createHostRegistry((element) => {
2443
+ this.#binding?.input.observe(element);
2444
+ this.#drawn.syncTabStop();
2445
+ return () => {
2446
+ this.#binding?.input.unobserve(element);
2447
+ this.#drawn.syncTabStop();
2448
+ };
2449
+ });
2450
+ keyInterceptors = /* @__PURE__ */ new Set();
2451
+ nodeReveal = createNodeReveal((id) => this.blockElement(id));
2452
+ announcer = createAnnouncer(() => this.#said);
2453
+ isMac;
2454
+ onFiles;
2455
+ goalX = null;
2456
+ nativeVerticalMove = false;
2457
+ dragging = false;
2458
+ composingKey = null;
2459
+ #flush;
2460
+ #hosts;
2461
+ #drawn = createDrawnElements(this.registry, () => this.editor.state);
2462
+ #cross = createCrossMode(this);
2463
+ #outside = createOutsideChanges(
2464
+ () => hasFocus(this, this.#ownerDocument()),
2465
+ (model) => movedSelection(this, this.#ownerDocument(), model)
2466
+ );
2467
+ #binding = null;
2468
+ #quiet = false;
2469
+ #loud = false;
2470
+ #said;
2471
+ #detachRoot = null;
2472
+ #readOnlyState;
2473
+ constructor(options) {
2474
+ this.editor = options.editor;
2475
+ options.editor.subscribe((state, tr) => {
2476
+ if (tr?.origin !== "remote") this.#said = state;
2477
+ });
2478
+ this.#flush = options.flush ?? runNow;
2479
+ this.onFiles = options.onFiles;
2480
+ this.isMac = options.isMac ?? detectMac();
2481
+ const decorationsOf = (block, region) => this.decorationsFor(block, region);
2482
+ const drain = (element) => this.#binding?.input.drain(element);
2483
+ this.#hosts = createHostRenderer(options.editor, options.render, decorationsOf, drain);
2484
+ this.#readOnlyState = createReadOnly(this.registry, options.readOnly ?? false);
2485
+ }
2486
+ get root() {
2487
+ return this.#binding?.root ?? null;
2488
+ }
2489
+ get readOnly() {
2490
+ return this.#readOnlyState.current();
2491
+ }
2492
+ get sink() {
2493
+ return this.#drawn.sink;
2494
+ }
2495
+ get emptyHost() {
2496
+ return this.#drawn.emptyHost;
2497
+ }
2498
+ attach(root) {
2499
+ this.#detachRoot?.();
2500
+ const binding = bindRoot(this, root, this.#cross, (_, tr) => {
2501
+ this.#quiet ||= tr?.origin === "remote" && tr.docChanged;
2502
+ this.#loud ||= tr?.origin !== "remote";
2503
+ this.#outside.committed(tr);
2504
+ this.nodeReveal.follow(this.editor.state.selection);
2505
+ this.#drawn.syncTabStop();
2506
+ this.#cross.syncLater();
2507
+ });
2508
+ const unmount = this.editor.mount();
2509
+ const detach = () => {
2510
+ unmount();
2511
+ binding.detach();
2512
+ if (this.#detachRoot !== detach) return;
2513
+ this.#detachRoot = null;
2514
+ this.#binding = null;
2515
+ };
2516
+ this.#binding = binding;
2517
+ this.#detachRoot = detach;
2518
+ this.#drawn.syncTabStop();
2519
+ return detach;
2520
+ }
2521
+ registerBlockElement(id, element) {
2522
+ return this.#drawn.registerBlock(id, element);
2523
+ }
2524
+ blockElement(id) {
2525
+ return this.#drawn.block(id);
2526
+ }
2527
+ blockAt(node) {
2528
+ return this.#drawn.blockAt(node);
2529
+ }
2530
+ registerSink(element) {
2531
+ return this.#drawn.registerSink(element);
2532
+ }
2533
+ // An empty document draws one host that stands for a paragraph not created yet, so it belongs to no block.
2534
+ registerEmptyHost(element) {
2535
+ return this.#drawn.registerEmptyHost(element);
2536
+ }
2537
+ isEditorElement(element) {
2538
+ return this.#drawn.isEditorElement(element);
2539
+ }
2540
+ decorationsFor(block, region) {
2541
+ return hostDecorations(this.editor, block, region, this.#cross.mode);
2542
+ }
2543
+ dispatch(tr) {
2544
+ this.edit(() => {
2545
+ this.editor.dispatch(tr);
2546
+ return true;
2547
+ });
2548
+ }
2549
+ // Runs editor calls that dispatch on their own (insertText, handleKey, chains), then brings the hosts, the focus and
2550
+ // the DOM selection up to date once. Focus is followed only while it is still on the editor or nowhere: a change can
2551
+ // open a popover that takes it.
2552
+ edit(change) {
2553
+ const before = this.editor.state;
2554
+ const doc = this.#ownerDocument();
2555
+ const focused = hasFocus(this, doc);
2556
+ let handled = false;
2557
+ this.#flush(
2558
+ () => this.#outside.inside(() => {
2559
+ handled = change();
2560
+ })
2561
+ );
2562
+ if (this.editor.state === before) return handled;
2563
+ if (this.editor.state.doc !== before.doc) this.goalX = null;
2564
+ this.refreshHosts();
2565
+ this.#cross.sync();
2566
+ if (focused && doc !== null && ownsFocus(this, doc)) this.#keepFocus();
2567
+ this.syncSelectionToDom(true);
2568
+ return handled;
2569
+ }
2570
+ // A change made outside edit() reaches the hosts when React draws them, so the selection is written back once that
2571
+ // has happened, and focus follows a host the change replaced. A render of collaborators' changes alone, or of one
2572
+ // that changed the document, scrolls nothing doing so; a caret move of theirs leaves the rest to scroll as it would.
2573
+ afterRender() {
2574
+ const quiet = this.#quiet || !this.#loud;
2575
+ this.#quiet = this.#loud = false;
2576
+ const doc = this.#ownerDocument();
2577
+ const moved = this.#outside.takeMoved();
2578
+ if (!this.#outside.takeFocused() || !doc || !ownsFocus(this, doc)) return;
2579
+ if (moved) this.#selectFromDom(moved);
2580
+ this.#cross.sync();
2581
+ const follow = () => {
2582
+ this.#keepFocus(quiet);
2583
+ this.syncSelectionToDom();
2584
+ };
2585
+ if (!quiet) return follow();
2586
+ this.nodeReveal.settle(this.editor.state.selection);
2587
+ inPlace(this.root, follow);
2588
+ }
2589
+ refreshHosts() {
2590
+ for (const host of this.registry.all()) {
2591
+ if (!this.#isComposing(host)) this.#hosts.renderIfChanged(host);
2592
+ }
2593
+ }
2594
+ run(command) {
2595
+ return this.edit(() => command(this.editor.state, this.editor.dispatch));
2596
+ }
2597
+ announce(message) {
2598
+ this.announcer.announce(message);
2599
+ }
2600
+ // A redraw of the decorations alone, such as one that follows a selection the browser made, replaces the nodes that
2601
+ // selection sits in, and nothing else writes it back. It is written only while the editor holds focus: written while
2602
+ // focus is on the page, it would pull focus into the line.
2603
+ renderHost(block, region) {
2604
+ const element = this.registry.get(block, region);
2605
+ const host = element && { block, region, element };
2606
+ if (!host || this.#isComposing(host) || !this.#hosts.render(host)) return;
2607
+ const inSelection = element.contains(this.domSelection()?.focusNode ?? null);
2608
+ if (inSelection && hasFocus(this, element.ownerDocument)) this.syncSelectionToDom();
2609
+ }
2610
+ domSelection() {
2611
+ return this.#ownerDocument()?.getSelection() ?? null;
2612
+ }
2613
+ syncSelectionToDom(local) {
2614
+ writeModelSelection(this, this.#ownerDocument(), () => this.#binding?.selectionSync.suppressNext(), local);
2615
+ }
2616
+ // A pointer drag across hosts sets the model selection itself, while the browser keeps extending its own. Leaving
2617
+ // cross mode re-renders the hosts that drew it as decorations, which can remove the node holding the caret, so the
2618
+ // selection is written back.
2619
+ readSelectionFromDom() {
2620
+ const selection = movedSelection(this, this.#ownerDocument(), this.editor.state.selection);
2621
+ if (selection === null) return;
2622
+ const wasCross = this.#cross.active;
2623
+ if (!this.nativeVerticalMove) this.goalX = null;
2624
+ this.#flush(() => this.#selectFromDom(selection));
2625
+ this.#cross.sync();
2626
+ if (!wasCross && !this.#cross.active) return;
2627
+ this.#keepFocus();
2628
+ this.syncSelectionToDom();
2629
+ }
2630
+ focus(target) {
2631
+ const { editor } = this;
2632
+ const selection = focusTarget(editor.state, target);
2633
+ if (selection !== null && !sameSelection(selection, editor.state.selection)) dispatchSelection(this, selection);
2634
+ this.#takeFocus();
2635
+ this.syncSelectionToDom();
2636
+ }
2637
+ setReadOnly(readOnly) {
2638
+ this.#readOnlyState.set(readOnly);
2639
+ }
2640
+ subscribeReadOnly(listener) {
2641
+ return this.#readOnlyState.subscribe(listener);
2642
+ }
2643
+ destroy() {
2644
+ this.#detachRoot?.();
2645
+ }
2646
+ #selectFromDom(selection) {
2647
+ const { editor } = this;
2648
+ this.#outside.inside(() => editor.dispatch(editor.createTransaction().setSelection(selection).setOrigin("input")));
2649
+ }
2650
+ #isComposing(host) {
2651
+ return this.composingKey !== null && hostKey(host.block, host.region) === this.composingKey;
2652
+ }
2653
+ #ownerDocument() {
2654
+ return (this.#binding?.root ?? this.registry.all().next().value?.element)?.ownerDocument ?? null;
2655
+ }
2656
+ // React replaces a host whose block changes kind, which drops focus, and Firefox does not move focus into another
2657
+ // host when a script selects inside it.
2658
+ #keepFocus(preventScroll) {
2659
+ if (this.composingKey === null) this.#takeFocus(preventScroll);
2660
+ }
2661
+ #takeFocus(preventScroll) {
2662
+ const element = this.#drawn.focusElement();
2663
+ if (!element || element.ownerDocument.activeElement === element) return;
2664
+ if (element === this.sink) focusSink(this);
2665
+ else element.focus({ preventScroll });
2666
+ }
2667
+ };
2668
+
2669
+ // src/view/react/editor-context.ts
2670
+ import { createContext, useContext } from "react";
2671
+ var QuoinEditorContext = createContext(null);
2672
+ function useViewController() {
2673
+ const controller = useContext(QuoinEditorContext);
2674
+ if (controller === null) throw new Error("useViewController must be called inside an EditorView");
2675
+ return controller;
2676
+ }
2677
+
2678
+ // src/view/react/use-quoin-editor.ts
2679
+ import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
2680
+ import { flushSync } from "react-dom";
2681
+
2682
+ // src/view/react/use-isomorphic-layout-effect.ts
2683
+ import { useEffect, useLayoutEffect } from "react";
2684
+ var useIsomorphicLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
2685
+
2686
+ // src/view/react/use-quoin-editor.ts
2687
+ function useQuoinEditor(options) {
2688
+ const { blocks, readOnly = false } = options;
2689
+ const callbacks = useRef2(options);
2690
+ const [controller] = useState(() => {
2691
+ const editor = createEditor({
2692
+ blocks,
2693
+ extensions: options.extensions,
2694
+ onChange: (next) => callbacks.current.onChange(next),
2695
+ onTransaction: (tr, state) => callbacks.current.onTransaction?.(tr, state)
2696
+ });
2697
+ return new ViewController({
2698
+ editor,
2699
+ flush: flushSync,
2700
+ render: options.render,
2701
+ readOnly,
2702
+ onFiles: (files, at) => callbacks.current.onFiles?.(files, at)
2703
+ });
2704
+ });
2705
+ useIsomorphicLayoutEffect(() => {
2706
+ callbacks.current = options;
2707
+ });
2708
+ useIsomorphicLayoutEffect(() => {
2709
+ controller.editor.setBlocks(blocks);
2710
+ }, [controller, blocks]);
2711
+ useIsomorphicLayoutEffect(() => {
2712
+ controller.setReadOnly(readOnly);
2713
+ }, [controller, readOnly]);
2714
+ useEffect2(() => () => controller.destroy(), [controller]);
2715
+ return controller;
2716
+ }
2717
+
2718
+ // src/view/react/SelectionSink.tsx
2719
+ import { useCallback as useCallback3, useRef as useRef3 } from "react";
2720
+
2721
+ // src/view/react/use-read-only.ts
2722
+ import { useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore2 } from "react";
2723
+ function useReadOnly(controller) {
2724
+ const subscribe = useCallback2((notify) => controller.subscribeReadOnly(notify), [controller]);
2725
+ const read = useCallback2(() => controller.readOnly, [controller]);
2726
+ return useSyncExternalStore2(subscribe, read, read);
2727
+ }
2728
+
2729
+ // src/view/react/SelectionSink.tsx
2730
+ import { jsx } from "react/jsx-runtime";
2731
+ function SelectionSink({ label = "Selected content", blockLabel }) {
2732
+ const controller = useViewController();
2733
+ const ref = useRef3(null);
2734
+ const readOnly = useReadOnly(controller);
2735
+ const name = useEditorState(
2736
+ controller,
2737
+ useCallback3((state) => selectedBlockName(state, blockLabel), [blockLabel])
2738
+ );
2739
+ useIsomorphicLayoutEffect(() => {
2740
+ const sink = ref.current;
2741
+ return sink ? controller.registerSink(sink) : void 0;
2742
+ }, [controller]);
2743
+ return /* @__PURE__ */ jsx(
2744
+ "div",
2745
+ {
2746
+ ref,
2747
+ contentEditable: !readOnly,
2748
+ suppressContentEditableWarning: true,
2749
+ role: "textbox",
2750
+ "aria-label": name || label,
2751
+ inputMode: name === null ? void 0 : "none",
2752
+ "aria-multiline": name === null ? "true" : void 0,
2753
+ "aria-readonly": readOnly ? "true" : void 0,
2754
+ tabIndex: -1,
2755
+ spellCheck: false,
2756
+ "data-quoin-sink": "",
2757
+ onFocus: () => {
2758
+ if (heldBySink(controller.editor.state.selection)) focusSink(controller);
2759
+ }
2760
+ }
2761
+ );
2762
+ }
2763
+
2764
+ // src/view/react/EditableRegion.tsx
2765
+ import { useCallback as useCallback4, useContext as useContext2, useRef as useRef4 } from "react";
2766
+
2767
+ // src/view/react/block-placeholder.ts
2768
+ import { createContext as createContext2 } from "react";
2769
+ var headingPlaceholder = (_state, { type, attrs }) => type === "MARKDOWN" && attrs.kind === "heading" ? `Heading ${headingLevel(attrs.level)}` : void 0;
2770
+ var BlockPlaceholderContext = createContext2(headingPlaceholder);
2771
+ function regionPlaceholder(state, blockId, region, placeholderOf) {
2772
+ const block = blockById(state.doc, blockId);
2773
+ return block !== void 0 && primaryRegion(state.schema, block) === region ? placeholderOf(state, block) : void 0;
2774
+ }
2775
+
2776
+ // src/view/react/EditableRegion.tsx
2777
+ import { jsx as jsx2 } from "react/jsx-runtime";
2778
+ var samePainted = (a, b) => a.inline === b.inline && a.placeholder === b.placeholder && sameDecorations(a.decorations, b.decorations);
2779
+ function EditableRegion({ block, region, label, id, placeholder, className }) {
2780
+ const controller = useViewController();
2781
+ const placeholderOf = useContext2(BlockPlaceholderContext);
2782
+ const ref = useRef4(null);
2783
+ const readOnly = useReadOnly(controller);
2784
+ const select = useCallback4(
2785
+ (state) => {
2786
+ const inline2 = regionInline(state, block, region);
2787
+ const empty = inline2 === void 0 || inlineLength(inline2) === 0;
2788
+ return {
2789
+ inline: inline2,
2790
+ decorations: controller.decorationsFor(block, region),
2791
+ placeholder: empty ? placeholder ?? regionPlaceholder(state, block, region, placeholderOf) : void 0
2792
+ };
2793
+ },
2794
+ [controller, block, region, placeholder, placeholderOf]
2795
+ );
2796
+ const { inline, decorations, placeholder: shown } = useEditorState(controller, select, samePainted);
2797
+ useIsomorphicLayoutEffect(() => {
2798
+ const host = ref.current;
2799
+ return host ? controller.registry.register(block, region, host) : void 0;
2800
+ }, [controller, block, region]);
2801
+ useIsomorphicLayoutEffect(() => {
2802
+ controller.renderHost(block, region);
2803
+ }, [controller, block, region, inline, decorations]);
2804
+ return /* @__PURE__ */ jsx2(
2805
+ "div",
2806
+ {
2807
+ ref,
2808
+ id,
2809
+ className,
2810
+ contentEditable: !readOnly,
2811
+ suppressContentEditableWarning: true,
2812
+ tabIndex: -1,
2813
+ role: "textbox",
2814
+ "aria-multiline": "true",
2815
+ "aria-readonly": readOnly ? "true" : void 0,
2816
+ "aria-label": label,
2817
+ "data-quoin-host": "",
2818
+ "data-placeholder": readOnly ? void 0 : shown,
2819
+ "aria-placeholder": readOnly ? void 0 : shown,
2820
+ spellCheck: true
2821
+ }
2822
+ );
2823
+ }
2824
+
2825
+ // src/view/react/default-renderers.tsx
2826
+ import { useContext as useContext3 } from "react";
2827
+
2828
+ // src/view/react/callout-block.tsx
2829
+ import { jsx as jsx3 } from "react/jsx-runtime";
2830
+ var CalloutBlock = ({ block }) => {
2831
+ const tone = typeof block.attrs.tone === "string" && block.attrs.tone !== "" ? block.attrs.tone : "info";
2832
+ return /* @__PURE__ */ jsx3("div", { className: "quoin-callout", "data-tone": tone, children: /* @__PURE__ */ jsx3(EditableRegion, { block: block.id, region: "body", label: `Callout, ${tone}` }) });
2833
+ };
2834
+
2835
+ // src/view/react/block-command.ts
2836
+ function onBlock(id, command) {
2837
+ return (state, dispatch) => {
2838
+ const { ok, tr } = runCommands(state, [setSelection(blockRangeSelection(id)), command]);
2839
+ if (!ok || tr === null) return false;
2840
+ dispatch?.(tr.setSelection(state.selection));
2841
+ return true;
2842
+ };
2843
+ }
2844
+
2845
+ // src/view/react/prevent-mouse-focus.ts
2846
+ var keepFocus = (event) => event.preventDefault();
2847
+
2848
+ // src/view/react/TaskCheckbox.tsx
2849
+ import { jsx as jsx4 } from "react/jsx-runtime";
2850
+ function TaskCheckbox({ block, controller, label = "Task", labelledBy }) {
2851
+ const checked = block.attrs.checked === true;
2852
+ const flip2 = flipCommand(block);
2853
+ const readOnly = useReadOnly(controller);
2854
+ return /* @__PURE__ */ jsx4(
2855
+ "button",
2856
+ {
2857
+ type: "button",
2858
+ role: "checkbox",
2859
+ "aria-checked": checked,
2860
+ "aria-readonly": readOnly ? "true" : void 0,
2861
+ "aria-label": label,
2862
+ "aria-labelledby": labelledBy,
2863
+ className: "quoin-task-box",
2864
+ contentEditable: false,
2865
+ tabIndex: -1,
2866
+ onMouseDown: keepFocus,
2867
+ onClick: () => {
2868
+ if (flip2 && !controller.readOnly) controller.run(onBlock(block.id, flip2));
2869
+ },
2870
+ children: checked ? /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx4("path", { d: "M3.5 8.5l3 3 6-7", fill: "none", stroke: "currentColor", strokeWidth: "2" }) }) : null
2871
+ }
2872
+ );
2873
+ }
2874
+
2875
+ // src/view/react/list-numbers.ts
2876
+ var isOrdered = (block) => block.type === "MARKDOWN" && block.attrs.kind === "ordered";
2877
+ function numberDoc(doc) {
2878
+ const numbers = doc.blocks.map(() => 1);
2879
+ const runs = [];
2880
+ doc.blocks.forEach((block, index) => {
2881
+ while (runs.length > 0 && runs[runs.length - 1].indent > block.indent) runs.pop();
2882
+ const run2 = runs[runs.length - 1];
2883
+ const sameLevel = run2 !== void 0 && run2.indent === block.indent;
2884
+ if (!isOrdered(block)) {
2885
+ if (sameLevel) runs.pop();
2886
+ return;
2887
+ }
2888
+ if (sameLevel) {
2889
+ numbers[index] = run2.next++;
2890
+ return;
2891
+ }
2892
+ const start = orderedStart(block.attrs.start);
2893
+ numbers[index] = start;
2894
+ runs.push({ indent: block.indent, next: start + 1 });
2895
+ });
2896
+ return numbers;
2897
+ }
2898
+ var cache = /* @__PURE__ */ new WeakMap();
2899
+ function orderedNumber(doc, index) {
2900
+ let numbers = cache.get(doc);
2901
+ if (numbers === void 0) {
2902
+ numbers = numberDoc(doc);
2903
+ cache.set(doc, numbers);
2904
+ }
2905
+ return numbers[index] ?? 1;
2906
+ }
2907
+
2908
+ // src/view/react/use-ordered-number.ts
2909
+ function useOrderedNumber(controller, blockId) {
2910
+ return useEditorState(controller, ({ doc }) => {
2911
+ const index = doc.index.get(blockId);
2912
+ return index === void 0 ? 1 : orderedNumber(doc, index);
2913
+ });
2914
+ }
2915
+
2916
+ // src/view/react/markdown-block.tsx
2917
+ import { jsx as jsx5, jsxs } from "react/jsx-runtime";
2918
+ var REGION = "text";
2919
+ function TextRegion({ block, label }) {
2920
+ return /* @__PURE__ */ jsx5(EditableRegion, { block: block.id, region: REGION, label });
2921
+ }
2922
+ function NumberedItem({ block, controller }) {
2923
+ const number = useOrderedNumber(controller, block.id);
2924
+ return /* @__PURE__ */ jsxs("div", { className: "quoin-list-item", "data-list": "ordered", children: [
2925
+ /* @__PURE__ */ jsx5("span", { className: "quoin-marker", "aria-hidden": "true", children: `${number}.` }),
2926
+ /* @__PURE__ */ jsx5(TextRegion, { block, label: `Numbered list item ${number}` })
2927
+ ] });
2928
+ }
2929
+ var MarkdownBlock = (props) => {
2930
+ const { block } = props;
2931
+ switch (block.attrs.kind) {
2932
+ case "heading": {
2933
+ const level = headingLevel(block.attrs.level);
2934
+ return /* @__PURE__ */ jsx5("div", { role: "heading", "aria-level": level, className: "quoin-heading", children: /* @__PURE__ */ jsx5(TextRegion, { block, label: `Heading ${level}` }) });
2935
+ }
2936
+ case "bullet":
2937
+ return /* @__PURE__ */ jsxs("div", { className: "quoin-list-item", "data-list": "bullet", children: [
2938
+ /* @__PURE__ */ jsx5("span", { className: "quoin-marker", "aria-hidden": "true", children: "\u2022" }),
2939
+ /* @__PURE__ */ jsx5(TextRegion, { block, label: "Bullet list item" })
2940
+ ] });
2941
+ case "ordered":
2942
+ return /* @__PURE__ */ jsx5(NumberedItem, { ...props });
2943
+ case "task":
2944
+ return /* @__PURE__ */ jsxs("div", { className: "quoin-task", "data-checked": block.attrs.checked === true, children: [
2945
+ /* @__PURE__ */ jsx5(TaskCheckbox, { block, controller: props.controller }),
2946
+ /* @__PURE__ */ jsx5(TextRegion, { block, label: "Task" })
2947
+ ] });
2948
+ case "quote":
2949
+ return /* @__PURE__ */ jsx5("div", { className: "quoin-quote", children: /* @__PURE__ */ jsx5(TextRegion, { block, label: "Quote" }) });
2950
+ default:
2951
+ return /* @__PURE__ */ jsx5(TextRegion, { block, label: "Paragraph" });
2952
+ }
2953
+ };
2954
+
2955
+ // src/view/react/use-reader-choices.ts
2956
+ import { useSyncExternalStore as useSyncExternalStore3 } from "react";
2957
+ function useReaderChoices(controller) {
2958
+ const store = readerToggles(controller);
2959
+ return useSyncExternalStore3(store.subscribe, () => store.choices, () => store.choices);
2960
+ }
2961
+ function useToggleOpen(controller, block) {
2962
+ return shownOpen(block, useReaderChoices(controller));
2963
+ }
2964
+
2965
+ // src/view/react/ToggleChevron.tsx
2966
+ import { jsx as jsx6 } from "react/jsx-runtime";
2967
+ function ToggleChevron({ block, controller, label = "Toggle", controls }) {
2968
+ const open = useToggleOpen(controller, block);
2969
+ const readOnly = useReadOnly(controller);
2970
+ const flip2 = flipCommand(block);
2971
+ return /* @__PURE__ */ jsx6(
2972
+ "button",
2973
+ {
2974
+ type: "button",
2975
+ "aria-expanded": open,
2976
+ "aria-controls": controls,
2977
+ "aria-label": label,
2978
+ className: "quoin-toggle-button",
2979
+ contentEditable: false,
2980
+ tabIndex: readOnly ? 0 : -1,
2981
+ onMouseDown: keepFocus,
2982
+ onClick: () => {
2983
+ if (controller.readOnly) readerToggles(controller).flip(block);
2984
+ else if (flip2) controller.run(onBlock(block.id, flip2));
2985
+ },
2986
+ children: /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx6("path", { d: "M6 3.5l4.5 4.5L6 12.5", fill: "none", stroke: "currentColor", strokeWidth: "2" }) })
2987
+ }
2988
+ );
2989
+ }
2990
+
2991
+ // src/view/react/toggle-block.tsx
2992
+ import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
2993
+ var ToggleBlock = ({ block, controller }) => {
2994
+ const open = useToggleOpen(controller, block);
2995
+ return /* @__PURE__ */ jsxs2("div", { className: "quoin-toggle", "data-open": open, children: [
2996
+ /* @__PURE__ */ jsx7(ToggleChevron, { block, controller }),
2997
+ /* @__PURE__ */ jsx7(EditableRegion, { block: block.id, region: "text", label: "Toggle" })
2998
+ ] });
2999
+ };
3000
+
3001
+ // src/view/react/unsupported-block-label.ts
3002
+ import { createContext as createContext3 } from "react";
3003
+ var UNSUPPORTED_BLOCK = "Unsupported block";
3004
+ var UnsupportedBlockLabel = createContext3(UNSUPPORTED_BLOCK);
3005
+
3006
+ // src/view/react/default-renderers.tsx
3007
+ import { jsx as jsx8 } from "react/jsx-runtime";
3008
+ var defaultRenderers = {
3009
+ MARKDOWN: MarkdownBlock,
3010
+ CALLOUT: CalloutBlock,
3011
+ TOGGLE: ToggleBlock
3012
+ };
3013
+ var VoidBlockFallback = ({ block }) => {
3014
+ const label = useContext3(UnsupportedBlockLabel);
3015
+ if (block.type === "DIVIDER") return /* @__PURE__ */ jsx8("div", { className: "quoin-void", contentEditable: false, role: "separator" });
3016
+ return /* @__PURE__ */ jsx8("div", { className: "quoin-void", contentEditable: false, role: "group", "aria-label": label, children: label });
3017
+ };
3018
+
3019
+ // src/view/react/BlockShell.tsx
3020
+ import { memo, useRef as useRef5 } from "react";
3021
+
3022
+ // src/view/react/block-dom-id.ts
3023
+ import { createContext as createContext4, useCallback as useCallback5, useContext as useContext4 } from "react";
3024
+ var BlockDomIdScope = createContext4("quoin");
3025
+ function useBlockDomId() {
3026
+ const scope = useContext4(BlockDomIdScope);
3027
+ return useCallback5((block) => `${scope}-${encodeURIComponent(block)}`, [scope]);
3028
+ }
3029
+ function useRegionDomId(block, region) {
3030
+ return `${useBlockDomId()(block)}/${encodeURIComponent(region)}`;
3031
+ }
3032
+
3033
+ // src/view/react/block-status.ts
3034
+ function within(state, index, ends) {
3035
+ const [anchor, head] = ends.map((id) => state.doc.index.get(id));
3036
+ if (anchor === void 0 || head === void 0) return false;
3037
+ return index >= Math.min(anchor, head) && index <= Math.max(anchor, head);
3038
+ }
3039
+ function covers(state, id, index) {
3040
+ const { doc, schema, selection } = state;
3041
+ if (selection === null) return false;
3042
+ if (selection.kind === "node") return selection.block === id;
3043
+ if (selection.kind === "blocks") return within(state, index, [selection.anchor, selection.head]);
3044
+ if (!isCrossSelection(selection) || isTextFlow(schema, doc.blocks[index])) return false;
3045
+ return within(state, index, [selection.anchor.block, selection.head.block]);
3046
+ }
3047
+ function blockStatus(state, id, choices = NO_CHOICES) {
3048
+ const index = state.doc.index.get(id);
3049
+ if (index === void 0) return "plain";
3050
+ if (isHiddenAt(state.doc, index, choices)) return "hidden";
3051
+ return covers(state, id, index) ? "selected" : "plain";
3052
+ }
3053
+
3054
+ // src/view/react/BlockShell.tsx
3055
+ import { jsx as jsx9 } from "react/jsx-runtime";
3056
+ function rendererFor(renderers, type) {
3057
+ for (const table of [renderers, defaultRenderers]) if (Object.hasOwn(table, type)) return table[type];
3058
+ return VoidBlockFallback;
3059
+ }
3060
+ var BlockShell = memo(function BlockShell2({ block, renderers }) {
3061
+ const controller = useViewController();
3062
+ const ref = useRef5(null);
3063
+ const domId = useBlockDomId();
3064
+ const choices = useReaderChoices(controller);
3065
+ const status = useEditorState(controller, (state) => blockStatus(state, block.id, choices));
3066
+ const decorations = useEditorState(controller, () => hostDecorations(controller.editor, block.id, ""), sameDecorations);
3067
+ const hidden = status === "hidden";
3068
+ useIsomorphicLayoutEffect(() => {
3069
+ const element = ref.current;
3070
+ return hidden || !element ? void 0 : controller.registerBlockElement(block.id, element);
3071
+ }, [controller, block.id, hidden]);
3072
+ if (hidden) return null;
3073
+ const Renderer = rendererFor(renderers, block.type);
3074
+ const { kind, color, background, align } = block.attrs;
3075
+ return /* @__PURE__ */ jsx9(
3076
+ "div",
3077
+ {
3078
+ ...Object.assign({}, ...decorations.map((decoration) => decoration.attrs)),
3079
+ ref,
3080
+ id: domId(block.id),
3081
+ "data-quoin-block": "",
3082
+ "data-block-id": block.id,
3083
+ "data-type": block.type,
3084
+ "data-kind": optionalString(kind),
3085
+ "data-indent": block.indent,
3086
+ "data-color": paletteOrUndefined(color),
3087
+ "data-bg": paletteOrUndefined(background),
3088
+ "data-align": alignOrUndefined(align),
3089
+ style: { "--quoin-indent": block.indent },
3090
+ tabIndex: -1,
3091
+ "data-selected": status === "selected" ? "true" : void 0,
3092
+ children: /* @__PURE__ */ jsx9(Renderer, { block, controller })
3093
+ }
3094
+ );
3095
+ });
3096
+
3097
+ // src/view/react/EditorView.tsx
3098
+ import { useId, useRef as useRef7 } from "react";
3099
+
3100
+ // src/view/react/EmptyDocumentHost.tsx
3101
+ import { useRef as useRef6 } from "react";
3102
+ import { jsx as jsx10 } from "react/jsx-runtime";
3103
+ function EmptyDocumentHost({ placeholder, label = "Paragraph" }) {
3104
+ const controller = useViewController();
3105
+ const ref = useRef6(null);
3106
+ const readOnly = useReadOnly(controller);
3107
+ useIsomorphicLayoutEffect(() => {
3108
+ const host = ref.current;
3109
+ if (host === null) return void 0;
3110
+ renderInline(host, [], []);
3111
+ return controller.registerEmptyHost(host);
3112
+ }, [controller]);
3113
+ return /* @__PURE__ */ jsx10(
3114
+ "div",
3115
+ {
3116
+ ref,
3117
+ contentEditable: !readOnly,
3118
+ suppressContentEditableWarning: true,
3119
+ role: "textbox",
3120
+ "aria-multiline": "true",
3121
+ "aria-readonly": readOnly ? "true" : void 0,
3122
+ "aria-label": label,
3123
+ "data-quoin-host": "",
3124
+ "data-quoin-empty": "",
3125
+ "data-placeholder": readOnly ? void 0 : placeholder,
3126
+ "aria-placeholder": readOnly ? void 0 : placeholder,
3127
+ spellCheck: true,
3128
+ onClick: () => ref.current?.focus()
3129
+ }
3130
+ );
3131
+ }
3132
+
3133
+ // src/view/react/focus-owners.ts
3134
+ var focusOwners = /* @__PURE__ */ new WeakMap();
3135
+
3136
+ // src/view/react/LiveRegion.tsx
3137
+ import { useCallback as useCallback6, useMemo, useSyncExternalStore as useSyncExternalStore4 } from "react";
3138
+
3139
+ // src/view/selection/selection-announcement.ts
3140
+ var SILENCE = { key: "", message: "" };
3141
+ function selectionAnnouncement({ doc, selection }, blocksSelected) {
3142
+ if (selection === null || selection.kind === "text" && !isCrossSelection(selection)) return SILENCE;
3143
+ if (selection.kind === "node") return { key: `node:${selection.block}`, message: blocksSelected(1) };
3144
+ const anchor = selection.kind === "blocks" ? selection.anchor : selection.anchor.block;
3145
+ const head = selection.kind === "blocks" ? selection.head : selection.head.block;
3146
+ const first = doc.index.get(anchor);
3147
+ const last = doc.index.get(head);
3148
+ if (first === void 0 || last === void 0) return SILENCE;
3149
+ const count = drawnBlockCount(doc, Math.min(first, last), Math.max(first, last) + 1);
3150
+ return { key: `${selection.kind}:${anchor}:${head}`, message: blocksSelected(count) };
3151
+ }
3152
+
3153
+ // src/view/react/LiveRegion.tsx
3154
+ import { jsx as jsx11 } from "react/jsx-runtime";
3155
+ function heardNow({ editor, announcer }, blocksSelected) {
3156
+ const spoken = announcer.current();
3157
+ if (spoken !== null) return { key: `said:${spoken.serial}`, message: spoken.message };
3158
+ return selectionAnnouncement(editor.state, blocksSelected);
3159
+ }
3160
+ function LiveRegion({ blocksSelected }) {
3161
+ const controller = useViewController();
3162
+ const subscribe = useCallback6(
3163
+ (notify) => {
3164
+ const stopState = controller.editor.subscribe(() => notify());
3165
+ const stopSpoken = controller.announcer.subscribe(notify);
3166
+ return () => {
3167
+ stopState();
3168
+ stopSpoken();
3169
+ };
3170
+ },
3171
+ [controller]
3172
+ );
3173
+ const read = useMemo(() => {
3174
+ let last = SILENCE;
3175
+ return () => {
3176
+ const next = heardNow(controller, blocksSelected);
3177
+ if (next.key !== last.key || next.message !== last.message) last = next;
3178
+ return last;
3179
+ };
3180
+ }, [controller, blocksSelected]);
3181
+ const { key, message } = useSyncExternalStore4(subscribe, read, read);
3182
+ return /* @__PURE__ */ jsx11("div", { className: "quoin-visually-hidden", role: "status", "aria-live": "polite", "aria-atomic": "true", children: message !== "" && /* @__PURE__ */ jsx11("span", { children: message }, key) });
3183
+ }
3184
+
3185
+ // src/view/react/SelectionWriter.tsx
3186
+ function SelectionWriter() {
3187
+ const controller = useViewController();
3188
+ const state = useEditorState(controller, wholeState);
3189
+ useIsomorphicLayoutEffect(() => controller.afterRender(), [controller, state]);
3190
+ return null;
3191
+ }
3192
+
3193
+ // src/view/react/use-block-elements.tsx
3194
+ import { useState as useState2 } from "react";
3195
+ import { jsx as jsx12 } from "react/jsx-runtime";
3196
+ function useBlockElements(blocks, renderers) {
3197
+ const [made] = useState2(() => /* @__PURE__ */ new WeakMap());
3198
+ return blocks.map((block) => {
3199
+ const known2 = made.get(block);
3200
+ if (known2?.renderers === renderers) return known2.element;
3201
+ const element = /* @__PURE__ */ jsx12(BlockShell, { block, renderers }, block.id);
3202
+ made.set(block, { renderers, element });
3203
+ return element;
3204
+ });
3205
+ }
3206
+
3207
+ // src/view/react/EditorView.tsx
3208
+ import { jsx as jsx13, jsxs as jsxs3 } from "react/jsx-runtime";
3209
+ var NO_RENDERERS = {};
3210
+ var HINT = "Press Escape, then Tab, to leave the editor.";
3211
+ var BLOCKS_SELECTED = (count) => `${count} ${count === 1 ? "block" : "blocks"} selected`;
3212
+ function EditorView({
3213
+ controller,
3214
+ renderers = NO_RENDERERS,
3215
+ className,
3216
+ id,
3217
+ "aria-label": label = "Editor",
3218
+ hint = HINT,
3219
+ paragraphLabel,
3220
+ selectedContentLabel,
3221
+ blockLabel,
3222
+ unsupportedBlockLabel = UNSUPPORTED_BLOCK,
3223
+ placeholder,
3224
+ blockPlaceholder = headingPlaceholder,
3225
+ blocksSelectedLabel = BLOCKS_SELECTED,
3226
+ children
3227
+ }) {
3228
+ const root = useRef7(null);
3229
+ const hintId = useId();
3230
+ const blockIdScope = useId();
3231
+ const blocks = useEditorState(controller, (state) => state.doc.blocks);
3232
+ const shells = useBlockElements(blocks, renderers);
3233
+ useIsomorphicLayoutEffect(() => root.current ? controller.attach(root.current) : void 0, [controller]);
3234
+ return /* @__PURE__ */ jsx13(QuoinEditorContext.Provider, { value: controller, children: /* @__PURE__ */ jsxs3(
3235
+ "div",
3236
+ {
3237
+ ref: root,
3238
+ id,
3239
+ className: ["quoin", "quoin-editor", className].filter(Boolean).join(" "),
3240
+ role: "group",
3241
+ "aria-label": label,
3242
+ "aria-describedby": hintId,
3243
+ onFocusCapture: (event) => focusOwners.set(event.target, controller),
3244
+ children: [
3245
+ /* @__PURE__ */ jsx13(BlockDomIdScope.Provider, { value: blockIdScope, children: /* @__PURE__ */ jsx13(UnsupportedBlockLabel.Provider, { value: unsupportedBlockLabel, children: /* @__PURE__ */ jsx13(BlockPlaceholderContext.Provider, { value: blockPlaceholder, children: blocks.length === 0 ? /* @__PURE__ */ jsx13(EmptyDocumentHost, { placeholder, label: paragraphLabel }) : shells }) }) }),
3246
+ children,
3247
+ /* @__PURE__ */ jsx13(SelectionSink, { label: selectedContentLabel, blockLabel }),
3248
+ /* @__PURE__ */ jsx13("div", { id: hintId, className: "quoin-visually-hidden", children: hint }),
3249
+ /* @__PURE__ */ jsx13(LiveRegion, { blocksSelected: blocksSelectedLabel }),
3250
+ /* @__PURE__ */ jsx13(SelectionWriter, {})
3251
+ ]
3252
+ }
3253
+ ) });
3254
+ }
3255
+
3256
+ export {
3257
+ dispatchSelection,
3258
+ wholeState,
3259
+ useEditorState,
3260
+ useReadOnly,
3261
+ isCrossSelection,
3262
+ NODE_ENTRY,
3263
+ visibleBlockIndexes,
3264
+ drawnBlockCount,
3265
+ neighbourBlock,
3266
+ moveToNeighbour,
3267
+ textDiff,
3268
+ domMapOf,
3269
+ renderInline,
3270
+ domToPos,
3271
+ posToDom,
3272
+ caretFromPoint,
3273
+ caretClientRect,
3274
+ lineEdgeRect,
3275
+ isOnFirstLine,
3276
+ isOnLastLine,
3277
+ isComposingKey,
3278
+ NATIVE_INTERACTION,
3279
+ useIsomorphicLayoutEffect,
3280
+ onBlock,
3281
+ listen,
3282
+ createAutoScroll,
3283
+ hostKey,
3284
+ regionInline,
3285
+ handleCopy,
3286
+ ViewController,
3287
+ useViewController,
3288
+ useQuoinEditor,
3289
+ useBlockDomId,
3290
+ useRegionDomId,
3291
+ focusOwners,
3292
+ SelectionSink,
3293
+ EditableRegion,
3294
+ TaskCheckbox,
3295
+ useOrderedNumber,
3296
+ useToggleOpen,
3297
+ ToggleChevron,
3298
+ defaultRenderers,
3299
+ VoidBlockFallback,
3300
+ BlockShell,
3301
+ EditorView
3302
+ };
3303
+ //# sourceMappingURL=chunk-SSIRYUCK.js.map