@rtif-sdk/web 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/block-drag-handler.d.ts +189 -0
  4. package/dist/block-drag-handler.d.ts.map +1 -0
  5. package/dist/block-drag-handler.js +745 -0
  6. package/dist/block-drag-handler.js.map +1 -0
  7. package/dist/block-renderer.d.ts +402 -0
  8. package/dist/block-renderer.d.ts.map +1 -0
  9. package/dist/block-renderer.js +424 -0
  10. package/dist/block-renderer.js.map +1 -0
  11. package/dist/clipboard.d.ts +178 -0
  12. package/dist/clipboard.d.ts.map +1 -0
  13. package/dist/clipboard.js +432 -0
  14. package/dist/clipboard.js.map +1 -0
  15. package/dist/command-bus.d.ts +113 -0
  16. package/dist/command-bus.d.ts.map +1 -0
  17. package/dist/command-bus.js +70 -0
  18. package/dist/command-bus.js.map +1 -0
  19. package/dist/composition.d.ts +220 -0
  20. package/dist/composition.d.ts.map +1 -0
  21. package/dist/composition.js +271 -0
  22. package/dist/composition.js.map +1 -0
  23. package/dist/content-extraction.d.ts +69 -0
  24. package/dist/content-extraction.d.ts.map +1 -0
  25. package/dist/content-extraction.js +228 -0
  26. package/dist/content-extraction.js.map +1 -0
  27. package/dist/content-handler-file.d.ts +40 -0
  28. package/dist/content-handler-file.d.ts.map +1 -0
  29. package/dist/content-handler-file.js +91 -0
  30. package/dist/content-handler-file.js.map +1 -0
  31. package/dist/content-handler-image.d.ts +82 -0
  32. package/dist/content-handler-image.d.ts.map +1 -0
  33. package/dist/content-handler-image.js +120 -0
  34. package/dist/content-handler-image.js.map +1 -0
  35. package/dist/content-handler-url.d.ts +129 -0
  36. package/dist/content-handler-url.d.ts.map +1 -0
  37. package/dist/content-handler-url.js +244 -0
  38. package/dist/content-handler-url.js.map +1 -0
  39. package/dist/content-handlers.d.ts +67 -0
  40. package/dist/content-handlers.d.ts.map +1 -0
  41. package/dist/content-handlers.js +263 -0
  42. package/dist/content-handlers.js.map +1 -0
  43. package/dist/content-pipeline.d.ts +383 -0
  44. package/dist/content-pipeline.d.ts.map +1 -0
  45. package/dist/content-pipeline.js +232 -0
  46. package/dist/content-pipeline.js.map +1 -0
  47. package/dist/cursor-nav.d.ts +149 -0
  48. package/dist/cursor-nav.d.ts.map +1 -0
  49. package/dist/cursor-nav.js +230 -0
  50. package/dist/cursor-nav.js.map +1 -0
  51. package/dist/cursor-rect.d.ts +65 -0
  52. package/dist/cursor-rect.d.ts.map +1 -0
  53. package/dist/cursor-rect.js +98 -0
  54. package/dist/cursor-rect.js.map +1 -0
  55. package/dist/drop-indicator.d.ts +108 -0
  56. package/dist/drop-indicator.d.ts.map +1 -0
  57. package/dist/drop-indicator.js +236 -0
  58. package/dist/drop-indicator.js.map +1 -0
  59. package/dist/editor.d.ts +41 -0
  60. package/dist/editor.d.ts.map +1 -0
  61. package/dist/editor.js +710 -0
  62. package/dist/editor.js.map +1 -0
  63. package/dist/floating-toolbar.d.ts +93 -0
  64. package/dist/floating-toolbar.d.ts.map +1 -0
  65. package/dist/floating-toolbar.js +159 -0
  66. package/dist/floating-toolbar.js.map +1 -0
  67. package/dist/index.d.ts +62 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +119 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/input-bridge.d.ts +273 -0
  72. package/dist/input-bridge.d.ts.map +1 -0
  73. package/dist/input-bridge.js +884 -0
  74. package/dist/input-bridge.js.map +1 -0
  75. package/dist/link-popover.d.ts +38 -0
  76. package/dist/link-popover.d.ts.map +1 -0
  77. package/dist/link-popover.js +278 -0
  78. package/dist/link-popover.js.map +1 -0
  79. package/dist/mark-renderer.d.ts +275 -0
  80. package/dist/mark-renderer.d.ts.map +1 -0
  81. package/dist/mark-renderer.js +210 -0
  82. package/dist/mark-renderer.js.map +1 -0
  83. package/dist/perf.d.ts +145 -0
  84. package/dist/perf.d.ts.map +1 -0
  85. package/dist/perf.js +260 -0
  86. package/dist/perf.js.map +1 -0
  87. package/dist/plugin-kit.d.ts +265 -0
  88. package/dist/plugin-kit.d.ts.map +1 -0
  89. package/dist/plugin-kit.js +234 -0
  90. package/dist/plugin-kit.js.map +1 -0
  91. package/dist/plugins/alignment-plugin.d.ts +68 -0
  92. package/dist/plugins/alignment-plugin.d.ts.map +1 -0
  93. package/dist/plugins/alignment-plugin.js +98 -0
  94. package/dist/plugins/alignment-plugin.js.map +1 -0
  95. package/dist/plugins/block-utils.d.ts +113 -0
  96. package/dist/plugins/block-utils.d.ts.map +1 -0
  97. package/dist/plugins/block-utils.js +191 -0
  98. package/dist/plugins/block-utils.js.map +1 -0
  99. package/dist/plugins/blockquote-plugin.d.ts +39 -0
  100. package/dist/plugins/blockquote-plugin.d.ts.map +1 -0
  101. package/dist/plugins/blockquote-plugin.js +88 -0
  102. package/dist/plugins/blockquote-plugin.js.map +1 -0
  103. package/dist/plugins/bold-plugin.d.ts +37 -0
  104. package/dist/plugins/bold-plugin.d.ts.map +1 -0
  105. package/dist/plugins/bold-plugin.js +48 -0
  106. package/dist/plugins/bold-plugin.js.map +1 -0
  107. package/dist/plugins/callout-plugin.d.ts +100 -0
  108. package/dist/plugins/callout-plugin.d.ts.map +1 -0
  109. package/dist/plugins/callout-plugin.js +200 -0
  110. package/dist/plugins/callout-plugin.js.map +1 -0
  111. package/dist/plugins/code-block-plugin.d.ts +62 -0
  112. package/dist/plugins/code-block-plugin.d.ts.map +1 -0
  113. package/dist/plugins/code-block-plugin.js +176 -0
  114. package/dist/plugins/code-block-plugin.js.map +1 -0
  115. package/dist/plugins/code-plugin.d.ts +37 -0
  116. package/dist/plugins/code-plugin.d.ts.map +1 -0
  117. package/dist/plugins/code-plugin.js +48 -0
  118. package/dist/plugins/code-plugin.js.map +1 -0
  119. package/dist/plugins/embed-plugin.d.ts +90 -0
  120. package/dist/plugins/embed-plugin.d.ts.map +1 -0
  121. package/dist/plugins/embed-plugin.js +147 -0
  122. package/dist/plugins/embed-plugin.js.map +1 -0
  123. package/dist/plugins/font-family-plugin.d.ts +58 -0
  124. package/dist/plugins/font-family-plugin.d.ts.map +1 -0
  125. package/dist/plugins/font-family-plugin.js +57 -0
  126. package/dist/plugins/font-family-plugin.js.map +1 -0
  127. package/dist/plugins/font-size-plugin.d.ts +57 -0
  128. package/dist/plugins/font-size-plugin.d.ts.map +1 -0
  129. package/dist/plugins/font-size-plugin.js +56 -0
  130. package/dist/plugins/font-size-plugin.js.map +1 -0
  131. package/dist/plugins/heading-plugin.d.ts +52 -0
  132. package/dist/plugins/heading-plugin.d.ts.map +1 -0
  133. package/dist/plugins/heading-plugin.js +114 -0
  134. package/dist/plugins/heading-plugin.js.map +1 -0
  135. package/dist/plugins/hr-plugin.d.ts +33 -0
  136. package/dist/plugins/hr-plugin.d.ts.map +1 -0
  137. package/dist/plugins/hr-plugin.js +75 -0
  138. package/dist/plugins/hr-plugin.js.map +1 -0
  139. package/dist/plugins/image-plugin.d.ts +115 -0
  140. package/dist/plugins/image-plugin.d.ts.map +1 -0
  141. package/dist/plugins/image-plugin.js +199 -0
  142. package/dist/plugins/image-plugin.js.map +1 -0
  143. package/dist/plugins/indent-plugin.d.ts +62 -0
  144. package/dist/plugins/indent-plugin.d.ts.map +1 -0
  145. package/dist/plugins/indent-plugin.js +128 -0
  146. package/dist/plugins/indent-plugin.js.map +1 -0
  147. package/dist/plugins/index.d.ts +45 -0
  148. package/dist/plugins/index.d.ts.map +1 -0
  149. package/dist/plugins/index.js +42 -0
  150. package/dist/plugins/index.js.map +1 -0
  151. package/dist/plugins/italic-plugin.d.ts +37 -0
  152. package/dist/plugins/italic-plugin.d.ts.map +1 -0
  153. package/dist/plugins/italic-plugin.js +48 -0
  154. package/dist/plugins/italic-plugin.js.map +1 -0
  155. package/dist/plugins/link-plugin.d.ts +129 -0
  156. package/dist/plugins/link-plugin.d.ts.map +1 -0
  157. package/dist/plugins/link-plugin.js +212 -0
  158. package/dist/plugins/link-plugin.js.map +1 -0
  159. package/dist/plugins/list-plugin.d.ts +53 -0
  160. package/dist/plugins/list-plugin.d.ts.map +1 -0
  161. package/dist/plugins/list-plugin.js +309 -0
  162. package/dist/plugins/list-plugin.js.map +1 -0
  163. package/dist/plugins/mark-utils.d.ts +173 -0
  164. package/dist/plugins/mark-utils.d.ts.map +1 -0
  165. package/dist/plugins/mark-utils.js +425 -0
  166. package/dist/plugins/mark-utils.js.map +1 -0
  167. package/dist/plugins/mention-plugin.d.ts +191 -0
  168. package/dist/plugins/mention-plugin.d.ts.map +1 -0
  169. package/dist/plugins/mention-plugin.js +295 -0
  170. package/dist/plugins/mention-plugin.js.map +1 -0
  171. package/dist/plugins/strikethrough-plugin.d.ts +37 -0
  172. package/dist/plugins/strikethrough-plugin.d.ts.map +1 -0
  173. package/dist/plugins/strikethrough-plugin.js +48 -0
  174. package/dist/plugins/strikethrough-plugin.js.map +1 -0
  175. package/dist/plugins/text-color-plugin.d.ts +57 -0
  176. package/dist/plugins/text-color-plugin.d.ts.map +1 -0
  177. package/dist/plugins/text-color-plugin.js +56 -0
  178. package/dist/plugins/text-color-plugin.js.map +1 -0
  179. package/dist/plugins/underline-plugin.d.ts +37 -0
  180. package/dist/plugins/underline-plugin.d.ts.map +1 -0
  181. package/dist/plugins/underline-plugin.js +48 -0
  182. package/dist/plugins/underline-plugin.js.map +1 -0
  183. package/dist/presets.d.ts +95 -0
  184. package/dist/presets.d.ts.map +1 -0
  185. package/dist/presets.js +159 -0
  186. package/dist/presets.js.map +1 -0
  187. package/dist/renderer.d.ts +125 -0
  188. package/dist/renderer.d.ts.map +1 -0
  189. package/dist/renderer.js +415 -0
  190. package/dist/renderer.js.map +1 -0
  191. package/dist/scroll-to-cursor.d.ts +25 -0
  192. package/dist/scroll-to-cursor.d.ts.map +1 -0
  193. package/dist/scroll-to-cursor.js +59 -0
  194. package/dist/scroll-to-cursor.js.map +1 -0
  195. package/dist/selection-sync.d.ts +159 -0
  196. package/dist/selection-sync.d.ts.map +1 -0
  197. package/dist/selection-sync.js +527 -0
  198. package/dist/selection-sync.js.map +1 -0
  199. package/dist/shortcut-handler.d.ts +98 -0
  200. package/dist/shortcut-handler.d.ts.map +1 -0
  201. package/dist/shortcut-handler.js +155 -0
  202. package/dist/shortcut-handler.js.map +1 -0
  203. package/dist/toolbar.d.ts +103 -0
  204. package/dist/toolbar.d.ts.map +1 -0
  205. package/dist/toolbar.js +134 -0
  206. package/dist/toolbar.js.map +1 -0
  207. package/dist/trigger-manager.d.ts +205 -0
  208. package/dist/trigger-manager.d.ts.map +1 -0
  209. package/dist/trigger-manager.js +466 -0
  210. package/dist/trigger-manager.js.map +1 -0
  211. package/dist/types.d.ts +216 -0
  212. package/dist/types.d.ts.map +1 -0
  213. package/dist/types.js +2 -0
  214. package/dist/types.js.map +1 -0
  215. package/package.json +30 -0
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Shared utilities for inline mark plugins.
3
+ *
4
+ * Provides `createToggleMarkCommand()` for building mark toggle commands,
5
+ * `toggleMark()` for the toggle logic, and `isMarkActiveAtSelection()`
6
+ * for querying active mark state.
7
+ *
8
+ * @module
9
+ */
10
+ import { docLength, blockTextLength, resolve } from '@rtif-sdk/core';
11
+ /**
12
+ * Create a `CommandDescriptor` for toggling a mark type on/off.
13
+ *
14
+ * When the cursor is collapsed, toggles a pending mark.
15
+ * When a selection exists, applies or removes the mark via `set_span_marks`.
16
+ *
17
+ * @param markType - The mark type string (e.g., "bold", "italic")
18
+ * @param markValue - The mark value to apply (default: `true`)
19
+ * @returns A `CommandDescriptor` with execute, canExecute, and isActive
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const boldCommand = createToggleMarkCommand('bold');
24
+ * registries.registerCommand('toggleMark:bold', boldCommand);
25
+ * ```
26
+ */
27
+ export function createToggleMarkCommand(markType, markValue = true) {
28
+ return {
29
+ execute(engine) {
30
+ toggleMark(engine, markType, markValue);
31
+ },
32
+ canExecute(_engine) {
33
+ return true;
34
+ },
35
+ isActive(engine) {
36
+ return isMarkActiveAtSelection(engine, markType);
37
+ },
38
+ };
39
+ }
40
+ /**
41
+ * Toggle a mark on the current selection or as a pending mark.
42
+ *
43
+ * If the cursor is collapsed, toggles the mark as a pending mark (the next
44
+ * typed text will have or not have the mark). If a range is selected,
45
+ * dispatches `set_span_marks` to apply or remove the mark.
46
+ *
47
+ * @param engine - The editor engine instance
48
+ * @param markType - The mark type string (e.g., "bold")
49
+ * @param markValue - The value to apply (default: `true`)
50
+ * @returns `true` if the toggle was performed
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * toggleMark(engine, 'bold');
55
+ * ```
56
+ */
57
+ export function toggleMark(engine, markType, markValue = true) {
58
+ const { anchor, focus } = engine.state.selection;
59
+ const start = Math.min(anchor.offset, focus.offset);
60
+ const end = Math.max(anchor.offset, focus.offset);
61
+ if (start === end) {
62
+ // Collapsed cursor — toggle pending mark
63
+ engine.togglePendingMark(markType, markValue);
64
+ return true;
65
+ }
66
+ // Range selection — check if mark is active across the range
67
+ const { common } = engine.getMarksInRange(start, end - start);
68
+ const isActive = markType in common;
69
+ engine.dispatch({
70
+ type: 'set_span_marks',
71
+ offset: start,
72
+ count: end - start,
73
+ marks: { [markType]: isActive ? null : markValue },
74
+ });
75
+ return true;
76
+ }
77
+ /**
78
+ * Check whether a mark type is active at the current selection.
79
+ *
80
+ * For collapsed cursors, checks the marks at the cursor offset and
81
+ * pending marks. For range selections, checks if the mark is present
82
+ * across the entire range (`common` marks).
83
+ *
84
+ * @param engine - The editor engine instance
85
+ * @param markType - The mark type to check (e.g., "bold")
86
+ * @returns `true` if the mark is active at the selection
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * if (isMarkActiveAtSelection(engine, 'bold')) {
91
+ * // Bold button should show as pressed
92
+ * }
93
+ * ```
94
+ */
95
+ export function isMarkActiveAtSelection(engine, markType) {
96
+ const { anchor, focus } = engine.state.selection;
97
+ const start = Math.min(anchor.offset, focus.offset);
98
+ const end = Math.max(anchor.offset, focus.offset);
99
+ // Check pending marks first (they override document marks for collapsed cursor)
100
+ const pending = engine.getPendingMarks();
101
+ if (markType in pending) {
102
+ return pending[markType] != null;
103
+ }
104
+ if (start === end) {
105
+ // Collapsed cursor — check marks at offset
106
+ const marks = engine.getMarksAtOffset(start);
107
+ return markType in marks;
108
+ }
109
+ // Range selection — check if mark is common across the entire range
110
+ const { common } = engine.getMarksInRange(start, end - start);
111
+ return markType in common;
112
+ }
113
+ /**
114
+ * Create a `CommandDescriptor` that sets a parameterized mark value.
115
+ *
116
+ * On a collapsed cursor, sets the mark as a pending mark so the next typed
117
+ * text inherits the value. On a range selection, dispatches `set_span_marks`
118
+ * to apply the mark to the selected text.
119
+ *
120
+ * @param markType - The mark type string (e.g., "color", "fontSize")
121
+ * @param extractValue - A function that extracts the mark value from the command payload
122
+ * @returns A `CommandDescriptor` with execute and isActive
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * const cmd = createSetMarkCommand('color', (p) => (p as { color: string }).color);
127
+ * registries.registerCommand('setTextColor', cmd);
128
+ * ```
129
+ */
130
+ export function createSetMarkCommand(markType, extractValue) {
131
+ return {
132
+ execute(engine, payload) {
133
+ const value = extractValue(payload);
134
+ const { anchor, focus } = engine.state.selection;
135
+ const start = Math.min(anchor.offset, focus.offset);
136
+ const end = Math.max(anchor.offset, focus.offset);
137
+ if (start === end) {
138
+ // Collapsed cursor — set pending mark
139
+ engine.setPendingMark(markType, value);
140
+ return;
141
+ }
142
+ // Range selection — apply mark
143
+ engine.dispatch({
144
+ type: 'set_span_marks',
145
+ offset: start,
146
+ count: end - start,
147
+ marks: { [markType]: value },
148
+ });
149
+ },
150
+ isActive(engine) {
151
+ return isMarkActiveAtSelection(engine, markType);
152
+ },
153
+ };
154
+ }
155
+ /**
156
+ * Create a `CommandDescriptor` that removes a mark.
157
+ *
158
+ * On a collapsed cursor, sets the pending mark to `null` so the next typed
159
+ * text will not have the mark. On a range selection, dispatches `set_span_marks`
160
+ * with `null` to remove the mark from the selected text.
161
+ *
162
+ * @param markType - The mark type string (e.g., "color", "fontSize")
163
+ * @returns A `CommandDescriptor` with execute and canExecute
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * const cmd = createRemoveMarkCommand('color');
168
+ * registries.registerCommand('removeTextColor', cmd);
169
+ * ```
170
+ */
171
+ export function createRemoveMarkCommand(markType) {
172
+ return {
173
+ execute(engine) {
174
+ const { anchor, focus } = engine.state.selection;
175
+ const start = Math.min(anchor.offset, focus.offset);
176
+ const end = Math.max(anchor.offset, focus.offset);
177
+ if (start === end) {
178
+ // Collapsed cursor — set pending mark to null (remove on next insert)
179
+ engine.setPendingMark(markType, null);
180
+ return;
181
+ }
182
+ // Range selection — remove mark
183
+ engine.dispatch({
184
+ type: 'set_span_marks',
185
+ offset: start,
186
+ count: end - start,
187
+ marks: { [markType]: null },
188
+ });
189
+ },
190
+ canExecute(engine) {
191
+ return isMarkActiveAtSelection(engine, markType);
192
+ },
193
+ };
194
+ }
195
+ // ---------------------------------------------------------------------------
196
+ // Contiguous mark range finder
197
+ // ---------------------------------------------------------------------------
198
+ /**
199
+ * Find the absolute document range of a contiguous mark span containing
200
+ * the given offset.
201
+ *
202
+ * Scans the spans of the block containing the offset to find the contiguous
203
+ * range of spans that share the same mark key (with a non-null object value).
204
+ *
205
+ * @param doc - The document to search
206
+ * @param offset - The absolute offset to find the mark at
207
+ * @param markKey - The mark key to search for (e.g., 'link', 'mention')
208
+ * @returns The absolute offset and count of the mark range, or null if no mark at offset
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * const range = findContiguousMarkRange(doc, 5, 'link');
213
+ * if (range) {
214
+ * engine.dispatch({ type: 'set_span_marks', offset: range.offset, count: range.count, marks: { link: null } });
215
+ * }
216
+ * ```
217
+ */
218
+ export function findContiguousMarkRange(doc, offset, markKey) {
219
+ const totalLen = docLength(doc);
220
+ if (offset < 0 || offset > totalLen)
221
+ return null;
222
+ // Resolve to block and local offset
223
+ let blockStartOffset = 0;
224
+ for (let bi = 0; bi < doc.blocks.length; bi++) {
225
+ const block = doc.blocks[bi];
226
+ const blockLen = blockTextLength(block);
227
+ if (offset <= blockStartOffset + blockLen) {
228
+ const localOffset = offset - blockStartOffset;
229
+ return findContiguousMarkRangeInBlock(block, localOffset, blockStartOffset, markKey);
230
+ }
231
+ blockStartOffset += blockLen + 1; // +1 for virtual \n
232
+ }
233
+ return null;
234
+ }
235
+ /** Find a contiguous mark range within a specific block at a local offset. */
236
+ function findContiguousMarkRangeInBlock(block, localOffset, blockStartOffset, markKey) {
237
+ // Find which span the offset is in
238
+ let spanStart = 0;
239
+ let targetSpanIndex = -1;
240
+ for (let si = 0; si < block.spans.length; si++) {
241
+ const span = block.spans[si];
242
+ const spanEnd = spanStart + span.text.length;
243
+ if (localOffset <= spanEnd && localOffset >= spanStart) {
244
+ // At span boundary, prefer previous span (consistent with mark inheritance)
245
+ if (localOffset === spanStart && si > 0) {
246
+ const prevSpan = block.spans[si - 1];
247
+ if (hasMarkKey(prevSpan, markKey)) {
248
+ targetSpanIndex = si - 1;
249
+ break;
250
+ }
251
+ }
252
+ if (hasMarkKey(span, markKey)) {
253
+ targetSpanIndex = si;
254
+ break;
255
+ }
256
+ // Check previous span at boundary
257
+ if (localOffset === spanStart && si > 0 && hasMarkKey(block.spans[si - 1], markKey)) {
258
+ targetSpanIndex = si - 1;
259
+ break;
260
+ }
261
+ return null;
262
+ }
263
+ spanStart = spanEnd;
264
+ }
265
+ if (targetSpanIndex < 0)
266
+ return null;
267
+ // Expand left — find first contiguous span with same mark
268
+ let rangeStart = 0;
269
+ for (let i = 0; i < targetSpanIndex; i++) {
270
+ rangeStart += block.spans[i].text.length;
271
+ }
272
+ let startIdx = targetSpanIndex;
273
+ while (startIdx > 0 && hasMarkKey(block.spans[startIdx - 1], markKey)) {
274
+ startIdx--;
275
+ rangeStart -= block.spans[startIdx].text.length;
276
+ }
277
+ // Expand right — find last contiguous span with same mark
278
+ let rangeEnd = rangeStart;
279
+ let endIdx = startIdx;
280
+ while (endIdx < block.spans.length && hasMarkKey(block.spans[endIdx], markKey)) {
281
+ rangeEnd += block.spans[endIdx].text.length;
282
+ endIdx++;
283
+ }
284
+ if (rangeEnd <= rangeStart)
285
+ return null;
286
+ return {
287
+ offset: blockStartOffset + rangeStart,
288
+ count: rangeEnd - rangeStart,
289
+ };
290
+ }
291
+ // ---------------------------------------------------------------------------
292
+ // Atomic mark cursor adjustment
293
+ // ---------------------------------------------------------------------------
294
+ /**
295
+ * Adjust an offset that falls inside an atomic mark range to the nearest boundary.
296
+ *
297
+ * When arrow keys or clicks land the cursor inside a `contenteditable="false"`
298
+ * span (e.g., an @mention), this function snaps the offset to the start or end
299
+ * of the atomic range based on the movement direction.
300
+ *
301
+ * Iterates up to `MAX_ITERATIONS` to handle adjacent atomic marks (e.g., two
302
+ * mentions side by side) — each iteration snaps to a boundary, then re-checks
303
+ * whether the boundary itself is inside another atomic range.
304
+ *
305
+ * @param offset - The absolute document offset to adjust
306
+ * @param direction - Which direction to snap: 'forward' snaps to end, 'backward' snaps to start, 'nearest' picks closer boundary
307
+ * @param findAtomicRange - Function that returns the atomic mark range at an offset, or null
308
+ * @returns The adjusted offset (unchanged if not inside an atomic range)
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * // Mention "@alice" spans offsets 5..10
313
+ * const find = (o: number) =>
314
+ * o > 5 && o < 10 ? { offset: 5, count: 5 } : null;
315
+ * adjustOffsetAroundAtomicMarks(7, 'forward', find); // => 10
316
+ * adjustOffsetAroundAtomicMarks(7, 'backward', find); // => 5
317
+ * ```
318
+ */
319
+ export function adjustOffsetAroundAtomicMarks(offset, direction, findAtomicRange) {
320
+ const MAX_ITERATIONS = 10;
321
+ let adjusted = offset;
322
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
323
+ const range = findAtomicRange(adjusted);
324
+ if (!range)
325
+ return adjusted;
326
+ // Offset is at the boundary (start or end) — not truly "inside"
327
+ if (adjusted === range.offset || adjusted === range.offset + range.count) {
328
+ return adjusted;
329
+ }
330
+ // Snap based on direction
331
+ if (direction === 'forward') {
332
+ adjusted = range.offset + range.count;
333
+ }
334
+ else if (direction === 'backward') {
335
+ adjusted = range.offset;
336
+ }
337
+ else {
338
+ // nearest: pick the closer boundary
339
+ const distToStart = adjusted - range.offset;
340
+ const distToEnd = (range.offset + range.count) - adjusted;
341
+ adjusted = distToStart <= distToEnd ? range.offset : range.offset + range.count;
342
+ }
343
+ }
344
+ return adjusted;
345
+ }
346
+ /**
347
+ * Adjust an absolute offset so it does not land inside an atomic block.
348
+ *
349
+ * When the cursor resolves to a block whose type is atomic (e.g., HR, image,
350
+ * embed), snap it to the nearest non-atomic block boundary based on the
351
+ * inferred movement direction.
352
+ *
353
+ * @param doc - The document
354
+ * @param offset - The absolute offset to adjust
355
+ * @param direction - Movement direction for choosing which boundary to snap to
356
+ * @param isAtomicBlock - Predicate that returns true for atomic block types
357
+ * @returns The adjusted offset, or the original if no adjustment needed
358
+ *
359
+ * @example
360
+ * ```ts
361
+ * const adjusted = adjustOffsetAroundAtomicBlocks(doc, 6, 'forward', (t) => t === 'hr');
362
+ * ```
363
+ */
364
+ export function adjustOffsetAroundAtomicBlocks(doc, offset, direction, isAtomicBlock) {
365
+ // Resolve offset to block index
366
+ let blockIndex;
367
+ try {
368
+ const resolved = resolve(doc, offset);
369
+ blockIndex = resolved.blockIndex;
370
+ }
371
+ catch {
372
+ return offset;
373
+ }
374
+ const block = doc.blocks[blockIndex];
375
+ if (!block || !isAtomicBlock(block.type))
376
+ return offset;
377
+ // Compute absolute offset of the start of this atomic block
378
+ let blockStart = 0;
379
+ for (let i = 0; i < blockIndex; i++) {
380
+ blockStart += blockTextLength(doc.blocks[i]) + 1; // +1 for virtual \n
381
+ }
382
+ if (direction === 'forward') {
383
+ // Snap to start of next non-atomic block
384
+ for (let i = blockIndex + 1; i < doc.blocks.length; i++) {
385
+ const b = doc.blocks[i];
386
+ blockStart += blockTextLength(doc.blocks[i - 1]) + 1;
387
+ if (!isAtomicBlock(b.type)) {
388
+ return blockStart;
389
+ }
390
+ }
391
+ // All remaining blocks are atomic — snap to end of document
392
+ return docLength(doc);
393
+ }
394
+ else if (direction === 'backward') {
395
+ // Snap to end of previous non-atomic block
396
+ for (let i = blockIndex - 1; i >= 0; i--) {
397
+ const b = doc.blocks[i];
398
+ if (!isAtomicBlock(b.type)) {
399
+ let prevEnd = 0;
400
+ for (let j = 0; j <= i; j++) {
401
+ prevEnd += blockTextLength(doc.blocks[j]);
402
+ if (j < i)
403
+ prevEnd += 1;
404
+ }
405
+ return prevEnd;
406
+ }
407
+ }
408
+ // All previous blocks are atomic — snap to offset 0 if first block
409
+ // is also atomic, or to doc start
410
+ return 0;
411
+ }
412
+ else {
413
+ // nearest: try both directions, pick closer
414
+ const fwd = adjustOffsetAroundAtomicBlocks(doc, offset, 'forward', isAtomicBlock);
415
+ const bwd = adjustOffsetAroundAtomicBlocks(doc, offset, 'backward', isAtomicBlock);
416
+ return Math.abs(offset - bwd) <= Math.abs(offset - fwd) ? bwd : fwd;
417
+ }
418
+ }
419
+ /** Check if a span has a non-null object mark for the given key. */
420
+ function hasMarkKey(span, markKey) {
421
+ return (span.marks != null &&
422
+ typeof span.marks[markKey] === 'object' &&
423
+ span.marks[markKey] !== null);
424
+ }
425
+ //# sourceMappingURL=mark-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mark-utils.js","sourceRoot":"","sources":["../../src/plugins/mark-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * @mention plugin — parameterized mark for user mentions with trigger-based
3
+ * picker integration.
4
+ *
5
+ * The plugin has two parts:
6
+ * - **Engine plugin** (`plugin` property): Mark type registration + commands.
7
+ * - **Web `attach()`**: Trigger registration + mark renderer.
8
+ *
9
+ * Consumers render their own UI (dropdown, popover, etc.) using the
10
+ * session callbacks. The plugin handles all the RTIF operation plumbing.
11
+ *
12
+ * @module
13
+ */
14
+ import type { Plugin } from '@rtif-sdk/engine';
15
+ import type { WebEditor } from '../types.js';
16
+ import type { TriggerDeactivateReason, TriggerPosition } from '../trigger-manager.js';
17
+ import type { Disposable } from '../types.js';
18
+ /**
19
+ * The value stored in a `mention` mark on a span.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const mark: MentionMark = { id: 'user-123', displayName: 'Alice Johnson' };
24
+ * ```
25
+ */
26
+ export interface MentionMark {
27
+ /** Unique identifier for the mentioned entity. */
28
+ readonly id: string;
29
+ /** Display name shown in the mention chip. */
30
+ readonly displayName: string;
31
+ }
32
+ /**
33
+ * A type-safe mention session exposed to consumer callbacks.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * function onActivate(session: MentionSession) {
38
+ * showPicker(session.query, session.getTriggerRect());
39
+ * }
40
+ * ```
41
+ */
42
+ export interface MentionSession {
43
+ /** The current query text (text typed after the trigger character). */
44
+ readonly query: string;
45
+ /** Absolute offset of the trigger character in the document. */
46
+ readonly triggerOffset: number;
47
+ /** Get the screen rect of the trigger character for positioning. */
48
+ getTriggerRect(): DOMRect | null;
49
+ /** Get the screen rect of the current cursor position. */
50
+ getCursorRect(): DOMRect | null;
51
+ /**
52
+ * Accept a mention — replaces trigger+query with a mention chip.
53
+ *
54
+ * @param mention - The mention to insert
55
+ */
56
+ accept(mention: MentionMark): void;
57
+ /** Dismiss the session — leaves text as-is. */
58
+ dismiss(): void;
59
+ /** Returns true if this session is still active. */
60
+ isActive(): boolean;
61
+ }
62
+ /**
63
+ * Configuration for the mention plugin.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const mention = createMentionPlugin({
68
+ * onActivate(session) { showDropdown(session.query); },
69
+ * onChange(session) { filterDropdown(session.query); },
70
+ * onDeactivate() { hideDropdown(); },
71
+ * });
72
+ * ```
73
+ */
74
+ export interface MentionPluginConfig {
75
+ /** The trigger character. Default: `"@"`. */
76
+ readonly triggerChar?: string;
77
+ /** Position constraint for activation. Default: `"after-whitespace"`. */
78
+ readonly triggerPosition?: TriggerPosition;
79
+ /** Called when a mention session activates. */
80
+ readonly onActivate: (session: MentionSession) => void;
81
+ /** Called when a mention session deactivates. */
82
+ readonly onDeactivate?: (reason: TriggerDeactivateReason) => void;
83
+ /** Called when the query text changes. */
84
+ readonly onChange?: (session: MentionSession) => void;
85
+ /**
86
+ * Called on keydown during an active session.
87
+ * Return `true` to stop event propagation, `false` to let it through.
88
+ */
89
+ readonly onKeyDown?: (session: MentionSession, event: KeyboardEvent) => boolean;
90
+ /** Whether to append a space after accepting a mention. Default: true. */
91
+ readonly appendSpace?: boolean;
92
+ /**
93
+ * Called when the mention trigger position may have changed due to scroll or resize.
94
+ * Re-query `session.getTriggerRect()` or `session.getCursorRect()` to reposition UI.
95
+ */
96
+ readonly onPositionChange?: (session: MentionSession) => void;
97
+ }
98
+ /**
99
+ * Command name constants for the mention plugin.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * engine.exec(MentionCommands.INSERT, { id: 'user-123', displayName: 'Alice' });
104
+ * engine.exec(MentionCommands.REMOVE);
105
+ * ```
106
+ */
107
+ export declare const MentionCommands: {
108
+ readonly INSERT: "insertMention";
109
+ readonly REMOVE: "removeMention";
110
+ };
111
+ /**
112
+ * The result of `createMentionPlugin()`.
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * const mention = createMentionPlugin(config);
117
+ * engine.use(mention.plugin);
118
+ * const editor = createWebEditor({ root, engine, accessibleLabel: 'Editor' });
119
+ * const detach = mention.attach(editor);
120
+ * ```
121
+ */
122
+ export interface MentionPluginResult {
123
+ /** The engine plugin — pass to `engine.use()`. */
124
+ readonly plugin: Plugin;
125
+ /**
126
+ * Attach the web-side behavior (trigger + renderer).
127
+ * Call AFTER `createWebEditor()`.
128
+ *
129
+ * @param editor - The web editor instance
130
+ * @returns A disposable to detach
131
+ */
132
+ attach(editor: WebEditor): Disposable;
133
+ }
134
+ /**
135
+ * Create a mention plugin with trigger-based picker integration.
136
+ *
137
+ * @param config - The mention plugin configuration
138
+ * @returns A result with `plugin` (for engine) and `attach()` (for web editor)
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const mention = createMentionPlugin({
143
+ * onActivate(session) { showDropdown(session.query, session.getTriggerRect()); },
144
+ * onDeactivate() { hideDropdown(); },
145
+ * onChange(session) { filterDropdown(session.query); },
146
+ * onKeyDown(session, event) {
147
+ * if (event.key === 'Enter') { session.accept(selected); return true; }
148
+ * return false;
149
+ * },
150
+ * });
151
+ *
152
+ * engine.use(mention.plugin);
153
+ * const editor = createWebEditor({ root, engine, accessibleLabel: 'Editor' });
154
+ * const detach = mention.attach(editor);
155
+ * ```
156
+ */
157
+ export declare function createMentionPlugin(config: MentionPluginConfig): MentionPluginResult;
158
+ /**
159
+ * Mention serializer for plaintext format.
160
+ * Produces: `@Alice` (just the display text, matching the span text).
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * mentionPlaintextSerializer.serialize('@Alice', { id: 'u1', displayName: 'Alice' });
165
+ * // => '@Alice'
166
+ * ```
167
+ */
168
+ export declare const mentionPlaintextSerializer: import('@rtif-sdk/core').MarkSerializer;
169
+ /**
170
+ * Mention serializer for markdown format.
171
+ * Produces: `[@Alice](mention:user-123)` (link-like syntax with mention: scheme).
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * mentionMarkdownSerializer.serialize('@Alice', { id: 'u1', displayName: 'Alice' });
176
+ * // => '[@Alice](mention:u1)'
177
+ * ```
178
+ */
179
+ export declare const mentionMarkdownSerializer: import('@rtif-sdk/core').MarkSerializer;
180
+ /**
181
+ * Mention serializer for HTML format.
182
+ * Produces: `<span data-mention-id="user-123" data-mention-name="Alice">@Alice</span>`.
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * mentionHtmlSerializer.serialize('@Alice', { id: 'u1', displayName: 'Alice' });
187
+ * // => '<span data-mention-id="u1" data-mention-name="Alice">@Alice</span>'
188
+ * ```
189
+ */
190
+ export declare const mentionHtmlSerializer: import('@rtif-sdk/core').MarkSerializer;
191
+ //# sourceMappingURL=mention-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mention-plugin.d.ts","sourceRoot":"","sources":["../../src/plugins/mention-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,kBAAkB,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAEV,uBAAuB,EACvB,eAAe,EAEhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAM9C;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,gEAAgE;IAChE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B,oEAAoE;IACpE,cAAc,IAAI,OAAO,GAAG,IAAI,CAAC;IAEjC,0DAA0D;IAC1D,aAAa,IAAI,OAAO,GAAG,IAAI,CAAC;IAEhC;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAEnC,+CAA+C;IAC/C,OAAO,IAAI,IAAI,CAAC;IAEhB,oDAAoD;IACpD,QAAQ,IAAI,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B,yEAAyE;IACzE,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAE3C,+CAA+C;IAC/C,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAEvD,iDAAiD;IACjD,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAElE,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAEtD;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;IAEhF,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;CAC/D;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;CAGlB,CAAC;AAEX;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC;CACvC;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CA0LpF;AAMD;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,EAAE,OAAO,gBAAgB,EAAE,cAIjE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,EAAE,OAAO,gBAAgB,EAAE,cAehE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,EAAE,OAAO,gBAAgB,EAAE,cAgB5D,CAAC"}