@doxi/core 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/collab/port.d.ts +46 -0
  4. package/dist/collab/port.d.ts.map +1 -0
  5. package/dist/collab/port.js +11 -0
  6. package/dist/collab/port.js.map +1 -0
  7. package/dist/commands/block-commands.d.ts +62 -0
  8. package/dist/commands/block-commands.d.ts.map +1 -0
  9. package/dist/commands/block-commands.js +208 -0
  10. package/dist/commands/block-commands.js.map +1 -0
  11. package/dist/commands/command.d.ts +13 -0
  12. package/dist/commands/command.d.ts.map +1 -0
  13. package/dist/commands/command.js +13 -0
  14. package/dist/commands/command.js.map +1 -0
  15. package/dist/commands/edit-commands.d.ts +5 -0
  16. package/dist/commands/edit-commands.d.ts.map +1 -0
  17. package/dist/commands/edit-commands.js +147 -0
  18. package/dist/commands/edit-commands.js.map +1 -0
  19. package/dist/commands/image-commands.d.ts +31 -0
  20. package/dist/commands/image-commands.d.ts.map +1 -0
  21. package/dist/commands/image-commands.js +130 -0
  22. package/dist/commands/image-commands.js.map +1 -0
  23. package/dist/commands/index.d.ts +11 -0
  24. package/dist/commands/index.d.ts.map +1 -0
  25. package/dist/commands/index.js +11 -0
  26. package/dist/commands/index.js.map +1 -0
  27. package/dist/commands/keymap.d.ts +34 -0
  28. package/dist/commands/keymap.d.ts.map +1 -0
  29. package/dist/commands/keymap.js +84 -0
  30. package/dist/commands/keymap.js.map +1 -0
  31. package/dist/commands/link-commands.d.ts +54 -0
  32. package/dist/commands/link-commands.d.ts.map +1 -0
  33. package/dist/commands/link-commands.js +151 -0
  34. package/dist/commands/link-commands.js.map +1 -0
  35. package/dist/commands/list-commands.d.ts +42 -0
  36. package/dist/commands/list-commands.d.ts.map +1 -0
  37. package/dist/commands/list-commands.js +316 -0
  38. package/dist/commands/list-commands.js.map +1 -0
  39. package/dist/commands/mark-commands.d.ts +53 -0
  40. package/dist/commands/mark-commands.d.ts.map +1 -0
  41. package/dist/commands/mark-commands.js +181 -0
  42. package/dist/commands/mark-commands.js.map +1 -0
  43. package/dist/commands/selection-commands.d.ts +3 -0
  44. package/dist/commands/selection-commands.d.ts.map +1 -0
  45. package/dist/commands/selection-commands.js +11 -0
  46. package/dist/commands/selection-commands.js.map +1 -0
  47. package/dist/commands/table-commands.d.ts +109 -0
  48. package/dist/commands/table-commands.d.ts.map +1 -0
  49. package/dist/commands/table-commands.js +884 -0
  50. package/dist/commands/table-commands.js.map +1 -0
  51. package/dist/history/history.d.ts +40 -0
  52. package/dist/history/history.d.ts.map +1 -0
  53. package/dist/history/history.js +139 -0
  54. package/dist/history/history.js.map +1 -0
  55. package/dist/history/index.d.ts +2 -0
  56. package/dist/history/index.d.ts.map +1 -0
  57. package/dist/history/index.js +2 -0
  58. package/dist/history/index.js.map +1 -0
  59. package/dist/html/index.d.ts +3 -0
  60. package/dist/html/index.d.ts.map +1 -0
  61. package/dist/html/index.js +3 -0
  62. package/dist/html/index.js.map +1 -0
  63. package/dist/html/parse.d.ts +4 -0
  64. package/dist/html/parse.d.ts.map +1 -0
  65. package/dist/html/parse.js +0 -0
  66. package/dist/html/parse.js.map +1 -0
  67. package/dist/html/serialize.d.ts +4 -0
  68. package/dist/html/serialize.d.ts.map +1 -0
  69. package/dist/html/serialize.js +75 -0
  70. package/dist/html/serialize.js.map +1 -0
  71. package/dist/index.d.ts +13 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +13 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/layout/index.d.ts +6 -0
  76. package/dist/layout/index.d.ts.map +1 -0
  77. package/dist/layout/index.js +6 -0
  78. package/dist/layout/index.js.map +1 -0
  79. package/dist/layout/layout-engine.d.ts +20 -0
  80. package/dist/layout/layout-engine.d.ts.map +1 -0
  81. package/dist/layout/layout-engine.js +198 -0
  82. package/dist/layout/layout-engine.js.map +1 -0
  83. package/dist/layout/measure.d.ts +9 -0
  84. package/dist/layout/measure.d.ts.map +1 -0
  85. package/dist/layout/measure.js +37 -0
  86. package/dist/layout/measure.js.map +1 -0
  87. package/dist/layout/split-paragraph.d.ts +28 -0
  88. package/dist/layout/split-paragraph.d.ts.map +1 -0
  89. package/dist/layout/split-paragraph.js +122 -0
  90. package/dist/layout/split-paragraph.js.map +1 -0
  91. package/dist/layout/split-table.d.ts +46 -0
  92. package/dist/layout/split-table.d.ts.map +1 -0
  93. package/dist/layout/split-table.js +84 -0
  94. package/dist/layout/split-table.js.map +1 -0
  95. package/dist/layout/types.d.ts +73 -0
  96. package/dist/layout/types.d.ts.map +1 -0
  97. package/dist/layout/types.js +36 -0
  98. package/dist/layout/types.js.map +1 -0
  99. package/dist/layout/widow-orphan.d.ts +15 -0
  100. package/dist/layout/widow-orphan.d.ts.map +1 -0
  101. package/dist/layout/widow-orphan.js +14 -0
  102. package/dist/layout/widow-orphan.js.map +1 -0
  103. package/dist/model/content-expr.d.ts +32 -0
  104. package/dist/model/content-expr.d.ts.map +1 -0
  105. package/dist/model/content-expr.js +106 -0
  106. package/dist/model/content-expr.js.map +1 -0
  107. package/dist/model/fragment.d.ts +17 -0
  108. package/dist/model/fragment.d.ts.map +1 -0
  109. package/dist/model/fragment.js +44 -0
  110. package/dist/model/fragment.js.map +1 -0
  111. package/dist/model/index.d.ts +10 -0
  112. package/dist/model/index.d.ts.map +1 -0
  113. package/dist/model/index.js +10 -0
  114. package/dist/model/index.js.map +1 -0
  115. package/dist/model/mark.d.ts +35 -0
  116. package/dist/model/mark.d.ts.map +1 -0
  117. package/dist/model/mark.js +89 -0
  118. package/dist/model/mark.js.map +1 -0
  119. package/dist/model/node-type.d.ts +36 -0
  120. package/dist/model/node-type.d.ts.map +1 -0
  121. package/dist/model/node-type.js +14 -0
  122. package/dist/model/node-type.js.map +1 -0
  123. package/dist/model/node.d.ts +36 -0
  124. package/dist/model/node.d.ts.map +1 -0
  125. package/dist/model/node.js +192 -0
  126. package/dist/model/node.js.map +1 -0
  127. package/dist/model/position.d.ts +66 -0
  128. package/dist/model/position.d.ts.map +1 -0
  129. package/dist/model/position.js +158 -0
  130. package/dist/model/position.js.map +1 -0
  131. package/dist/model/schema.d.ts +28 -0
  132. package/dist/model/schema.d.ts.map +1 -0
  133. package/dist/model/schema.js +195 -0
  134. package/dist/model/schema.js.map +1 -0
  135. package/dist/model/slice.d.ts +26 -0
  136. package/dist/model/slice.d.ts.map +1 -0
  137. package/dist/model/slice.js +56 -0
  138. package/dist/model/slice.js.map +1 -0
  139. package/dist/model/table-grid.d.ts +71 -0
  140. package/dist/model/table-grid.d.ts.map +1 -0
  141. package/dist/model/table-grid.js +130 -0
  142. package/dist/model/table-grid.js.map +1 -0
  143. package/dist/plugin/index.d.ts +3 -0
  144. package/dist/plugin/index.d.ts.map +1 -0
  145. package/dist/plugin/index.js +3 -0
  146. package/dist/plugin/index.js.map +1 -0
  147. package/dist/plugin/plugin-key.d.ts +13 -0
  148. package/dist/plugin/plugin-key.d.ts.map +1 -0
  149. package/dist/plugin/plugin-key.js +13 -0
  150. package/dist/plugin/plugin-key.js.map +1 -0
  151. package/dist/plugin/plugin-state.d.ts +2 -0
  152. package/dist/plugin/plugin-state.d.ts.map +1 -0
  153. package/dist/plugin/plugin-state.js +3 -0
  154. package/dist/plugin/plugin-state.js.map +1 -0
  155. package/dist/plugin/plugin.d.ts +39 -0
  156. package/dist/plugin/plugin.d.ts.map +1 -0
  157. package/dist/plugin/plugin.js +10 -0
  158. package/dist/plugin/plugin.js.map +1 -0
  159. package/dist/schema/default.d.ts +163 -0
  160. package/dist/schema/default.d.ts.map +1 -0
  161. package/dist/schema/default.js +94 -0
  162. package/dist/schema/default.js.map +1 -0
  163. package/dist/schema/index.d.ts +2 -0
  164. package/dist/schema/index.d.ts.map +1 -0
  165. package/dist/schema/index.js +2 -0
  166. package/dist/schema/index.js.map +1 -0
  167. package/dist/serialize/index.d.ts +2 -0
  168. package/dist/serialize/index.d.ts.map +1 -0
  169. package/dist/serialize/index.js +2 -0
  170. package/dist/serialize/index.js.map +1 -0
  171. package/dist/serialize/json.d.ts +15 -0
  172. package/dist/serialize/json.d.ts.map +1 -0
  173. package/dist/serialize/json.js +23 -0
  174. package/dist/serialize/json.js.map +1 -0
  175. package/dist/state/all-selection.d.ts +11 -0
  176. package/dist/state/all-selection.d.ts.map +1 -0
  177. package/dist/state/all-selection.js +17 -0
  178. package/dist/state/all-selection.js.map +1 -0
  179. package/dist/state/cell-selection.d.ts +30 -0
  180. package/dist/state/cell-selection.d.ts.map +1 -0
  181. package/dist/state/cell-selection.js +38 -0
  182. package/dist/state/cell-selection.js.map +1 -0
  183. package/dist/state/editor-state.d.ts +46 -0
  184. package/dist/state/editor-state.d.ts.map +1 -0
  185. package/dist/state/editor-state.js +211 -0
  186. package/dist/state/editor-state.js.map +1 -0
  187. package/dist/state/index.d.ts +7 -0
  188. package/dist/state/index.d.ts.map +1 -0
  189. package/dist/state/index.js +7 -0
  190. package/dist/state/index.js.map +1 -0
  191. package/dist/state/node-selection.d.ts +16 -0
  192. package/dist/state/node-selection.d.ts.map +1 -0
  193. package/dist/state/node-selection.js +51 -0
  194. package/dist/state/node-selection.js.map +1 -0
  195. package/dist/state/selection.d.ts +29 -0
  196. package/dist/state/selection.d.ts.map +1 -0
  197. package/dist/state/selection.js +24 -0
  198. package/dist/state/selection.js.map +1 -0
  199. package/dist/state/text-selection.d.ts +10 -0
  200. package/dist/state/text-selection.d.ts.map +1 -0
  201. package/dist/state/text-selection.js +26 -0
  202. package/dist/state/text-selection.js.map +1 -0
  203. package/dist/transform/attr-step.d.ts +16 -0
  204. package/dist/transform/attr-step.d.ts.map +1 -0
  205. package/dist/transform/attr-step.js +98 -0
  206. package/dist/transform/attr-step.js.map +1 -0
  207. package/dist/transform/index.d.ts +10 -0
  208. package/dist/transform/index.d.ts.map +1 -0
  209. package/dist/transform/index.js +10 -0
  210. package/dist/transform/index.js.map +1 -0
  211. package/dist/transform/mapping.d.ts +44 -0
  212. package/dist/transform/mapping.d.ts.map +1 -0
  213. package/dist/transform/mapping.js +101 -0
  214. package/dist/transform/mapping.js.map +1 -0
  215. package/dist/transform/mark-step.d.ts +27 -0
  216. package/dist/transform/mark-step.d.ts.map +1 -0
  217. package/dist/transform/mark-step.js +146 -0
  218. package/dist/transform/mark-step.js.map +1 -0
  219. package/dist/transform/replace-around-step.d.ts +35 -0
  220. package/dist/transform/replace-around-step.d.ts.map +1 -0
  221. package/dist/transform/replace-around-step.js +144 -0
  222. package/dist/transform/replace-around-step.js.map +1 -0
  223. package/dist/transform/replace-step.d.ts +17 -0
  224. package/dist/transform/replace-step.d.ts.map +1 -0
  225. package/dist/transform/replace-step.js +72 -0
  226. package/dist/transform/replace-step.js.map +1 -0
  227. package/dist/transform/replace.d.ts +18 -0
  228. package/dist/transform/replace.d.ts.map +1 -0
  229. package/dist/transform/replace.js +132 -0
  230. package/dist/transform/replace.js.map +1 -0
  231. package/dist/transform/set-page-meta-step.d.ts +42 -0
  232. package/dist/transform/set-page-meta-step.d.ts.map +1 -0
  233. package/dist/transform/set-page-meta-step.js +75 -0
  234. package/dist/transform/set-page-meta-step.js.map +1 -0
  235. package/dist/transform/step.d.ts +34 -0
  236. package/dist/transform/step.d.ts.map +1 -0
  237. package/dist/transform/step.js +23 -0
  238. package/dist/transform/step.js.map +1 -0
  239. package/dist/transform/transaction.d.ts +20 -0
  240. package/dist/transform/transaction.d.ts.map +1 -0
  241. package/dist/transform/transaction.js +38 -0
  242. package/dist/transform/transaction.js.map +1 -0
  243. package/dist/version.d.ts +2 -0
  244. package/dist/version.d.ts.map +1 -0
  245. package/dist/version.js +5 -0
  246. package/dist/version.js.map +1 -0
  247. package/dist/view/cell-drag.d.ts +33 -0
  248. package/dist/view/cell-drag.d.ts.map +1 -0
  249. package/dist/view/cell-drag.js +177 -0
  250. package/dist/view/cell-drag.js.map +1 -0
  251. package/dist/view/clipboard.d.ts +5 -0
  252. package/dist/view/clipboard.d.ts.map +1 -0
  253. package/dist/view/clipboard.js +97 -0
  254. package/dist/view/clipboard.js.map +1 -0
  255. package/dist/view/default-renderer.d.ts +3 -0
  256. package/dist/view/default-renderer.d.ts.map +1 -0
  257. package/dist/view/default-renderer.js +142 -0
  258. package/dist/view/default-renderer.js.map +1 -0
  259. package/dist/view/dom-spec.d.ts +11 -0
  260. package/dist/view/dom-spec.d.ts.map +1 -0
  261. package/dist/view/dom-spec.js +32 -0
  262. package/dist/view/dom-spec.js.map +1 -0
  263. package/dist/view/editor-view.d.ts +55 -0
  264. package/dist/view/editor-view.d.ts.map +1 -0
  265. package/dist/view/editor-view.js +143 -0
  266. package/dist/view/editor-view.js.map +1 -0
  267. package/dist/view/image-resize.d.ts +37 -0
  268. package/dist/view/image-resize.d.ts.map +1 -0
  269. package/dist/view/image-resize.js +191 -0
  270. package/dist/view/image-resize.js.map +1 -0
  271. package/dist/view/index.d.ts +15 -0
  272. package/dist/view/index.d.ts.map +1 -0
  273. package/dist/view/index.js +15 -0
  274. package/dist/view/index.js.map +1 -0
  275. package/dist/view/input-pipeline.d.ts +24 -0
  276. package/dist/view/input-pipeline.d.ts.map +1 -0
  277. package/dist/view/input-pipeline.js +226 -0
  278. package/dist/view/input-pipeline.js.map +1 -0
  279. package/dist/view/mutation-observer.d.ts +17 -0
  280. package/dist/view/mutation-observer.d.ts.map +1 -0
  281. package/dist/view/mutation-observer.js +62 -0
  282. package/dist/view/mutation-observer.js.map +1 -0
  283. package/dist/view/page-slots.d.ts +56 -0
  284. package/dist/view/page-slots.d.ts.map +1 -0
  285. package/dist/view/page-slots.js +230 -0
  286. package/dist/view/page-slots.js.map +1 -0
  287. package/dist/view/paginator.d.ts +17 -0
  288. package/dist/view/paginator.d.ts.map +1 -0
  289. package/dist/view/paginator.js +93 -0
  290. package/dist/view/paginator.js.map +1 -0
  291. package/dist/view/print.d.ts +42 -0
  292. package/dist/view/print.d.ts.map +1 -0
  293. package/dist/view/print.js +70 -0
  294. package/dist/view/print.js.map +1 -0
  295. package/dist/view/reconcile.d.ts +16 -0
  296. package/dist/view/reconcile.d.ts.map +1 -0
  297. package/dist/view/reconcile.js +158 -0
  298. package/dist/view/reconcile.js.map +1 -0
  299. package/dist/view/renderer.d.ts +31 -0
  300. package/dist/view/renderer.d.ts.map +1 -0
  301. package/dist/view/renderer.js +89 -0
  302. package/dist/view/renderer.js.map +1 -0
  303. package/dist/view/selection-sync.d.ts +35 -0
  304. package/dist/view/selection-sync.d.ts.map +1 -0
  305. package/dist/view/selection-sync.js +324 -0
  306. package/dist/view/selection-sync.js.map +1 -0
  307. package/dist/view/table-resize.d.ts +41 -0
  308. package/dist/view/table-resize.d.ts.map +1 -0
  309. package/dist/view/table-resize.js +216 -0
  310. package/dist/view/table-resize.js.map +1 -0
  311. package/package.json +93 -0
  312. package/styles/base.css +269 -0
  313. package/styles/dark.css +36 -0
  314. package/styles/light.css +13 -0
  315. package/styles/page.css +93 -0
  316. package/styles/print-a4.css +87 -0
  317. package/styles/print.css +88 -0
