@blankdotpage/cake 0.1.8 → 0.1.11

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 (172) hide show
  1. package/README.md +68 -1
  2. package/dist/cake/clipboard.d.ts +1 -0
  3. package/dist/cake/clipboard.d.ts.map +1 -0
  4. package/dist/cake/clipboard.js +391 -0
  5. package/dist/cake/core/mapping/cursor-source-map.d.ts +1 -0
  6. package/dist/cake/core/mapping/cursor-source-map.d.ts.map +1 -0
  7. package/dist/cake/core/mapping/cursor-source-map.js +146 -0
  8. package/dist/cake/core/runtime.d.ts +1 -0
  9. package/dist/cake/core/runtime.d.ts.map +1 -0
  10. package/dist/cake/core/runtime.js +1758 -0
  11. package/dist/cake/core/types.d.ts +1 -0
  12. package/dist/cake/core/types.d.ts.map +1 -0
  13. package/dist/cake/core/types.js +1 -0
  14. package/dist/cake/dom/dom-map.d.ts +1 -0
  15. package/dist/cake/dom/dom-map.d.ts.map +1 -0
  16. package/dist/cake/dom/dom-map.js +151 -0
  17. package/dist/cake/dom/dom-selection.d.ts +1 -0
  18. package/dist/cake/dom/dom-selection.d.ts.map +1 -0
  19. package/dist/cake/dom/dom-selection.js +216 -0
  20. package/dist/cake/dom/render.d.ts +1 -0
  21. package/dist/cake/dom/render.d.ts.map +1 -0
  22. package/dist/cake/dom/render.js +470 -0
  23. package/dist/cake/dom/types.d.ts +1 -0
  24. package/dist/cake/dom/types.d.ts.map +1 -0
  25. package/dist/cake/dom/types.js +1 -0
  26. package/dist/cake/editor/cake-editor.d.ts +230 -0
  27. package/dist/cake/editor/cake-editor.d.ts.map +1 -0
  28. package/dist/cake/editor/cake-editor.js +3589 -0
  29. package/dist/cake/editor/selection/selection-geometry-dom.d.ts +24 -0
  30. package/dist/cake/editor/selection/selection-geometry-dom.d.ts.map +1 -0
  31. package/dist/cake/editor/selection/selection-geometry-dom.js +302 -0
  32. package/dist/cake/editor/selection/selection-geometry.d.ts +22 -0
  33. package/dist/cake/editor/selection/selection-geometry.d.ts.map +1 -0
  34. package/dist/cake/editor/selection/selection-geometry.js +158 -0
  35. package/dist/cake/editor/selection/selection-layout-dom.d.ts +50 -0
  36. package/dist/cake/editor/selection/selection-layout-dom.d.ts.map +1 -0
  37. package/dist/cake/editor/selection/selection-layout-dom.js +781 -0
  38. package/dist/cake/editor/selection/selection-layout.d.ts +55 -0
  39. package/dist/cake/editor/selection/selection-layout.d.ts.map +1 -0
  40. package/dist/cake/editor/selection/selection-layout.js +128 -0
  41. package/dist/cake/editor/selection/selection-navigation.d.ts +22 -0
  42. package/dist/cake/editor/selection/selection-navigation.d.ts.map +1 -0
  43. package/dist/cake/editor/selection/selection-navigation.js +229 -0
  44. package/dist/cake/editor/selection/visible-text.d.ts +5 -0
  45. package/dist/cake/editor/selection/visible-text.d.ts.map +1 -0
  46. package/dist/cake/editor/selection/visible-text.js +66 -0
  47. package/dist/cake/engine/cake-engine.d.ts +1 -0
  48. package/dist/cake/engine/cake-engine.d.ts.map +1 -0
  49. package/dist/cake/engine/cake-engine.js +3589 -0
  50. package/dist/cake/engine/selection/selection-geometry-dom.d.ts +1 -0
  51. package/dist/cake/engine/selection/selection-geometry-dom.d.ts.map +1 -0
  52. package/dist/cake/engine/selection/selection-geometry-dom.js +302 -0
  53. package/dist/cake/engine/selection/selection-geometry.d.ts +1 -0
  54. package/dist/cake/engine/selection/selection-geometry.d.ts.map +1 -0
  55. package/dist/cake/engine/selection/selection-geometry.js +158 -0
  56. package/dist/cake/engine/selection/selection-layout-dom.d.ts +1 -0
  57. package/dist/cake/engine/selection/selection-layout-dom.d.ts.map +1 -0
  58. package/dist/cake/engine/selection/selection-layout-dom.js +781 -0
  59. package/dist/cake/engine/selection/selection-layout.d.ts +1 -0
  60. package/dist/cake/engine/selection/selection-layout.d.ts.map +1 -0
  61. package/dist/cake/engine/selection/selection-layout.js +128 -0
  62. package/dist/cake/engine/selection/selection-navigation.d.ts +1 -0
  63. package/dist/cake/engine/selection/selection-navigation.d.ts.map +1 -0
  64. package/dist/cake/engine/selection/selection-navigation.js +229 -0
  65. package/dist/cake/engine/selection/visible-text.d.ts +1 -0
  66. package/dist/cake/engine/selection/visible-text.d.ts.map +1 -0
  67. package/dist/cake/engine/selection/visible-text.js +66 -0
  68. package/dist/cake/extensions/blockquote/blockquote.d.ts +1 -0
  69. package/dist/cake/extensions/blockquote/blockquote.d.ts.map +1 -0
  70. package/dist/cake/extensions/blockquote/blockquote.js +177 -0
  71. package/dist/cake/extensions/blockquote/index.d.ts +2 -0
  72. package/dist/cake/extensions/blockquote/index.d.ts.map +1 -0
  73. package/dist/cake/extensions/blockquote/index.js +1 -0
  74. package/dist/cake/extensions/bold/bold.d.ts +1 -0
  75. package/dist/cake/extensions/bold/bold.d.ts.map +1 -0
  76. package/dist/cake/extensions/bold/bold.js +113 -0
  77. package/dist/cake/extensions/bold/index.d.ts +2 -0
  78. package/dist/cake/extensions/bold/index.d.ts.map +1 -0
  79. package/dist/cake/extensions/bold/index.js +1 -0
  80. package/dist/cake/extensions/bundles.d.ts +1 -0
  81. package/dist/cake/extensions/bundles.d.ts.map +1 -0
  82. package/dist/cake/extensions/bundles.js +12 -0
  83. package/dist/cake/extensions/combined-emphasis/combined-emphasis.d.ts +1 -0
  84. package/dist/cake/extensions/combined-emphasis/combined-emphasis.d.ts.map +1 -0
  85. package/dist/cake/extensions/combined-emphasis/combined-emphasis.js +42 -0
  86. package/dist/cake/extensions/combined-emphasis/index.d.ts +2 -0
  87. package/dist/cake/extensions/combined-emphasis/index.d.ts.map +1 -0
  88. package/dist/cake/extensions/combined-emphasis/index.js +1 -0
  89. package/dist/cake/extensions/heading/heading.d.ts +1 -0
  90. package/dist/cake/extensions/heading/heading.d.ts.map +1 -0
  91. package/dist/cake/extensions/heading/heading.js +337 -0
  92. package/dist/cake/extensions/heading/index.d.ts +2 -0
  93. package/dist/cake/extensions/heading/index.d.ts.map +1 -0
  94. package/dist/cake/extensions/heading/index.js +1 -0
  95. package/dist/cake/extensions/image/image.d.ts +1 -0
  96. package/dist/cake/extensions/image/image.d.ts.map +1 -0
  97. package/dist/cake/extensions/image/image.js +120 -0
  98. package/dist/cake/extensions/image/index.d.ts +2 -0
  99. package/dist/cake/extensions/image/index.d.ts.map +1 -0
  100. package/dist/cake/extensions/image/index.js +1 -0
  101. package/dist/cake/extensions/index.d.ts +3 -3
  102. package/dist/cake/extensions/index.d.ts.map +1 -0
  103. package/dist/cake/extensions/index.js +25 -0
  104. package/dist/cake/extensions/italic/index.d.ts +2 -0
  105. package/dist/cake/extensions/italic/index.d.ts.map +1 -0
  106. package/dist/cake/extensions/italic/index.js +1 -0
  107. package/dist/cake/extensions/italic/italic.d.ts +1 -0
  108. package/dist/cake/extensions/italic/italic.d.ts.map +1 -0
  109. package/dist/cake/extensions/italic/italic.js +91 -0
  110. package/dist/cake/extensions/link/index.d.ts +2 -0
  111. package/dist/cake/extensions/link/index.d.ts.map +1 -0
  112. package/dist/cake/extensions/link/index.js +1 -0
  113. package/dist/cake/extensions/link/link-popover.d.ts +1 -0
  114. package/dist/cake/extensions/link/link-popover.d.ts.map +1 -0
  115. package/dist/cake/extensions/link/link-popover.js +205 -0
  116. package/dist/cake/extensions/link/link.d.ts +1 -0
  117. package/dist/cake/extensions/link/link.d.ts.map +1 -0
  118. package/dist/cake/extensions/link/link.js +202 -0
  119. package/dist/cake/extensions/list/index.d.ts +2 -0
  120. package/dist/cake/extensions/list/index.d.ts.map +1 -0
  121. package/dist/cake/extensions/list/index.js +1 -0
  122. package/dist/cake/extensions/list/list-ast.d.ts +1 -0
  123. package/dist/cake/extensions/list/list-ast.d.ts.map +1 -0
  124. package/dist/cake/extensions/list/list-ast.js +248 -0
  125. package/dist/cake/extensions/list/list.d.ts +2 -1
  126. package/dist/cake/extensions/list/list.d.ts.map +1 -0
  127. package/dist/cake/extensions/list/list.js +859 -0
  128. package/dist/cake/extensions/scrollbar/index.d.ts +1 -0
  129. package/dist/cake/extensions/scrollbar/index.d.ts.map +1 -0
  130. package/dist/cake/extensions/scrollbar/index.js +216 -0
  131. package/dist/cake/extensions/strikethrough/index.d.ts +2 -0
  132. package/dist/cake/extensions/strikethrough/index.d.ts.map +1 -0
  133. package/dist/cake/extensions/strikethrough/index.js +1 -0
  134. package/dist/cake/extensions/strikethrough/strikethrough.d.ts +1 -0
  135. package/dist/cake/extensions/strikethrough/strikethrough.d.ts.map +1 -0
  136. package/dist/cake/extensions/strikethrough/strikethrough.js +84 -0
  137. package/dist/cake/extensions/types.d.ts +1 -0
  138. package/dist/cake/extensions/types.d.ts.map +1 -0
  139. package/dist/cake/extensions/types.js +1 -0
  140. package/dist/cake/index.d.ts +1 -2
  141. package/dist/cake/index.d.ts.map +1 -0
  142. package/dist/cake/index.js +1 -0
  143. package/dist/cake/react/CakeEditor.d.ts +2 -2
  144. package/dist/cake/react/CakeEditor.d.ts.map +1 -0
  145. package/dist/cake/react/CakeEditor.js +225 -0
  146. package/dist/cake/react/index.d.ts +58 -0
  147. package/dist/cake/react/index.d.ts.map +1 -0
  148. package/dist/cake/react/index.js +225 -0
  149. package/dist/cake/shared/platform.d.ts +1 -0
  150. package/dist/cake/shared/platform.d.ts.map +1 -0
  151. package/dist/cake/shared/platform.js +19 -0
  152. package/dist/cake/shared/segmenter.d.ts +1 -0
  153. package/dist/cake/shared/segmenter.d.ts.map +1 -0
  154. package/dist/cake/shared/segmenter.js +46 -0
  155. package/dist/cake/shared/url.d.ts +1 -0
  156. package/dist/cake/shared/url.d.ts.map +1 -0
  157. package/dist/cake/shared/url.js +37 -0
  158. package/dist/cake/shared/word-break.d.ts +1 -0
  159. package/dist/cake/shared/word-break.d.ts.map +1 -0
  160. package/dist/cake/shared/word-break.js +178 -0
  161. package/dist/cake/test/harness.d.ts +3 -2
  162. package/dist/cake/test/harness.d.ts.map +1 -0
  163. package/dist/cake/test/harness.js +504 -0
  164. package/dist/codemirror/markdown-commands.d.ts +1 -0
  165. package/dist/codemirror/markdown-commands.d.ts.map +1 -0
  166. package/dist/codemirror/markdown-commands.js +532 -0
  167. package/dist/index.d.ts +2 -3
  168. package/dist/index.d.ts.map +1 -0
  169. package/dist/index.js +2 -11793
  170. package/package.json +12 -6
  171. package/dist/cake/extensions/pipe-link/pipe-link.d.ts +0 -1
  172. package/dist/index.cjs +0 -11793
