@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,220 @@
1
+ /**
2
+ * IME composition state machine for the RTIF web editor.
3
+ *
4
+ * During composition (CJK input, accent dead keys, dictation, emoji picker),
5
+ * the browser owns the DOM in the composing block. RTIF operations are NOT
6
+ * dispatched and the composing block is NOT reconciled until the composition
7
+ * session ends. On commit, a single {@link CompositionCommit} is returned
8
+ * so the caller can dispatch the appropriate RTIF operations.
9
+ *
10
+ * State machine:
11
+ * ```
12
+ * IDLE --compositionstart--> COMPOSING --compositionend--> IDLE
13
+ * | |
14
+ * (browser renders preview) (commit RTIF ops)
15
+ * (no RTIF ops dispatched)
16
+ * ```
17
+ *
18
+ * @module
19
+ */
20
+ import type { Selection } from '@rtif-sdk/core';
21
+ import type { CompositionState } from './types.js';
22
+ /**
23
+ * The result of a completed composition session.
24
+ *
25
+ * Returned by {@link CompositionHandler.onCompositionEnd} when the user
26
+ * commits text. The caller is responsible for dispatching the corresponding
27
+ * RTIF operations (delete selection range if present, then insert text).
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const commit = compositionHandler.onCompositionEnd(event);
32
+ * if (commit) {
33
+ * if (commit.deleteRange) {
34
+ * engine.dispatch({ type: 'delete_text', offset: commit.deleteRange.offset, count: commit.deleteRange.count });
35
+ * }
36
+ * engine.dispatch({ type: 'insert_text', offset: commit.offset, text: commit.text });
37
+ * }
38
+ * ```
39
+ */
40
+ export interface CompositionCommit {
41
+ /** The committed text from the composition session. */
42
+ readonly text: string;
43
+ /** The absolute document offset where the text should be inserted. */
44
+ readonly offset: number;
45
+ /**
46
+ * If the user had a non-collapsed selection when composition started,
47
+ * this range should be deleted before inserting the committed text.
48
+ * Deletion happens on commit, not on start, to avoid breaking IME.
49
+ */
50
+ readonly deleteRange: {
51
+ readonly offset: number;
52
+ readonly count: number;
53
+ } | null;
54
+ }
55
+ /**
56
+ * Dependencies injected into the {@link CompositionHandler}.
57
+ *
58
+ * These callbacks allow the handler to read editor state without
59
+ * depending directly on the engine or DOM.
60
+ */
61
+ export interface CompositionDeps {
62
+ /**
63
+ * Return the current RTIF selection from the engine, or null if
64
+ * the editor has no selection (e.g. during initialization).
65
+ */
66
+ readonly getSelection: () => Selection | null;
67
+ /**
68
+ * Resolve an absolute document offset to the block ID at that position.
69
+ * Returns null if the offset cannot be resolved.
70
+ *
71
+ * @param offset - Absolute document offset
72
+ */
73
+ readonly getBlockIdAtOffset: (offset: number) => string | null;
74
+ }
75
+ /**
76
+ * Manages IME composition state for the RTIF web editor.
77
+ *
78
+ * Attach the three `onComposition*` methods to the corresponding DOM events
79
+ * on the contenteditable element. The handler tracks whether a composition
80
+ * session is active and produces a {@link CompositionCommit} when the session
81
+ * ends with committed text.
82
+ *
83
+ * During `isComposing() === true`:
84
+ * - The InputBridge must NOT dispatch RTIF operations for composition-related
85
+ * `beforeinput` events (`insertCompositionText`, `deleteByComposition`).
86
+ * - The InputBridge must NOT call `preventDefault()` on those events.
87
+ * - The Renderer must NOT reconcile the composing block.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * const handler = new CompositionHandler({
92
+ * getSelection: () => engine.state.selection,
93
+ * getBlockIdAtOffset: (offset) => {
94
+ * const { blockIndex } = resolve(engine.state.doc, offset);
95
+ * return engine.state.doc.blocks[blockIndex]?.id ?? null;
96
+ * },
97
+ * });
98
+ *
99
+ * el.addEventListener('compositionstart', (e) => handler.onCompositionStart(e));
100
+ * el.addEventListener('compositionupdate', (e) => handler.onCompositionUpdate(e));
101
+ * el.addEventListener('compositionend', (e) => {
102
+ * const commit = handler.onCompositionEnd(e);
103
+ * if (commit) {
104
+ * // dispatch RTIF operations from commit
105
+ * }
106
+ * });
107
+ * ```
108
+ */
109
+ export declare class CompositionHandler {
110
+ private _composing;
111
+ private _composingBlockId;
112
+ private _startOffset;
113
+ private _selectionRange;
114
+ private readonly _deps;
115
+ constructor(deps: CompositionDeps);
116
+ /**
117
+ * Handle the `compositionstart` DOM event.
118
+ *
119
+ * Enters the composing state. Reads the current RTIF selection to
120
+ * determine where the composition will insert text. If a non-collapsed
121
+ * selection exists, records it so the range can be deleted on commit
122
+ * (not on start, to avoid breaking IME).
123
+ *
124
+ * Does NOT call `preventDefault()` — the browser must be allowed to
125
+ * render its native composition preview.
126
+ *
127
+ * @param _e - The compositionstart event (data not used)
128
+ */
129
+ onCompositionStart(_e: CompositionEvent): void;
130
+ /**
131
+ * Handle the `compositionupdate` DOM event.
132
+ *
133
+ * This is intentionally a no-op. The browser is rendering its native
134
+ * composition preview and we must not interfere. No RTIF operations
135
+ * are dispatched during composition.
136
+ *
137
+ * @param _e - The compositionupdate event (ignored)
138
+ */
139
+ onCompositionUpdate(_e: CompositionEvent): void;
140
+ /**
141
+ * Handle the `compositionend` DOM event.
142
+ *
143
+ * Exits the composing state and returns a {@link CompositionCommit}
144
+ * describing the text to insert and any selection range to delete.
145
+ *
146
+ * Returns `null` if:
147
+ * - The committed text (`e.data`) is empty or null (composition cancelled)
148
+ * - No composition was in progress (`compositionstart` was never received)
149
+ * - The start offset is unknown (selection was null at composition start)
150
+ *
151
+ * The caller is responsible for dispatching RTIF operations from the
152
+ * returned commit.
153
+ *
154
+ * @param e - The compositionend event containing the committed text
155
+ * @returns A commit descriptor, or `null` if no operation should be dispatched
156
+ */
157
+ onCompositionEnd(e: CompositionEvent): CompositionCommit | null;
158
+ /**
159
+ * Whether a composition session is currently active.
160
+ *
161
+ * When `true`, the InputBridge and Renderer must defer to the browser's
162
+ * native composition handling.
163
+ *
164
+ * @returns `true` if between `compositionstart` and `compositionend`
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * if (compositionHandler.isComposing()) {
169
+ * // Skip RTIF operation dispatch and DOM reconciliation
170
+ * }
171
+ * ```
172
+ */
173
+ isComposing(): boolean;
174
+ /**
175
+ * The block ID where the current composition is occurring.
176
+ *
177
+ * Used by the Renderer to skip reconciliation of this block during
178
+ * composition. Returns `null` when not composing or when the block
179
+ * could not be resolved.
180
+ *
181
+ * @returns The composing block ID, or `null`
182
+ *
183
+ * @example
184
+ * ```ts
185
+ * const blockId = compositionHandler.getComposingBlockId();
186
+ * reconcile(root, prevDoc, nextDoc, blockId);
187
+ * ```
188
+ */
189
+ getComposingBlockId(): string | null;
190
+ /**
191
+ * Return a snapshot of the current composition state.
192
+ *
193
+ * The returned object is a plain value — it will not change when the
194
+ * handler's internal state changes.
195
+ *
196
+ * @returns An immutable snapshot of the composition state
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * const state = compositionHandler.getState();
201
+ * console.log(state.composing, state.composingBlockId);
202
+ * ```
203
+ */
204
+ getState(): CompositionState;
205
+ /**
206
+ * Force-reset the handler to idle state.
207
+ *
208
+ * Use this when the editor blurs, is destroyed, or needs to abandon
209
+ * a composition session for any reason.
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * el.addEventListener('blur', () => compositionHandler.reset());
214
+ * ```
215
+ */
216
+ reset(): void;
217
+ /** Clear all internal state to the idle defaults. */
218
+ private _resetInternal;
219
+ }
220
+ //# sourceMappingURL=composition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMnD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;KACxB,GAAG,IAAI,CAAC;CACV;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,SAAS,GAAG,IAAI,CAAC;IAE9C;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAChE;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,eAAe,CAAiE;IAExF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;gBAE5B,IAAI,EAAE,eAAe;IAQjC;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI;IAgC9C;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI;IAI/C;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,IAAI;IAgD/D;;;;;;;;;;;;;;OAcG;IACH,WAAW,IAAI,OAAO;IAItB;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,IAAI,MAAM,GAAG,IAAI;IAIpC;;;;;;;;;;;;;OAaG;IACH,QAAQ,IAAI,gBAAgB;IAa5B;;;;;;;;;;OAUG;IACH,KAAK,IAAI,IAAI;IAQb,qDAAqD;IACrD,OAAO,CAAC,cAAc;CAMvB"}
@@ -0,0 +1,271 @@
1
+ /**
2
+ * IME composition state machine for the RTIF web editor.
3
+ *
4
+ * During composition (CJK input, accent dead keys, dictation, emoji picker),
5
+ * the browser owns the DOM in the composing block. RTIF operations are NOT
6
+ * dispatched and the composing block is NOT reconciled until the composition
7
+ * session ends. On commit, a single {@link CompositionCommit} is returned
8
+ * so the caller can dispatch the appropriate RTIF operations.
9
+ *
10
+ * State machine:
11
+ * ```
12
+ * IDLE --compositionstart--> COMPOSING --compositionend--> IDLE
13
+ * | |
14
+ * (browser renders preview) (commit RTIF ops)
15
+ * (no RTIF ops dispatched)
16
+ * ```
17
+ *
18
+ * @module
19
+ */
20
+ // ---------------------------------------------------------------------------
21
+ // Idle state singleton (avoids re-creating the same object)
22
+ // ---------------------------------------------------------------------------
23
+ const IDLE_STATE = {
24
+ composing: false,
25
+ composingBlockId: null,
26
+ startOffset: null,
27
+ selectionRange: null,
28
+ };
29
+ // ---------------------------------------------------------------------------
30
+ // CompositionHandler
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Manages IME composition state for the RTIF web editor.
34
+ *
35
+ * Attach the three `onComposition*` methods to the corresponding DOM events
36
+ * on the contenteditable element. The handler tracks whether a composition
37
+ * session is active and produces a {@link CompositionCommit} when the session
38
+ * ends with committed text.
39
+ *
40
+ * During `isComposing() === true`:
41
+ * - The InputBridge must NOT dispatch RTIF operations for composition-related
42
+ * `beforeinput` events (`insertCompositionText`, `deleteByComposition`).
43
+ * - The InputBridge must NOT call `preventDefault()` on those events.
44
+ * - The Renderer must NOT reconcile the composing block.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const handler = new CompositionHandler({
49
+ * getSelection: () => engine.state.selection,
50
+ * getBlockIdAtOffset: (offset) => {
51
+ * const { blockIndex } = resolve(engine.state.doc, offset);
52
+ * return engine.state.doc.blocks[blockIndex]?.id ?? null;
53
+ * },
54
+ * });
55
+ *
56
+ * el.addEventListener('compositionstart', (e) => handler.onCompositionStart(e));
57
+ * el.addEventListener('compositionupdate', (e) => handler.onCompositionUpdate(e));
58
+ * el.addEventListener('compositionend', (e) => {
59
+ * const commit = handler.onCompositionEnd(e);
60
+ * if (commit) {
61
+ * // dispatch RTIF operations from commit
62
+ * }
63
+ * });
64
+ * ```
65
+ */
66
+ export class CompositionHandler {
67
+ _composing = false;
68
+ _composingBlockId = null;
69
+ _startOffset = null;
70
+ _selectionRange = null;
71
+ _deps;
72
+ constructor(deps) {
73
+ this._deps = deps;
74
+ }
75
+ // -----------------------------------------------------------------------
76
+ // Event handlers
77
+ // -----------------------------------------------------------------------
78
+ /**
79
+ * Handle the `compositionstart` DOM event.
80
+ *
81
+ * Enters the composing state. Reads the current RTIF selection to
82
+ * determine where the composition will insert text. If a non-collapsed
83
+ * selection exists, records it so the range can be deleted on commit
84
+ * (not on start, to avoid breaking IME).
85
+ *
86
+ * Does NOT call `preventDefault()` — the browser must be allowed to
87
+ * render its native composition preview.
88
+ *
89
+ * @param _e - The compositionstart event (data not used)
90
+ */
91
+ onCompositionStart(_e) {
92
+ this._composing = true;
93
+ const selection = this._deps.getSelection();
94
+ if (selection === null) {
95
+ // No selection available — store nulls, compositionEnd will return null
96
+ this._composingBlockId = null;
97
+ this._startOffset = null;
98
+ this._selectionRange = null;
99
+ return;
100
+ }
101
+ const anchorOffset = selection.anchor.offset;
102
+ const focusOffset = selection.focus.offset;
103
+ // startOffset is always the focus position (where composition occurs)
104
+ this._startOffset = focusOffset;
105
+ // Resolve composing block from the focus offset
106
+ this._composingBlockId = this._deps.getBlockIdAtOffset(focusOffset);
107
+ // Record selection range if non-collapsed
108
+ if (anchorOffset !== focusOffset) {
109
+ const start = Math.min(anchorOffset, focusOffset);
110
+ const end = Math.max(anchorOffset, focusOffset);
111
+ this._selectionRange = { start, end };
112
+ }
113
+ else {
114
+ this._selectionRange = null;
115
+ }
116
+ }
117
+ /**
118
+ * Handle the `compositionupdate` DOM event.
119
+ *
120
+ * This is intentionally a no-op. The browser is rendering its native
121
+ * composition preview and we must not interfere. No RTIF operations
122
+ * are dispatched during composition.
123
+ *
124
+ * @param _e - The compositionupdate event (ignored)
125
+ */
126
+ onCompositionUpdate(_e) {
127
+ // Intentional no-op. Browser renders the composition preview.
128
+ }
129
+ /**
130
+ * Handle the `compositionend` DOM event.
131
+ *
132
+ * Exits the composing state and returns a {@link CompositionCommit}
133
+ * describing the text to insert and any selection range to delete.
134
+ *
135
+ * Returns `null` if:
136
+ * - The committed text (`e.data`) is empty or null (composition cancelled)
137
+ * - No composition was in progress (`compositionstart` was never received)
138
+ * - The start offset is unknown (selection was null at composition start)
139
+ *
140
+ * The caller is responsible for dispatching RTIF operations from the
141
+ * returned commit.
142
+ *
143
+ * @param e - The compositionend event containing the committed text
144
+ * @returns A commit descriptor, or `null` if no operation should be dispatched
145
+ */
146
+ onCompositionEnd(e) {
147
+ // If we weren't composing, nothing to commit
148
+ const wasComposing = this._composing;
149
+ const startOffset = this._startOffset;
150
+ const selectionRange = this._selectionRange;
151
+ // Always reset state, even if we return null
152
+ this._resetInternal();
153
+ if (!wasComposing) {
154
+ return null;
155
+ }
156
+ // If startOffset is null, we can't produce a valid commit
157
+ if (startOffset === null) {
158
+ return null;
159
+ }
160
+ // Check for cancelled composition (empty or null data)
161
+ const committedText = e.data;
162
+ if (!committedText) {
163
+ return null;
164
+ }
165
+ // Build the commit
166
+ let deleteRange = null;
167
+ let insertOffset = startOffset;
168
+ if (selectionRange !== null) {
169
+ deleteRange = {
170
+ offset: selectionRange.start,
171
+ count: selectionRange.end - selectionRange.start,
172
+ };
173
+ // Insertion happens at the start of the deleted range
174
+ insertOffset = selectionRange.start;
175
+ }
176
+ return {
177
+ text: committedText,
178
+ offset: insertOffset,
179
+ deleteRange,
180
+ };
181
+ }
182
+ // -----------------------------------------------------------------------
183
+ // State queries
184
+ // -----------------------------------------------------------------------
185
+ /**
186
+ * Whether a composition session is currently active.
187
+ *
188
+ * When `true`, the InputBridge and Renderer must defer to the browser's
189
+ * native composition handling.
190
+ *
191
+ * @returns `true` if between `compositionstart` and `compositionend`
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * if (compositionHandler.isComposing()) {
196
+ * // Skip RTIF operation dispatch and DOM reconciliation
197
+ * }
198
+ * ```
199
+ */
200
+ isComposing() {
201
+ return this._composing;
202
+ }
203
+ /**
204
+ * The block ID where the current composition is occurring.
205
+ *
206
+ * Used by the Renderer to skip reconciliation of this block during
207
+ * composition. Returns `null` when not composing or when the block
208
+ * could not be resolved.
209
+ *
210
+ * @returns The composing block ID, or `null`
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * const blockId = compositionHandler.getComposingBlockId();
215
+ * reconcile(root, prevDoc, nextDoc, blockId);
216
+ * ```
217
+ */
218
+ getComposingBlockId() {
219
+ return this._composingBlockId;
220
+ }
221
+ /**
222
+ * Return a snapshot of the current composition state.
223
+ *
224
+ * The returned object is a plain value — it will not change when the
225
+ * handler's internal state changes.
226
+ *
227
+ * @returns An immutable snapshot of the composition state
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const state = compositionHandler.getState();
232
+ * console.log(state.composing, state.composingBlockId);
233
+ * ```
234
+ */
235
+ getState() {
236
+ if (!this._composing) {
237
+ return IDLE_STATE;
238
+ }
239
+ return {
240
+ composing: this._composing,
241
+ composingBlockId: this._composingBlockId,
242
+ startOffset: this._startOffset,
243
+ selectionRange: this._selectionRange,
244
+ };
245
+ }
246
+ /**
247
+ * Force-reset the handler to idle state.
248
+ *
249
+ * Use this when the editor blurs, is destroyed, or needs to abandon
250
+ * a composition session for any reason.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * el.addEventListener('blur', () => compositionHandler.reset());
255
+ * ```
256
+ */
257
+ reset() {
258
+ this._resetInternal();
259
+ }
260
+ // -----------------------------------------------------------------------
261
+ // Private
262
+ // -----------------------------------------------------------------------
263
+ /** Clear all internal state to the idle defaults. */
264
+ _resetInternal() {
265
+ this._composing = false;
266
+ this._composingBlockId = null;
267
+ this._startOffset = null;
268
+ this._selectionRange = null;
269
+ }
270
+ }
271
+ //# sourceMappingURL=composition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composition.js","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Content extraction — converts browser clipboard and drag events into
3
+ * {@link ContentItem} arrays for the content pipeline.
4
+ *
5
+ * This module bridges between the browser's DataTransfer API and the
6
+ * content pipeline's abstract ContentItem interface. It handles deduplication,
7
+ * lazy data access, and file wrapping.
8
+ *
9
+ * @module
10
+ */
11
+ import type { ContentItem, ContentItemSource } from './content-pipeline.js';
12
+ /**
13
+ * Extract content items from a ClipboardEvent.
14
+ *
15
+ * Uses the `DataTransferItemList` API when available for lazy data access.
16
+ * Falls back to `getData()` for string types. Deduplicates entries where both
17
+ * APIs provide the same MIME type.
18
+ *
19
+ * @param e - The clipboard event (from a 'paste' handler)
20
+ * @returns Array of ContentItem objects for pipeline processing
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * root.addEventListener('paste', (e) => {
25
+ * e.preventDefault();
26
+ * const items = extractFromPaste(e);
27
+ * pipeline.process(items, contextOpts);
28
+ * });
29
+ * ```
30
+ */
31
+ export declare function extractFromPaste(e: ClipboardEvent): ContentItem[];
32
+ /**
33
+ * Extract content items from a DragEvent.
34
+ *
35
+ * Uses the `DataTransferItemList` API when available, falling back to
36
+ * `DataTransfer.files` and `getData()`. Items are created with source 'drop'.
37
+ *
38
+ * @param e - The drag event (from a 'drop' handler)
39
+ * @returns Array of ContentItem objects for pipeline processing
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * root.addEventListener('drop', (e) => {
44
+ * e.preventDefault();
45
+ * const items = extractFromDrop(e);
46
+ * pipeline.process(items, contextOpts);
47
+ * });
48
+ * ```
49
+ */
50
+ export declare function extractFromDrop(e: DragEvent): ContentItem[];
51
+ /**
52
+ * Create a ContentItem from a File object.
53
+ *
54
+ * The `getString()` method returns `file.text()` for text MIME types,
55
+ * null otherwise. `getFile()` and `getBlob()` always return the file.
56
+ *
57
+ * @param file - The File to wrap
58
+ * @param source - How the file entered the editor
59
+ * @returns A ContentItem wrapping the file
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const fileInput = document.querySelector('input[type=file]');
64
+ * const file = fileInput.files[0];
65
+ * const item = fileToContentItem(file, 'input');
66
+ * ```
67
+ */
68
+ export declare function fileToContentItem(file: File, source: ContentItemSource): ContentItem;
69
+ //# sourceMappingURL=content-extraction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-extraction.d.ts","sourceRoot":"","sources":["../src/content-extraction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAM5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,cAAc,GAAG,WAAW,EAAE,CAKjE;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,GAAG,WAAW,EAAE,CAK3D;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,iBAAiB,GACxB,WAAW,CAiBb"}