@@ -0,0 +1,158 @@
1
+ import { renderSpec as renderSpecImport, HOLE_MARKER_ATTR as HOLE_MARKER_ATTR_IMPORT } from './dom-spec.js';
2
+ import { renderNode } from './renderer.js';
3
+ /**
4
+ * Reconcile the DOM children of `domRoot` so they represent `newDoc`'s content,
5
+ * starting from a DOM that already represents `oldDoc`'s content.
6
+ *
7
+ * Algorithm (v0.1):
8
+ * - If oldDoc === newDoc (reference equality, which happens whenever the
9
+ * immutable model wasn't touched): no-op.
10
+ * - Otherwise walk children in parallel; if a child differs, replace it
11
+ * wholesale (re-render the differing subtree). More sophisticated diffing
12
+ * (Levenshtein on child arrays, in-place updates for same-type nodes)
13
+ * lands in Phase 7 polish.
14
+ */
15
+ export function reconcile(domRoot, oldDoc, newDoc, renderer) {
16
+ if (oldDoc === newDoc)
17
+ return;
18
+ reconcileChildren(domRoot, oldDoc, newDoc, renderer, 0);
19
+ }
20
+ function reconcileChildren(parentEl, oldParent, newParent, renderer, baseAbsPos) {
21
+ const oldChildren = oldParent.content.children;
22
+ const newChildren = newParent.content.children;
23
+ const max = Math.max(oldChildren.length, newChildren.length);
24
+ let modelOffset = baseAbsPos + 1; // +1 to step past the parent's [open] token
25
+ for (let i = 0; i < max; i++) {
26
+ const oldChild = oldChildren[i];
27
+ const newChild = newChildren[i];
28
+ const domChild = parentEl.childNodes[i];
29
+ if (!newChild) {
30
+ // Trailing children removed
31
+ if (domChild)
32
+ parentEl.removeChild(domChild);
33
+ continue;
34
+ }
35
+ if (!oldChild) {
36
+ // New child appended
37
+ const fresh = renderNode(document, renderer, newChild, modelOffset);
38
+ parentEl.appendChild(fresh);
39
+ modelOffset += newChild.nodeSize;
40
+ continue;
41
+ }
42
+ if (oldChild === newChild) {
43
+ modelOffset += newChild.nodeSize;
44
+ continue;
45
+ }
46
+ if (sameShape(oldChild, newChild) && !newChild.isText && !newChild.isAtom && domChild) {
47
+ // Same node type + attrs — descend.
48
+ reconcileChildren(domChild, oldChild, newChild, renderer, modelOffset);
49
+ }
50
+ else if (oldChild.isText &&
51
+ newChild.isText &&
52
+ oldChild.type === newChild.type &&
53
+ sameMarkArrays(oldChild.marks, newChild.marks) &&
54
+ domChild) {
55
+ // Text leaf with same marks but different text — update .data in place.
56
+ updateTextLeaf(domChild, newChild.text);
57
+ }
58
+ else if (oldChild.isText &&
59
+ newChild.isText &&
60
+ oldChild.type === newChild.type &&
61
+ oldChild.text === newChild.text &&
62
+ domChild) {
63
+ // Same text, different marks — rewrap in place, preserving the Text node.
64
+ const innerText = findInnerTextNode(domChild);
65
+ if (innerText) {
66
+ // Record insertion point BEFORE wrapWithMarks detaches innerText
67
+ // (which may BE domChild itself when the leaf had no marks).
68
+ const next = domChild.nextSibling;
69
+ if (domChild.parentNode === parentEl)
70
+ parentEl.removeChild(domChild);
71
+ const wrapped = wrapWithMarks(innerText, newChild.marks, renderer);
72
+ if (next)
73
+ parentEl.insertBefore(wrapped, next);
74
+ else
75
+ parentEl.appendChild(wrapped);
76
+ }
77
+ }
78
+ else {
79
+ // Different shape, or atom — replace.
80
+ const fresh = renderNode(document, renderer, newChild, modelOffset);
81
+ if (domChild)
82
+ parentEl.replaceChild(fresh, domChild);
83
+ else
84
+ parentEl.appendChild(fresh);
85
+ }
86
+ modelOffset += newChild.nodeSize;
87
+ }
88
+ }
89
+ function sameShape(a, b) {
90
+ if (a.type !== b.type)
91
+ return false;
92
+ const ak = Object.keys(a.attrs), bk = Object.keys(b.attrs);
93
+ if (ak.length !== bk.length)
94
+ return false;
95
+ for (const k of ak) {
96
+ if (!(k in b.attrs))
97
+ return false;
98
+ if (!Object.is(a.attrs[k], b.attrs[k]))
99
+ return false;
100
+ }
101
+ return true;
102
+ }
103
+ function sameMarkArrays(a, b) {
104
+ if (a.length !== b.length)
105
+ return false;
106
+ for (let i = 0; i < a.length; i++) {
107
+ if (!a[i].eq(b[i]))
108
+ return false;
109
+ }
110
+ return true;
111
+ }
112
+ function updateTextLeaf(domNode, newText) {
113
+ // The rendered text leaf is either a Text node (when no marks) or an Element
114
+ // wrapping a Text node (one or more marks). Walk down to the innermost text.
115
+ let cur = domNode;
116
+ while (cur.nodeType !== 3 /* TEXT_NODE */) {
117
+ if (cur.firstChild)
118
+ cur = cur.firstChild;
119
+ else
120
+ break;
121
+ }
122
+ if (cur.nodeType === 3) {
123
+ ;
124
+ cur.data = newText;
125
+ }
126
+ }
127
+ function findInnerTextNode(domNode) {
128
+ let cur = domNode;
129
+ while (cur.nodeType !== 3 /* TEXT_NODE */) {
130
+ if (cur.firstChild)
131
+ cur = cur.firstChild;
132
+ else
133
+ return null;
134
+ }
135
+ return cur;
136
+ }
137
+ function wrapWithMarks(textNode, marks, renderer) {
138
+ // Detach from any current parent.
139
+ textNode.parentNode?.removeChild(textNode);
140
+ let outer = textNode;
141
+ for (let i = marks.length - 1; i >= 0; i--) {
142
+ const mark = marks[i];
143
+ const markFn = renderer.getMarkRenderer(mark.type.name);
144
+ if (!markFn)
145
+ continue;
146
+ const wrapped = renderSpecImport(textNode.ownerDocument, markFn(mark));
147
+ const hole = wrapped.hasAttribute(HOLE_MARKER_ATTR_IMPORT)
148
+ ? wrapped
149
+ : wrapped.querySelector(`[${HOLE_MARKER_ATTR_IMPORT}]`);
150
+ if (hole)
151
+ hole.appendChild(outer);
152
+ else
153
+ wrapped.appendChild(outer);
154
+ outer = wrapped;
155
+ }
156
+ return outer;
157
+ }
158
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/view/reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { Mark } from '../model/mark.js';
2
+ import type { DxNode } from '../model/node.js';
3
+ import { type DOMSpec } from './dom-spec.js';
4
+ export type NodeRenderFn = (node: DxNode) => DOMSpec;
5
+ export type MarkRenderFn = (mark: Mark) => DOMSpec;
6
+ export interface RendererSpec {
7
+ readonly nodes: Readonly<Record<string, NodeRenderFn>>;
8
+ readonly marks: Readonly<Record<string, MarkRenderFn>>;
9
+ }
10
+ export declare class Renderer {
11
+ readonly spec: RendererSpec;
12
+ constructor(spec: RendererSpec);
13
+ getNodeRenderer(name: string): NodeRenderFn;
14
+ getMarkRenderer(name: string): MarkRenderFn | undefined;
15
+ }
16
+ export interface NodeRef {
17
+ readonly node: DxNode;
18
+ /** Absolute document position of this node's [open] boundary (or -1 for root). */
19
+ readonly pos: number;
20
+ }
21
+ export declare function getNodeRef(el: Node): NodeRef | undefined;
22
+ /**
23
+ * Render a model node into a DOM subtree, recursively populating the content
24
+ * hole at each level. Attaches __dxNode/__pos back-pointers (via getNodeRef)
25
+ * for selection translation.
26
+ *
27
+ * `absPos` is the absolute document position of this node (where its [open]
28
+ * token sits). For the root doc, callers pass -1.
29
+ */
30
+ export declare function renderNode(doc: Document, renderer: Renderer, node: DxNode, absPos?: number): Node;
31
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/view/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAgC,KAAK,OAAO,EAAE,MAAM,eAAe,CAAA;AAE1E,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;AACpD,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAA;AAElD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;CACvD;AAED,qBAAa,QAAQ;IACP,QAAQ,CAAC,IAAI,EAAE,YAAY;gBAAlB,IAAI,EAAE,YAAY;IAEvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAM3C,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD;AAID,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,CAExD;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,IAAI,CA6C7F"}
@@ -0,0 +1,89 @@
1
+ import { renderSpec, HOLE_MARKER_ATTR } from './dom-spec.js';
2
+ export class Renderer {
3
+ spec;
4
+ constructor(spec) {
5
+ this.spec = spec;
6
+ }
7
+ getNodeRenderer(name) {
8
+ const fn = this.spec.nodes[name];
9
+ if (!fn)
10
+ throw new Error(`Renderer: no renderer for node type '${name}'`);
11
+ return fn;
12
+ }
13
+ getMarkRenderer(name) {
14
+ return this.spec.marks[name];
15
+ }
16
+ }
17
+ const NODE_REF = Symbol.for('@doxi/core.view.nodeRef');
18
+ export function getNodeRef(el) {
19
+ return el[NODE_REF];
20
+ }
21
+ function setNodeRef(el, ref) {
22
+ ;
23
+ el[NODE_REF] = ref;
24
+ }
25
+ /**
26
+ * Render a model node into a DOM subtree, recursively populating the content
27
+ * hole at each level. Attaches __dxNode/__pos back-pointers (via getNodeRef)
28
+ * for selection translation.
29
+ *
30
+ * `absPos` is the absolute document position of this node (where its [open]
31
+ * token sits). For the root doc, callers pass -1.
32
+ */
33
+ export function renderNode(doc, renderer, node, absPos = -1) {
34
+ if (node.isText) {
35
+ const text = node.text;
36
+ let outer = doc.createTextNode(text);
37
+ for (let i = node.marks.length - 1; i >= 0; i--) {
38
+ const mark = node.marks[i];
39
+ const markFn = renderer.getMarkRenderer(mark.type.name);
40
+ if (!markFn)
41
+ continue;
42
+ const wrapped = renderSpec(doc, markFn(mark));
43
+ const hole = findHole(wrapped);
44
+ if (hole)
45
+ hole.appendChild(outer);
46
+ else
47
+ wrapped.appendChild(outer);
48
+ outer = wrapped;
49
+ }
50
+ setNodeRef(outer, { node, pos: absPos });
51
+ return outer;
52
+ }
53
+ const nodeFn = renderer.getNodeRenderer(node.type.name);
54
+ const root = renderSpec(doc, nodeFn(node));
55
+ setNodeRef(root, { node, pos: absPos });
56
+ if (!node.isAtom) {
57
+ const hole = findHole(root) ?? root;
58
+ let childPos = absPos + 1; // +1 to step past this node's [open] token
59
+ for (let i = 0; i < node.content.childCount; i++) {
60
+ const child = node.content.child(i);
61
+ hole.appendChild(renderNode(doc, renderer, child, childPos));
62
+ childPos += child.nodeSize;
63
+ }
64
+ hole.removeAttribute(HOLE_MARKER_ATTR);
65
+ // Empty inline-content blocks (paragraph/heading/etc.) need a placeholder
66
+ // so the line has visible height and contenteditable can place a caret
67
+ // inside it. The doc node itself is exempt.
68
+ if (node.content.childCount === 0 &&
69
+ node.type.name !== 'doc' &&
70
+ acceptsInlineContent(node)) {
71
+ const br = doc.createElement('br');
72
+ br.setAttribute('data-dx-placeholder', 'true');
73
+ hole.appendChild(br);
74
+ }
75
+ }
76
+ return root;
77
+ }
78
+ function acceptsInlineContent(node) {
79
+ const content = node.type.spec.content;
80
+ if (!content)
81
+ return false;
82
+ return content.includes('inline');
83
+ }
84
+ function findHole(el) {
85
+ if (el.hasAttribute(HOLE_MARKER_ATTR))
86
+ return el;
87
+ return el.querySelector(`[${HOLE_MARKER_ATTR}]`);
88
+ }
89
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/view/renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { DxNode } from '../model/node.js';
2
+ import type { Selection } from '../state/selection.js';
3
+ /**
4
+ * Move the host window's selection to mirror `sel`. Walks the rendered DOM
5
+ * subtree under `root`, finds the text node and offset corresponding to
6
+ * sel.anchor/head, and calls Selection.setBaseAndExtent.
7
+ *
8
+ * Positions on a block boundary (depth 0 of the model) snap to the nearest
9
+ * inline text node.
10
+ *
11
+ * For a CellSelection, paints `dx-cell-selected` on every cell in the
12
+ * rectangle and clears the native DOM selection (no caret) instead.
13
+ */
14
+ export declare function renderSelection(root: Element, sel: Selection, doc: DxNode): void;
15
+ export interface DomLoc {
16
+ readonly node: Node;
17
+ readonly offset: number;
18
+ }
19
+ /**
20
+ * Find the DOM text node + offset that corresponds to model position `pos`.
21
+ * Strategy: walk the DOM in document order. Each text node carries a
22
+ * NodeRef (set by renderer.ts) pointing back at the model node and the
23
+ * model position of its [open] boundary. We match by model position.
24
+ *
25
+ * Exported as `locateDomFromModelPos` for collab presence overlays — see
26
+ * `@doxi/collab` `installRemoteCursors`.
27
+ */
28
+ export declare function locateDomFromModelPos(root: Element, pos: number, doc: DxNode): DomLoc | null;
29
+ import { TextSelection } from '../state/text-selection.js';
30
+ /**
31
+ * Translate the host window's current selection into a model TextSelection.
32
+ * Returns null when the selection is not inside `root` (or there is none).
33
+ */
34
+ export declare function resolveDOMSelection(root: Element, _doc: DxNode): TextSelection | null;
35
+ //# sourceMappingURL=selection-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-sync.d.ts","sourceRoot":"","sources":["../../src/view/selection-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAI9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAMtD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAyBhF;AA4FD,MAAM,WAAW,MAAM;IAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAExE;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE5F;AAsED,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE1D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAmCrF"}
@@ -0,0 +1,324 @@
1
+ import { tableGrid } from '../model/table-grid.js';
2
+ import { CellSelection } from '../state/cell-selection.js';
3
+ import { NodeSelection } from '../state/node-selection.js';
4
+ import { getNodeRef } from './renderer.js';
5
+ const CELL_SELECTED_CLASS = 'dx-cell-selected';
6
+ const IMAGE_SELECTED_CLASS = 'dx-image-selected';
7
+ /**
8
+ * Move the host window's selection to mirror `sel`. Walks the rendered DOM
9
+ * subtree under `root`, finds the text node and offset corresponding to
10
+ * sel.anchor/head, and calls Selection.setBaseAndExtent.
11
+ *
12
+ * Positions on a block boundary (depth 0 of the model) snap to the nearest
13
+ * inline text node.
14
+ *
15
+ * For a CellSelection, paints `dx-cell-selected` on every cell in the
16
+ * rectangle and clears the native DOM selection (no caret) instead.
17
+ */
18
+ export function renderSelection(root, sel, doc) {
19
+ const win = root.ownerDocument.defaultView;
20
+ if (!win)
21
+ return;
22
+ if (sel instanceof CellSelection) {
23
+ paintCellSelection(root, sel, doc);
24
+ clearImageSelectionPainting(root);
25
+ win.getSelection()?.removeAllRanges();
26
+ return;
27
+ }
28
+ if (sel instanceof NodeSelection) {
29
+ // Only image NodeSelections get painted today; other node selections
30
+ // (hr, etc.) fall back to clearing the image highlight without painting.
31
+ paintImageSelection(root, sel);
32
+ clearCellSelectionPainting(root);
33
+ win.getSelection()?.removeAllRanges();
34
+ return;
35
+ }
36
+ clearCellSelectionPainting(root);
37
+ clearImageSelectionPainting(root);
38
+ const anchor = locate(root, sel.anchor, doc);
39
+ const head = locate(root, sel.head, doc);
40
+ if (!anchor || !head)
41
+ return;
42
+ const sel2 = win.getSelection();
43
+ if (!sel2)
44
+ return;
45
+ sel2.setBaseAndExtent(anchor.node, anchor.offset, head.node, head.offset);
46
+ }
47
+ /**
48
+ * Paint `dx-cell-selected` on every cell inside the bounding rectangle of
49
+ * the CellSelection, and remove the class from every other cell under `root`.
50
+ *
51
+ * Strategy: locate the anchor and head cell DOM elements via the NodeRef
52
+ * back-pointers attached by the renderer, walk up to their shared `<table>`,
53
+ * find the master positions in `tableGrid`, and call `cellPositionsInRect`
54
+ * over the bounding rectangle. Match those positions against the cell DOM
55
+ * elements inside the table.
56
+ */
57
+ function paintCellSelection(root, sel, doc) {
58
+ // Find every cell DOM under the root once.
59
+ const allCells = Array.from(root.querySelectorAll('.dx-table-cell'));
60
+ const anchorEl = allCells.find((el) => getNodeRef(el)?.pos === sel.anchorCell) ?? null;
61
+ const headEl = allCells.find((el) => getNodeRef(el)?.pos === sel.headCell) ?? null;
62
+ if (!anchorEl || !headEl) {
63
+ // Couldn't locate the cells — clear stale highlighting and bail.
64
+ clearCellSelectionPainting(root);
65
+ return;
66
+ }
67
+ const anchorTable = anchorEl.closest('table.dx-table');
68
+ const headTable = headEl.closest('table.dx-table');
69
+ if (!anchorTable || anchorTable !== headTable) {
70
+ clearCellSelectionPainting(root);
71
+ return;
72
+ }
73
+ const tableRef = getNodeRef(anchorTable);
74
+ if (!tableRef || tableRef.pos < 0) {
75
+ clearCellSelectionPainting(root);
76
+ return;
77
+ }
78
+ const grid = tableGrid(tableRef.node, tableRef.pos);
79
+ const anchorCell = grid.byPos(sel.anchorCell);
80
+ const headCell = grid.byPos(sel.headCell);
81
+ if (!anchorCell || !headCell) {
82
+ clearCellSelectionPainting(root);
83
+ return;
84
+ }
85
+ const r0 = Math.min(anchorCell.row, headCell.row);
86
+ const r1 = Math.max(anchorCell.row + anchorCell.rowspan - 1, headCell.row + headCell.rowspan - 1);
87
+ const c0 = Math.min(anchorCell.col, headCell.col);
88
+ const c1 = Math.max(anchorCell.col + anchorCell.colspan - 1, headCell.col + headCell.colspan - 1);
89
+ const selectedPositions = new Set(grid.cellPositionsInRect(r0, r1, c0, c1));
90
+ // Clear painting on cells outside the table; mark inside the table.
91
+ for (const el of allCells) {
92
+ const pos = getNodeRef(el)?.pos;
93
+ if (pos !== undefined && selectedPositions.has(pos) && el.closest('table.dx-table') === anchorTable) {
94
+ el.classList.add(CELL_SELECTED_CLASS);
95
+ }
96
+ else {
97
+ el.classList.remove(CELL_SELECTED_CLASS);
98
+ }
99
+ }
100
+ // Reference doc to keep the signature stable for callers that pass it.
101
+ void doc;
102
+ }
103
+ function clearCellSelectionPainting(root) {
104
+ const painted = root.querySelectorAll(`.${CELL_SELECTED_CLASS}`);
105
+ for (let i = 0; i < painted.length; i++) {
106
+ painted.item(i).classList.remove(CELL_SELECTED_CLASS);
107
+ }
108
+ }
109
+ /**
110
+ * Paint `dx-image-selected` on the `.dx-image` whose NodeRef.pos matches
111
+ * `sel.anchor`, and remove it from every other image under `root`. Used by
112
+ * NodeSelection to give the user a visible affordance for the selected image.
113
+ *
114
+ * If no matching image is found (e.g., the NodeSelection targets a non-image
115
+ * atomic node), this still clears stale highlights and bails — the model
116
+ * selection remains valid even without DOM affordance.
117
+ */
118
+ function paintImageSelection(root, sel) {
119
+ const allImages = Array.from(root.querySelectorAll('.dx-image'));
120
+ for (const el of allImages) {
121
+ if (getNodeRef(el)?.pos === sel.anchor) {
122
+ el.classList.add(IMAGE_SELECTED_CLASS);
123
+ }
124
+ else {
125
+ el.classList.remove(IMAGE_SELECTED_CLASS);
126
+ }
127
+ }
128
+ }
129
+ function clearImageSelectionPainting(root) {
130
+ const painted = root.querySelectorAll(`.${IMAGE_SELECTED_CLASS}`);
131
+ for (let i = 0; i < painted.length; i++) {
132
+ painted.item(i).classList.remove(IMAGE_SELECTED_CLASS);
133
+ }
134
+ }
135
+ /**
136
+ * Find the DOM text node + offset that corresponds to model position `pos`.
137
+ * Strategy: walk the DOM in document order. Each text node carries a
138
+ * NodeRef (set by renderer.ts) pointing back at the model node and the
139
+ * model position of its [open] boundary. We match by model position.
140
+ *
141
+ * Exported as `locateDomFromModelPos` for collab presence overlays — see
142
+ * `@doxi/collab` `installRemoteCursors`.
143
+ */
144
+ export function locateDomFromModelPos(root, pos, doc) {
145
+ return locate(root, pos, doc);
146
+ }
147
+ function locate(root, pos, _doc) {
148
+ for (const textNode of collectTextNodes(root)) {
149
+ const ref = findRefForText(textNode);
150
+ if (!ref)
151
+ continue;
152
+ const start = ref.pos;
153
+ const end = start + textNode.data.length;
154
+ if (pos >= start && pos <= end) {
155
+ return { node: textNode, offset: pos - start };
156
+ }
157
+ }
158
+ // Fallback for positions inside empty blocks (e.g., the tail block produced
159
+ // by splitBlock when the cursor was at the end of a line). Walk elements
160
+ // with NodeRefs and pick the deepest one whose inside-range contains pos.
161
+ let best = null;
162
+ let bestStart = -1;
163
+ const walk = (el) => {
164
+ const ref = getNodeRef(el);
165
+ if (ref) {
166
+ const start = ref.pos;
167
+ const end = start + ref.node.nodeSize;
168
+ const insideStart = start + 1;
169
+ const insideEnd = end - 1;
170
+ if (pos >= insideStart && pos <= insideEnd && start > bestStart) {
171
+ best = el;
172
+ bestStart = start;
173
+ }
174
+ }
175
+ for (let i = 0; i < el.children.length; i++) {
176
+ const child = el.children[i];
177
+ if (child)
178
+ walk(child);
179
+ }
180
+ };
181
+ walk(root);
182
+ if (best) {
183
+ return { node: best, offset: 0 };
184
+ }
185
+ if (root.lastChild)
186
+ return { node: root.lastChild, offset: 0 };
187
+ return { node: root, offset: 0 };
188
+ }
189
+ function collectTextNodes(root) {
190
+ const out = [];
191
+ const recur = (n) => {
192
+ if (n.nodeType === 3 /* TEXT_NODE */) {
193
+ out.push(n);
194
+ return;
195
+ }
196
+ for (let i = 0; i < n.childNodes.length; i++) {
197
+ const c = n.childNodes[i];
198
+ if (c)
199
+ recur(c);
200
+ }
201
+ };
202
+ recur(root);
203
+ return out;
204
+ }
205
+ function findRefForText(textNode) {
206
+ // The renderer attached NodeRef to the OUTER text element (the unwrapped Text
207
+ // node OR the outermost mark span). Walk up the DOM until we find one.
208
+ let cur = textNode;
209
+ while (cur) {
210
+ const ref = getNodeRef(cur);
211
+ if (ref)
212
+ return ref;
213
+ cur = cur.parentNode;
214
+ }
215
+ return undefined;
216
+ }
217
+ import { TextSelection } from '../state/text-selection.js';
218
+ /**
219
+ * Translate the host window's current selection into a model TextSelection.
220
+ * Returns null when the selection is not inside `root` (or there is none).
221
+ */
222
+ export function resolveDOMSelection(root, _doc) {
223
+ const win = root.ownerDocument.defaultView;
224
+ if (!win)
225
+ return null;
226
+ const domSel = win.getSelection();
227
+ if (!domSel || domSel.rangeCount === 0)
228
+ return null;
229
+ // Prefer anchor/focus so reverse selections (head < anchor when the user
230
+ // drags right-to-left or shift-arrows backward) are preserved in the
231
+ // model. Fall back to the range's start/end when anchor/focus aren't
232
+ // usable (e.g., happy-dom doesn't expose focusOffset reliably for
233
+ // selections set via setBaseAndExtent).
234
+ const range = domSel.getRangeAt(0);
235
+ let aNode = domSel.anchorNode;
236
+ let aOff = domSel.anchorOffset;
237
+ let fNode = domSel.focusNode;
238
+ let fOff = domSel.focusOffset;
239
+ const haveAnchorFocus = aNode != null && fNode != null && root.contains(aNode) && root.contains(fNode);
240
+ // happy-dom only updates anchor when a selection is created via addRange or
241
+ // setBaseAndExtent; focus stays equal to anchor. Detect that as "AF is
242
+ // collapsed but the range isn't" and fall back to range start/end (loses
243
+ // direction information, but is at least correct).
244
+ const afAppearsCollapsed = aNode === fNode && aOff === fOff;
245
+ const rangeNotCollapsed = !range.collapsed;
246
+ if (!haveAnchorFocus || (afAppearsCollapsed && rangeNotCollapsed)) {
247
+ aNode = range.startContainer;
248
+ aOff = range.startOffset;
249
+ fNode = range.endContainer;
250
+ fOff = range.endOffset;
251
+ }
252
+ if (!aNode || !fNode)
253
+ return null;
254
+ if (!root.contains(aNode) || !root.contains(fNode))
255
+ return null;
256
+ const anchor = domOffsetToModelPos(aNode, aOff);
257
+ const head = domOffsetToModelPos(fNode, fOff);
258
+ if (anchor === null || head === null)
259
+ return null;
260
+ return new TextSelection(anchor, head);
261
+ }
262
+ function domOffsetToModelPos(node, offset) {
263
+ if (node.nodeType === 3 /* TEXT_NODE */) {
264
+ const ref = findAncestorRef(node);
265
+ if (!ref)
266
+ return null;
267
+ return ref.pos + offset;
268
+ }
269
+ // Element selection. The browser uses element + child-offset when no text
270
+ // node is at the precise click point (e.g., user clicked past the end of a
271
+ // line, or the caret sits inside an empty block whose only DOM child is a
272
+ // <br> placeholder).
273
+ const childAt = node.childNodes[offset];
274
+ if (childAt) {
275
+ const tDesc = lastTextDescendant(childAt);
276
+ if (tDesc) {
277
+ const ref = findAncestorRef(tDesc);
278
+ if (ref)
279
+ return ref.pos;
280
+ }
281
+ }
282
+ const childBefore = node.childNodes[offset - 1];
283
+ if (childBefore) {
284
+ const last = lastTextDescendant(childBefore);
285
+ if (last) {
286
+ const ref = findAncestorRef(last);
287
+ if (ref)
288
+ return ref.pos + last.data.length;
289
+ }
290
+ }
291
+ // Fall back to the nearest ancestor ref. For text refs, `pos` is already
292
+ // the start of text content. For container refs (e.g., an empty <p>),
293
+ // `pos` is the container's [open] token — a depth-0 boundary that the
294
+ // selectionchange guard rejects. Nudge to the first inside position so the
295
+ // caret is on an editable spot inside the container.
296
+ const ref = findAncestorRef(node);
297
+ if (!ref)
298
+ return null;
299
+ return ref.node.isText ? ref.pos : ref.pos + 1;
300
+ }
301
+ function lastTextDescendant(node) {
302
+ if (node.nodeType === 3)
303
+ return node;
304
+ for (let i = node.childNodes.length - 1; i >= 0; i--) {
305
+ const c = node.childNodes[i];
306
+ if (!c)
307
+ continue;
308
+ const t = lastTextDescendant(c);
309
+ if (t)
310
+ return t;
311
+ }
312
+ return null;
313
+ }
314
+ function findAncestorRef(node) {
315
+ let cur = node;
316
+ while (cur) {
317
+ const ref = getNodeRef(cur);
318
+ if (ref)
319
+ return ref;
320
+ cur = cur.parentNode;
321
+ }
322
+ return undefined;
323
+ }
324
+ //# sourceMappingURL=selection-sync.js.map