@@ -0,0 +1,504 @@
1
+ import { userEvent } from "vitest/browser";
2
+ import { createElement, Fragment } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import { CakeEditor } from "../editor/cake-editor";
5
+ import { bundledExtensions } from "../extensions";
6
+ import { measureLayoutModelFromDom } from "../editor/selection/selection-layout-dom";
7
+ export function createTestHarness(valueOrOptions) {
8
+ const options = typeof valueOrOptions === "string"
9
+ ? { value: valueOrOptions }
10
+ : valueOrOptions;
11
+ const container = document.createElement("div");
12
+ container.className = "cake";
13
+ container.style.width = "400px";
14
+ container.style.height = "200px";
15
+ container.style.position = "absolute";
16
+ container.style.top = "0";
17
+ container.style.left = "0";
18
+ document.body.appendChild(container);
19
+ let styleElement = null;
20
+ const caretStyles = `
21
+ .cake-caret {
22
+ width: 3px !important;
23
+ animation: none !important;
24
+ background-color: #000 !important;
25
+ display: block !important;
26
+ }
27
+ `;
28
+ styleElement = document.createElement("style");
29
+ styleElement.textContent = caretStyles + (options.css ?? "");
30
+ document.head.appendChild(styleElement);
31
+ const extensions = options.extensions ?? bundledExtensions;
32
+ const engine = new CakeEditor({
33
+ container,
34
+ value: options.value,
35
+ selection: { start: 0, end: 0, affinity: "forward" },
36
+ extensions,
37
+ });
38
+ let overlayRoot = null;
39
+ if (options.renderOverlays) {
40
+ const overlayContainer = engine.getOverlayRoot();
41
+ const contentRoot = engine.getContentRoot();
42
+ if (!overlayContainer || !contentRoot) {
43
+ throw new Error("Missing overlay root for extensions");
44
+ }
45
+ const overlayContext = {
46
+ container,
47
+ contentRoot,
48
+ overlayRoot: overlayContainer,
49
+ toOverlayRect: (rect) => {
50
+ const containerRect = container.getBoundingClientRect();
51
+ return {
52
+ top: rect.top - containerRect.top,
53
+ left: rect.left - containerRect.left,
54
+ width: rect.width,
55
+ height: rect.height,
56
+ };
57
+ },
58
+ insertText: (text) => {
59
+ engine.insertText(text);
60
+ },
61
+ replaceText: (oldText, newText) => {
62
+ engine.replaceText(oldText, newText);
63
+ },
64
+ getSelection: () => {
65
+ const selection = engine.getSelection();
66
+ if (!selection) {
67
+ return null;
68
+ }
69
+ const focus = selection.start === selection.end
70
+ ? selection.start
71
+ : Math.max(selection.start, selection.end);
72
+ return { start: focus, end: focus };
73
+ },
74
+ executeCommand: (command) => {
75
+ return engine.executeCommand(command);
76
+ },
77
+ };
78
+ const overlayElements = extensions.flatMap((extension) => {
79
+ if (!extension.renderOverlay) {
80
+ return [];
81
+ }
82
+ const rendered = extension.renderOverlay(overlayContext);
83
+ if (!rendered) {
84
+ return [];
85
+ }
86
+ return [createElement(Fragment, { key: extension.name }, rendered)];
87
+ });
88
+ overlayRoot = createRoot(overlayContainer);
89
+ overlayRoot.render(createElement(Fragment, null, ...overlayElements));
90
+ }
91
+ function getContentRoot() {
92
+ const root = container.querySelector(".cake-content");
93
+ if (!root || !(root instanceof HTMLElement)) {
94
+ throw new Error("Content root not found");
95
+ }
96
+ return root;
97
+ }
98
+ function getLine(index) {
99
+ const line = container.querySelector(`[data-line-index="${index}"]`);
100
+ if (!line || !(line instanceof HTMLElement)) {
101
+ throw new Error(`Line ${index} not found`);
102
+ }
103
+ return line;
104
+ }
105
+ function getLineCount() {
106
+ return container.querySelectorAll("[data-line-index]").length;
107
+ }
108
+ function getLineRect(index) {
109
+ return getLine(index).getBoundingClientRect();
110
+ }
111
+ function getTextNode(lineIndex = 0) {
112
+ const line = getLine(lineIndex);
113
+ const walker = document.createTreeWalker(line, NodeFilter.SHOW_TEXT);
114
+ const node = walker.nextNode();
115
+ if (!node || !(node instanceof Text)) {
116
+ throw new Error(`No text node in line ${lineIndex}`);
117
+ }
118
+ return node;
119
+ }
120
+ function getTextNodes(lineIndex = 0) {
121
+ const line = getLine(lineIndex);
122
+ const walker = document.createTreeWalker(line, NodeFilter.SHOW_TEXT);
123
+ const nodes = [];
124
+ let node = walker.nextNode();
125
+ while (node) {
126
+ if (node instanceof Text) {
127
+ nodes.push(node);
128
+ }
129
+ node = walker.nextNode();
130
+ }
131
+ return nodes;
132
+ }
133
+ function getCharRect(offset, lineIndex = 0) {
134
+ const textNodes = getTextNodes(lineIndex);
135
+ if (textNodes.length === 0) {
136
+ throw new Error(`No text nodes in line ${lineIndex}`);
137
+ }
138
+ let remaining = offset;
139
+ for (const textNode of textNodes) {
140
+ const len = textNode.data.length;
141
+ if (remaining < len) {
142
+ const range = document.createRange();
143
+ range.setStart(textNode, remaining);
144
+ range.setEnd(textNode, remaining + 1);
145
+ // Prefer `getClientRects()` because `getBoundingClientRect()` can span
146
+ // multiple rows at wrap boundaries. Some engines (notably WebKit) can
147
+ // include zero-width rect fragments; pick the largest rect.
148
+ const rects = Array.from(range.getClientRects());
149
+ if (rects.length > 0) {
150
+ let best = rects[0];
151
+ let bestArea = best.width * best.height;
152
+ for (const rect of rects) {
153
+ const area = rect.width * rect.height;
154
+ if (area > bestArea) {
155
+ best = rect;
156
+ bestArea = area;
157
+ }
158
+ }
159
+ if (bestArea > 0) {
160
+ return best;
161
+ }
162
+ }
163
+ return range.getBoundingClientRect();
164
+ }
165
+ remaining -= len;
166
+ }
167
+ throw new Error(`Offset ${offset} out of bounds for line ${lineIndex} (len=${offset - remaining})`);
168
+ }
169
+ function dispatchSyntheticClickAt(clientX, clientY) {
170
+ // `userEvent.click(el, { position })` can quantize/round coordinates
171
+ // differently across engines (notably WebKit), which makes coordinate-based
172
+ // selection tests flaky. Dispatch events with explicit `clientX/Y` instead.
173
+ const target = document.elementFromPoint(clientX, clientY) ?? container;
174
+ // Ensure focus follows click semantics.
175
+ getContentRoot().focus();
176
+ const base = {
177
+ bubbles: true,
178
+ cancelable: true,
179
+ composed: true,
180
+ clientX,
181
+ clientY,
182
+ button: 0,
183
+ };
184
+ if (typeof PointerEvent !== "undefined") {
185
+ target.dispatchEvent(new PointerEvent("pointerdown", {
186
+ ...base,
187
+ buttons: 1,
188
+ pointerId: 1,
189
+ pointerType: "mouse",
190
+ isPrimary: true,
191
+ }));
192
+ }
193
+ target.dispatchEvent(new MouseEvent("mousedown", { ...base, buttons: 1 }));
194
+ if (typeof PointerEvent !== "undefined") {
195
+ target.dispatchEvent(new PointerEvent("pointerup", {
196
+ ...base,
197
+ buttons: 0,
198
+ pointerId: 1,
199
+ pointerType: "mouse",
200
+ isPrimary: true,
201
+ }));
202
+ }
203
+ target.dispatchEvent(new MouseEvent("mouseup", { ...base, buttons: 0 }));
204
+ target.dispatchEvent(new MouseEvent("click", { ...base, detail: 1 }));
205
+ }
206
+ async function clickAtPosition(offset, side, lineIndex = 0) {
207
+ const charRect = getCharRect(offset, lineIndex);
208
+ let clickX;
209
+ if (side === "left") {
210
+ // Prefer clicking well inside the left half of the glyph so "left of"
211
+ // remains meaningful even for very narrow characters across engines.
212
+ clickX =
213
+ charRect.width >= 2
214
+ ? charRect.left + charRect.width * 0.25
215
+ : charRect.left - 1;
216
+ }
217
+ else if (side === "right") {
218
+ // Prefer clicking well inside the right half of the glyph so "right of"
219
+ // is robust even when glyph widths differ slightly across engines/fonts.
220
+ //
221
+ // Some engines report very narrow (or even ~0 width) rects for certain
222
+ // characters (notably spaces). In that case, nudge past the right edge so
223
+ // we reliably land on the post-character boundary.
224
+ clickX =
225
+ charRect.width >= 2
226
+ ? charRect.left + charRect.width * 0.75
227
+ : charRect.right + 1;
228
+ }
229
+ else {
230
+ clickX = charRect.left + charRect.width / 2;
231
+ }
232
+ const clickY = charRect.top + charRect.height / 2;
233
+ dispatchSyntheticClickAt(clickX, clickY);
234
+ }
235
+ async function doubleClick(offset, lineIndex) {
236
+ const line = getLine(lineIndex);
237
+ const lineRect = line.getBoundingClientRect();
238
+ const charRect = getCharRect(offset, lineIndex);
239
+ const clickX = charRect.left + charRect.width / 2;
240
+ const clickY = charRect.top + charRect.height / 2;
241
+ await userEvent.dblClick(line, {
242
+ position: {
243
+ x: clickX - lineRect.left,
244
+ y: clickY - lineRect.top,
245
+ },
246
+ });
247
+ }
248
+ async function tripleClick(lineIndex) {
249
+ const line = getLine(lineIndex);
250
+ await userEvent.tripleClick(line);
251
+ }
252
+ async function clickAtCoords(clientX, clientY) {
253
+ dispatchSyntheticClickAt(clientX, clientY);
254
+ }
255
+ function getSelectionRects() {
256
+ const rects = container.querySelectorAll(".cake-selection-rect");
257
+ return Array.from(rects).map((rect) => {
258
+ const el = rect;
259
+ return {
260
+ top: parseFloat(el.style.top),
261
+ left: parseFloat(el.style.left),
262
+ width: parseFloat(el.style.width),
263
+ height: parseFloat(el.style.height),
264
+ };
265
+ });
266
+ }
267
+ function getCaretRect() {
268
+ const caret = container.querySelector(".cake-caret");
269
+ if (!caret) {
270
+ return null;
271
+ }
272
+ return {
273
+ top: parseFloat(caret.style.top),
274
+ left: parseFloat(caret.style.left),
275
+ height: caret.offsetHeight,
276
+ };
277
+ }
278
+ function getVisualRows(lineIndex = 0) {
279
+ const lines = engine.getLines();
280
+ const root = engine.getContentRoot();
281
+ if (!root) {
282
+ return [];
283
+ }
284
+ const layout = measureLayoutModelFromDom({
285
+ lines,
286
+ root,
287
+ container,
288
+ });
289
+ if (!layout) {
290
+ return [];
291
+ }
292
+ const lineLayout = layout.lines[lineIndex];
293
+ if (!lineLayout) {
294
+ return [];
295
+ }
296
+ const containerRect = container.getBoundingClientRect();
297
+ return lineLayout.rows.map((row) => {
298
+ // Use actual character positions for left/right bounds
299
+ const startCharOffset = row.startOffset;
300
+ const endCharOffset = row.endOffset > row.startOffset ? row.endOffset - 1 : row.startOffset;
301
+ // Measure first character's left position
302
+ let left = row.rect.left + containerRect.left;
303
+ try {
304
+ const firstCharRect = getCharRect(lineLayout.lineStartOffset + startCharOffset, lineIndex);
305
+ left = firstCharRect.left;
306
+ }
307
+ catch {
308
+ // Fall back to layout model rect if char measurement fails
309
+ }
310
+ // Measure last character's right position
311
+ let right = row.rect.left + row.rect.width + containerRect.left;
312
+ try {
313
+ const lastCharRect = getCharRect(lineLayout.lineStartOffset + endCharOffset, lineIndex);
314
+ right = lastCharRect.right;
315
+ }
316
+ catch {
317
+ // Fall back to layout model rect if char measurement fails
318
+ }
319
+ // Note: Layout model endOffset is exclusive, but old harness used inclusive.
320
+ // Convert to inclusive by subtracting 1 (unless it's an empty row).
321
+ const inclusiveEndOffset = row.endOffset > row.startOffset
322
+ ? row.endOffset - 1
323
+ : row.startOffset;
324
+ return {
325
+ startOffset: lineLayout.lineStartOffset + row.startOffset,
326
+ endOffset: lineLayout.lineStartOffset + inclusiveEndOffset,
327
+ top: row.rect.top + containerRect.top,
328
+ bottom: row.rect.top + row.rect.height + containerRect.top,
329
+ left,
330
+ right,
331
+ };
332
+ });
333
+ }
334
+ function assertCaretOnVisualRow(rowIndex, lineIndex = 0) {
335
+ const rows = getVisualRows(lineIndex);
336
+ if (rowIndex >= rows.length) {
337
+ throw new Error(`Row ${rowIndex} does not exist (only ${rows.length} rows)`);
338
+ }
339
+ const row = rows[rowIndex];
340
+ const caret = getCaretRect();
341
+ if (!caret) {
342
+ throw new Error("Caret not found");
343
+ }
344
+ if (caret.top < row.top - 2 || caret.top > row.bottom + 2) {
345
+ throw new Error(`Caret Y (${caret.top}) not on row ${rowIndex} (top: ${row.top}, bottom: ${row.bottom})`);
346
+ }
347
+ }
348
+ function assertCaretAtEndOfVisualRow(rowIndex, lineIndex = 0) {
349
+ const rows = getVisualRows(lineIndex);
350
+ if (rowIndex >= rows.length) {
351
+ throw new Error(`Row ${rowIndex} does not exist (only ${rows.length} rows)`);
352
+ }
353
+ const row = rows[rowIndex];
354
+ const caret = getCaretRect();
355
+ if (!caret) {
356
+ throw new Error("Caret not found");
357
+ }
358
+ // Caret should be on this row
359
+ if (caret.top < row.top - 2 || caret.top > row.bottom + 2) {
360
+ throw new Error(`Caret Y (${caret.top}) not on row ${rowIndex} (top: ${row.top}, bottom: ${row.bottom})`);
361
+ }
362
+ // Caret X should be at the right edge of the row
363
+ if (caret.left < row.right - 5) {
364
+ throw new Error(`Caret X (${caret.left}) not at end of row ${rowIndex} (right: ${row.right})`);
365
+ }
366
+ }
367
+ function assertCaretAtStartOfVisualRow(rowIndex, lineIndex = 0) {
368
+ const rows = getVisualRows(lineIndex);
369
+ if (rowIndex >= rows.length) {
370
+ throw new Error(`Row ${rowIndex} does not exist (only ${rows.length} rows)`);
371
+ }
372
+ const row = rows[rowIndex];
373
+ const caret = getCaretRect();
374
+ if (!caret) {
375
+ throw new Error("Caret not found");
376
+ }
377
+ // Caret should be on this row
378
+ if (caret.top < row.top - 2 || caret.top > row.bottom + 2) {
379
+ throw new Error(`Caret Y (${caret.top}) not on row ${rowIndex} (top: ${row.top}, bottom: ${row.bottom})`);
380
+ }
381
+ // Caret X should be at the left edge of the row
382
+ if (caret.left > row.left + 5) {
383
+ throw new Error(`Caret X (${caret.left}) not at start of row ${rowIndex} (left: ${row.left})`);
384
+ }
385
+ }
386
+ async function typeText(text) {
387
+ const contentRoot = getContentRoot();
388
+ contentRoot.focus();
389
+ for (const char of text) {
390
+ const event = new InputEvent("beforeinput", {
391
+ bubbles: true,
392
+ cancelable: true,
393
+ inputType: "insertText",
394
+ data: char,
395
+ });
396
+ contentRoot.dispatchEvent(event);
397
+ }
398
+ }
399
+ async function focus() {
400
+ const contentRoot = getContentRoot();
401
+ contentRoot.focus();
402
+ }
403
+ async function pressEnter() {
404
+ const contentRoot = getContentRoot();
405
+ contentRoot.focus();
406
+ const event = new InputEvent("beforeinput", {
407
+ bubbles: true,
408
+ cancelable: true,
409
+ inputType: "insertParagraph",
410
+ });
411
+ contentRoot.dispatchEvent(event);
412
+ }
413
+ async function pressBackspace() {
414
+ const contentRoot = getContentRoot();
415
+ contentRoot.focus();
416
+ const event = new InputEvent("beforeinput", {
417
+ bubbles: true,
418
+ cancelable: true,
419
+ inputType: "deleteContentBackward",
420
+ });
421
+ contentRoot.dispatchEvent(event);
422
+ }
423
+ async function pressTab() {
424
+ const contentRoot = getContentRoot();
425
+ contentRoot.focus();
426
+ const event = new KeyboardEvent("keydown", {
427
+ bubbles: true,
428
+ cancelable: true,
429
+ key: "Tab",
430
+ code: "Tab",
431
+ });
432
+ contentRoot.dispatchEvent(event);
433
+ }
434
+ async function pressShiftTab() {
435
+ const contentRoot = getContentRoot();
436
+ contentRoot.focus();
437
+ const event = new KeyboardEvent("keydown", {
438
+ bubbles: true,
439
+ cancelable: true,
440
+ key: "Tab",
441
+ code: "Tab",
442
+ shiftKey: true,
443
+ });
444
+ contentRoot.dispatchEvent(event);
445
+ }
446
+ async function pressKey(key, modifiers) {
447
+ const contentRoot = getContentRoot();
448
+ contentRoot.focus();
449
+ const event = new KeyboardEvent("keydown", {
450
+ bubbles: true,
451
+ cancelable: true,
452
+ key,
453
+ code: key.length === 1 ? `Key${key.toUpperCase()}` : key,
454
+ metaKey: modifiers?.meta ?? false,
455
+ ctrlKey: modifiers?.ctrl ?? false,
456
+ shiftKey: modifiers?.shift ?? false,
457
+ altKey: modifiers?.alt ?? false,
458
+ });
459
+ contentRoot.dispatchEvent(event);
460
+ }
461
+ function destroy() {
462
+ overlayRoot?.unmount();
463
+ engine.destroy();
464
+ container.remove();
465
+ if (styleElement) {
466
+ styleElement.remove();
467
+ }
468
+ }
469
+ return {
470
+ container,
471
+ get contentRoot() {
472
+ return getContentRoot();
473
+ },
474
+ engine,
475
+ get selection() {
476
+ return engine.getSelection();
477
+ },
478
+ getLine,
479
+ getLineCount,
480
+ getLineRect,
481
+ getTextNode,
482
+ getCharRect,
483
+ getSelectionRects,
484
+ getCaretRect,
485
+ getVisualRows,
486
+ clickLeftOf: (offset, lineIndex) => clickAtPosition(offset, "left", lineIndex),
487
+ clickRightOf: (offset, lineIndex) => clickAtPosition(offset, "right", lineIndex),
488
+ clickAt: (offset, lineIndex) => clickAtPosition(offset, "center", lineIndex),
489
+ clickAtCoords,
490
+ doubleClick,
491
+ tripleClick,
492
+ typeText,
493
+ pressEnter,
494
+ pressBackspace,
495
+ pressTab,
496
+ pressShiftTab,
497
+ pressKey,
498
+ focus,
499
+ assertCaretOnVisualRow,
500
+ assertCaretAtEndOfVisualRow,
501
+ assertCaretAtStartOfVisualRow,
502
+ destroy,
503
+ };
504
+ }
@@ -10,3 +10,4 @@ export declare const toggleInlineCode: StateCommand;
10
10
  export declare const toggleQuote: StateCommand;
11
11
  export declare const toggleBulletList: StateCommand;
12
12
  export declare const toggleNumberedList: StateCommand;
13
+ //# sourceMappingURL=markdown-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-commands.d.ts","sourceRoot":"","sources":["../../src/codemirror/markdown-commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD,eAAO,MAAM,UAAU,EAAE,YAuDxB,CAAC;AAGF,eAAO,MAAM,YAAY,EAAE,YAuD1B,CAAC;AAGF,eAAO,MAAM,eAAe,EAAE,YAwD7B,CAAC;AAGF,eAAO,MAAM,mBAAmB,EAAE,YAyDjC,CAAC;AAGF,eAAO,MAAM,UAAU,EAAE,YAsDxB,CAAC;AAGF,eAAO,MAAM,aAAa,EAAE,YAqD3B,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,YAqD9B,CAAC;AAGF,eAAO,MAAM,gBAAgB,EAAE,YAuD9B,CAAC;AAGF,eAAO,MAAM,WAAW,EAAE,YAqDzB,CAAC;AAMF,eAAO,MAAM,gBAAgB,EAAE,YAsE9B,CAAC;AAGF,eAAO,MAAM,kBAAkB,EAAE,YAoFhC,CAAC"}