@thomasfosterau/effect-richtext 0.0.0 → 0.1.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 (176) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +117 -1
  3. package/dist/_virtual/_rolldown/runtime.js +12 -0
  4. package/dist/commands/commands.d.ts +323 -0
  5. package/dist/commands/commands.js +722 -0
  6. package/dist/commands/internal.js +43 -0
  7. package/dist/commands/keymaps.d.ts +46 -0
  8. package/dist/commands/keymaps.js +64 -0
  9. package/dist/commands.d.ts +3 -0
  10. package/dist/commands.js +3 -0
  11. package/dist/compat/commands.d.ts +250 -0
  12. package/dist/compat/commands.js +275 -0
  13. package/dist/compat/internal/core.d.ts +20 -0
  14. package/dist/compat/internal/core.js +148 -0
  15. package/dist/compat/internal/props.js +82 -0
  16. package/dist/compat/keymap.d.ts +21 -0
  17. package/dist/compat/keymap.js +38 -0
  18. package/dist/compat/model.d.ts +893 -0
  19. package/dist/compat/model.js +1212 -0
  20. package/dist/compat/orderedmap.d.ts +74 -0
  21. package/dist/compat/orderedmap.js +124 -0
  22. package/dist/compat/schema-basic.d.ts +38 -0
  23. package/dist/compat/schema-basic.js +238 -0
  24. package/dist/compat/schema-list.d.ts +81 -0
  25. package/dist/compat/schema-list.js +156 -0
  26. package/dist/compat/state.d.ts +415 -0
  27. package/dist/compat/state.js +689 -0
  28. package/dist/compat/test-builder.d.ts +98 -0
  29. package/dist/compat/test-builder.js +197 -0
  30. package/dist/compat/transform.d.ts +458 -0
  31. package/dist/compat/transform.js +803 -0
  32. package/dist/compat/view.d.ts +411 -0
  33. package/dist/compat/view.js +409 -0
  34. package/dist/dropcursor.d.ts +46 -0
  35. package/dist/dropcursor.js +176 -0
  36. package/dist/extensions/defaults.d.ts +716 -0
  37. package/dist/extensions/defaults.js +209 -0
  38. package/dist/extensions/editor.d.ts +246 -0
  39. package/dist/extensions/editor.js +271 -0
  40. package/dist/extensions/extension.d.ts +375 -0
  41. package/dist/extensions/extension.js +233 -0
  42. package/dist/extensions.d.ts +4 -0
  43. package/dist/extensions.js +4 -0
  44. package/dist/gapcursor.d.ts +66 -0
  45. package/dist/gapcursor.js +273 -0
  46. package/dist/history.d.ts +178 -0
  47. package/dist/history.js +412 -0
  48. package/dist/html.d.ts +158 -0
  49. package/dist/html.js +164 -0
  50. package/dist/inputrules.d.ts +271 -0
  51. package/dist/inputrules.js +321 -0
  52. package/dist/internal/attempt.js +15 -0
  53. package/dist/internal/json.js +29 -0
  54. package/dist/keymap/keymap.d.ts +130 -0
  55. package/dist/keymap/keymap.js +159 -0
  56. package/dist/keymap.d.ts +2 -0
  57. package/dist/keymap.js +1 -0
  58. package/dist/markdown/errors.d.ts +23 -0
  59. package/dist/markdown/errors.js +26 -0
  60. package/dist/markdown/from_markdown.d.ts +185 -0
  61. package/dist/markdown/from_markdown.js +266 -0
  62. package/dist/markdown/schema.d.ts +522 -0
  63. package/dist/markdown/schema.js +292 -0
  64. package/dist/markdown/to_markdown.d.ts +251 -0
  65. package/dist/markdown/to_markdown.js +413 -0
  66. package/dist/markdown.d.ts +5 -0
  67. package/dist/markdown.js +5 -0
  68. package/dist/model/attrsCodec.js +48 -0
  69. package/dist/model/comparedeep.js +17 -0
  70. package/dist/model/content.d.ts +232 -0
  71. package/dist/model/content.js +630 -0
  72. package/dist/model/diff.js +66 -0
  73. package/dist/model/dom.d.ts +3 -0
  74. package/dist/model/errors.d.ts +90 -0
  75. package/dist/model/errors.js +76 -0
  76. package/dist/model/fragment.d.ts +131 -0
  77. package/dist/model/fragment.js +262 -0
  78. package/dist/model/from_dom.d.ts +299 -0
  79. package/dist/model/from_dom.js +618 -0
  80. package/dist/model/json.d.ts +37 -0
  81. package/dist/model/json.js +126 -0
  82. package/dist/model/mark.d.ts +73 -0
  83. package/dist/model/mark.js +121 -0
  84. package/dist/model/node.d.ts +267 -0
  85. package/dist/model/node.js +434 -0
  86. package/dist/model/replace.d.ts +78 -0
  87. package/dist/model/replace.js +207 -0
  88. package/dist/model/resolvedpos.d.ts +196 -0
  89. package/dist/model/resolvedpos.js +305 -0
  90. package/dist/model/schema.d.ts +740 -0
  91. package/dist/model/schema.js +451 -0
  92. package/dist/model/to_dom.d.ts +169 -0
  93. package/dist/model/to_dom.js +238 -0
  94. package/dist/model.d.ts +12 -0
  95. package/dist/model.js +12 -0
  96. package/dist/placeholder.d.ts +53 -0
  97. package/dist/placeholder.js +79 -0
  98. package/dist/reactive/observer.d.ts +54 -0
  99. package/dist/reactive/observer.js +61 -0
  100. package/dist/reactive/selectors.d.ts +22 -0
  101. package/dist/reactive/selectors.js +61 -0
  102. package/dist/reactive/stateref.d.ts +72 -0
  103. package/dist/reactive/stateref.js +71 -0
  104. package/dist/reactive/streams.d.ts +83 -0
  105. package/dist/reactive/streams.js +85 -0
  106. package/dist/reactive.d.ts +16 -0
  107. package/dist/reactive.js +26 -0
  108. package/dist/schema-basic.d.ts +448 -0
  109. package/dist/schema-basic.js +269 -0
  110. package/dist/schema-list/commands.d.ts +66 -0
  111. package/dist/schema-list/commands.js +203 -0
  112. package/dist/schema-list.d.ts +92 -0
  113. package/dist/schema-list.js +100 -0
  114. package/dist/state/errors.d.ts +39 -0
  115. package/dist/state/errors.js +32 -0
  116. package/dist/state/plugin.d.ts +232 -0
  117. package/dist/state/plugin.js +82 -0
  118. package/dist/state/selection.d.ts +297 -0
  119. package/dist/state/selection.js +486 -0
  120. package/dist/state/state.d.ts +212 -0
  121. package/dist/state/state.js +489 -0
  122. package/dist/state/transaction.d.ts +145 -0
  123. package/dist/state/transaction.js +195 -0
  124. package/dist/state.d.ts +6 -0
  125. package/dist/state.js +6 -0
  126. package/dist/test-builder.d.ts +3582 -0
  127. package/dist/test-builder.js +239 -0
  128. package/dist/transform/attr_step.d.ts +60 -0
  129. package/dist/transform/attr_step.js +105 -0
  130. package/dist/transform/errors.d.ts +50 -0
  131. package/dist/transform/errors.js +35 -0
  132. package/dist/transform/map.d.ts +182 -0
  133. package/dist/transform/map.js +280 -0
  134. package/dist/transform/mark.js +98 -0
  135. package/dist/transform/mark_set_step.js +69 -0
  136. package/dist/transform/mark_step.d.ts +113 -0
  137. package/dist/transform/mark_step.js +225 -0
  138. package/dist/transform/replace.d.ts +18 -0
  139. package/dist/transform/replace.js +381 -0
  140. package/dist/transform/replace_step.d.ts +109 -0
  141. package/dist/transform/replace_step.js +175 -0
  142. package/dist/transform/step.d.ts +157 -0
  143. package/dist/transform/step.js +168 -0
  144. package/dist/transform/structure.d.ts +89 -0
  145. package/dist/transform/structure.js +350 -0
  146. package/dist/transform/transform.d.ts +248 -0
  147. package/dist/transform/transform.js +373 -0
  148. package/dist/transform.d.ts +10 -0
  149. package/dist/transform.js +10 -0
  150. package/dist/view/browser.js +39 -0
  151. package/dist/view/capturekeys.js +282 -0
  152. package/dist/view/clipboard.d.ts +8 -0
  153. package/dist/view/clipboard.js +245 -0
  154. package/dist/view/decoration.d.ts +339 -0
  155. package/dist/view/decoration.js +566 -0
  156. package/dist/view/dom.d.ts +18 -0
  157. package/dist/view/dom.js +140 -0
  158. package/dist/view/domchange.js +295 -0
  159. package/dist/view/domcoords.d.ts +22 -0
  160. package/dist/view/domcoords.js +448 -0
  161. package/dist/view/domobserver.d.ts +47 -0
  162. package/dist/view/domobserver.js +328 -0
  163. package/dist/view/errors.d.ts +18 -0
  164. package/dist/view/errors.js +21 -0
  165. package/dist/view/input.d.ts +58 -0
  166. package/dist/view/input.js +704 -0
  167. package/dist/view/selection.js +198 -0
  168. package/dist/view/view.d.ts +699 -0
  169. package/dist/view/view.js +623 -0
  170. package/dist/view/viewdesc.d.ts +236 -0
  171. package/dist/view/viewdesc.js +1205 -0
  172. package/dist/view-css.d.ts +43 -0
  173. package/dist/view-css.js +124 -0
  174. package/dist/view.d.ts +6 -0
  175. package/dist/view.js +4 -0
  176. package/package.json +200 -4
@@ -0,0 +1,486 @@
1
+ import { attempt } from "../internal/attempt.js";
2
+ import { Fragment } from "../model/fragment.js";
3
+ import { JSONError, isModelError } from "../model/errors.js";
4
+ import { Slice } from "../model/replace.js";
5
+ import { ReplaceAroundStep, ReplaceStep } from "../transform/replace_step.js";
6
+ import { SelectionError, isStateFailure } from "./errors.js";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Option from "effect/Option";
9
+ import * as Predicate from "effect/Predicate";
10
+ import * as S from "effect/Schema";
11
+ import * as SchemaGetter from "effect/SchemaGetter";
12
+ import * as SchemaIssue from "effect/SchemaIssue";
13
+ // src/state/selection.ts
14
+ /**
15
+ * Editor selections.
16
+ *
17
+ * @since 0.1.0
18
+ */
19
+ const isPositionOrSelectionError = (u) => isModelError(u) || u instanceof SelectionError;
20
+ const classesById = Object.create(null);
21
+ /**
22
+ * Superclass for editor selections. Every selection type should extend
23
+ * this. Should not be instantiated directly.
24
+ *
25
+ * @since 0.1.0
26
+ * @category models
27
+ */
28
+ var Selection = class Selection {
29
+ /**
30
+ * Initialize a selection with the head and anchor and ranges. If no
31
+ * ranges are given, constructs a single range across `$anchor` and
32
+ * `$head`.
33
+ */
34
+ constructor($anchor, $head, ranges) {
35
+ this.$anchor = $anchor;
36
+ this.$head = $head;
37
+ this.ranges = ranges || [new SelectionRange($anchor.min($head), $anchor.max($head))];
38
+ }
39
+ /** The selection's anchor, as an unresolved position. */
40
+ get anchor() {
41
+ return this.$anchor.pos;
42
+ }
43
+ /** The selection's head. */
44
+ get head() {
45
+ return this.$head.pos;
46
+ }
47
+ /** The lower bound of the selection's main range. */
48
+ get from() {
49
+ return this.$from.pos;
50
+ }
51
+ /** The upper bound of the selection's main range. */
52
+ get to() {
53
+ return this.$to.pos;
54
+ }
55
+ /** The resolved lower bound of the selection's main range. */
56
+ get $from() {
57
+ return this.ranges[0].$from;
58
+ }
59
+ /** The resolved upper bound of the selection's main range. */
60
+ get $to() {
61
+ return this.ranges[0].$to;
62
+ }
63
+ /** Indicates whether the selection contains any content. */
64
+ get empty() {
65
+ const ranges = this.ranges;
66
+ for (let i = 0; i < ranges.length; i++) if (ranges[i].$from.pos !== ranges[i].$to.pos) return false;
67
+ return true;
68
+ }
69
+ /**
70
+ * Controls whether, when a selection of this type is active in the
71
+ * browser, the selected range should be visible to the user.
72
+ * Defaults to `true`.
73
+ */
74
+ get visible() {
75
+ return true;
76
+ }
77
+ /** Get the content of this selection as a slice. */
78
+ content() {
79
+ return this.$from.doc.sliceUnsafe(this.from, this.to, true);
80
+ }
81
+ /**
82
+ * Replace the selection with a slice or, if no slice is given, delete
83
+ * the selection. Will append to the given transaction.
84
+ */
85
+ replace(tr, content = Slice.empty) {
86
+ let lastNode = content.content.content[content.content.childCount - 1];
87
+ let lastParent = null;
88
+ for (let i = 0; i < content.openEnd; i++) {
89
+ lastParent = lastNode;
90
+ lastNode = lastNode.content.content[lastNode.childCount - 1];
91
+ }
92
+ const mapFrom = tr.steps.length, ranges = this.ranges;
93
+ for (let i = 0; i < ranges.length; i++) {
94
+ const { $from, $to } = ranges[i], mapping = tr.mapping.slice(mapFrom);
95
+ tr.replaceRangeUnsafe(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content);
96
+ if (i === 0) selectionToInsertionEnd(tr, mapFrom, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1);
97
+ }
98
+ }
99
+ /** Replace the selection with the given node, appending the changes to the given transaction. */
100
+ replaceWith(tr, node) {
101
+ const mapFrom = tr.steps.length, ranges = this.ranges;
102
+ for (let i = 0; i < ranges.length; i++) {
103
+ const { $from, $to } = ranges[i], mapping = tr.mapping.slice(mapFrom);
104
+ const from = mapping.map($from.pos), to = mapping.map($to.pos);
105
+ if (i) tr.deleteRangeUnsafe(from, to);
106
+ else {
107
+ tr.replaceRangeWithUnsafe(from, to, node);
108
+ selectionToInsertionEnd(tr, mapFrom, node.isInline ? -1 : 1);
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Find a valid cursor or leaf node selection starting at the given
114
+ * position and searching back if `dir` is negative, and forward if
115
+ * positive. When `textOnly` is true, only consider cursor selections.
116
+ * Will return `None` when no valid selection position is found.
117
+ */
118
+ static findFrom($pos, dir, textOnly = false) {
119
+ return Option.fromNullishOr(Selection.findFromOrNull($pos, dir, textOnly));
120
+ }
121
+ /** @internal */
122
+ static findFromOrNull($pos, dir, textOnly = false) {
123
+ const inner = $pos.parent.inlineContent ? new TextSelection($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
124
+ if (inner) return inner;
125
+ for (let depth = $pos.depth - 1; depth >= 0; depth--) {
126
+ const found = dir < 0 ? findSelectionIn($pos.node(0), $pos.node(depth), $pos.beforeUnsafe(depth + 1), $pos.index(depth), dir, textOnly) : findSelectionIn($pos.node(0), $pos.node(depth), $pos.afterUnsafe(depth + 1), $pos.index(depth) + 1, dir, textOnly);
127
+ if (found) return found;
128
+ }
129
+ return null;
130
+ }
131
+ /**
132
+ * Find a valid cursor or leaf node selection near the given position.
133
+ * Searches forward first by default, but if `bias` is negative, it
134
+ * will search backwards first.
135
+ */
136
+ static near($pos, bias = 1) {
137
+ return this.findFromOrNull($pos, bias) || this.findFromOrNull($pos, -bias) || new AllSelection($pos.node(0));
138
+ }
139
+ /**
140
+ * Find the cursor or leaf node selection closest to the start of the
141
+ * given document. Will return an `AllSelection` if no valid position
142
+ * exists.
143
+ */
144
+ static atStart(doc) {
145
+ return findSelectionIn(doc, doc, 0, 0, 1) || new AllSelection(doc);
146
+ }
147
+ /** Find the cursor or leaf node selection closest to the end of the given document. */
148
+ static atEnd(doc) {
149
+ return findSelectionIn(doc, doc, doc.content.size, doc.childCount, -1) || new AllSelection(doc);
150
+ }
151
+ /**
152
+ * Deserialize the JSON representation of a selection. Dispatches to
153
+ * the selection class registered (with `jsonID`) for the JSON's
154
+ * `type`.
155
+ */
156
+ static fromJSON(doc, json) {
157
+ return attempt(() => Selection.fromJSONUnsafe(doc, json), (u) => isModelError(u) || isStateFailure(u));
158
+ }
159
+ /** Like `fromJSON`, but throws on invalid input. */
160
+ static fromJSONUnsafe(doc, json) {
161
+ if (!json || typeof json !== "object" || !json.type) throw new JSONError({ message: "Invalid input for Selection.fromJSON" });
162
+ const cls = classesById[json.type];
163
+ if (!cls) throw new JSONError({ message: `No selection type ${json.type} defined` });
164
+ return cls.fromJSONUnsafe(doc, json);
165
+ }
166
+ /**
167
+ * To be able to deserialize selections from JSON, custom selection
168
+ * classes must register themselves with an ID string, so that they
169
+ * can be disambiguated. Try to pick something that's unlikely to
170
+ * clash with classes from other modules. Throws a `SelectionError`
171
+ * when the ID is already in use.
172
+ */
173
+ static jsonID(id, selectionClass) {
174
+ if (id in classesById) throw new SelectionError({ message: "Duplicate use of selection JSON ID " + id });
175
+ classesById[id] = selectionClass;
176
+ const proto = selectionClass.prototype;
177
+ if (proto) proto.jsonID = id;
178
+ return selectionClass;
179
+ }
180
+ /**
181
+ * Get a bookmark for this selection, which is a value that can be
182
+ * mapped without having access to a current document, and later
183
+ * resolved to a real selection for a given document again. (This is
184
+ * used mostly by the history to track and restore old selections.)
185
+ * The default implementation of this method just converts the
186
+ * selection to a text selection and returns the bookmark for that.
187
+ */
188
+ getBookmark() {
189
+ return TextSelection.between(this.$anchor, this.$head).getBookmark();
190
+ }
191
+ };
192
+ /**
193
+ * Represents a selected range in a document.
194
+ *
195
+ * @since 0.1.0
196
+ * @category models
197
+ */
198
+ var SelectionRange = class {
199
+ /** Create a range. */
200
+ constructor($from, $to) {
201
+ this.$from = $from;
202
+ this.$to = $to;
203
+ }
204
+ };
205
+ let warnedAboutTextSelection = false;
206
+ function checkTextSelection($pos) {
207
+ if (!warnedAboutTextSelection && !$pos.parent.inlineContent) {
208
+ warnedAboutTextSelection = true;
209
+ console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")");
210
+ }
211
+ }
212
+ /**
213
+ * A text selection represents a classical editor selection, with a head
214
+ * (the moving side) and anchor (immobile side), both of which point into
215
+ * textblock nodes. It can be empty (a regular cursor position).
216
+ *
217
+ * @since 0.1.0
218
+ * @category models
219
+ */
220
+ var TextSelection = class TextSelection extends Selection {
221
+ /** Construct a text selection between the given points. */
222
+ constructor($anchor, $head = $anchor) {
223
+ checkTextSelection($anchor);
224
+ checkTextSelection($head);
225
+ super($anchor, $head);
226
+ }
227
+ /** Returns a resolved position if this is a cursor selection (an empty text selection). */
228
+ get $cursor() {
229
+ return Option.fromNullishOr(this.$cursorOrNull);
230
+ }
231
+ /** @internal */
232
+ get $cursorOrNull() {
233
+ return this.$anchor.pos === this.$head.pos ? this.$head : null;
234
+ }
235
+ map(doc, mapping) {
236
+ const $head = doc.resolveUnsafe(mapping.map(this.head));
237
+ if (!$head.parent.inlineContent) return Selection.near($head);
238
+ const $anchor = doc.resolveUnsafe(mapping.map(this.anchor));
239
+ return new TextSelection($anchor.parent.inlineContent ? $anchor : $head, $head);
240
+ }
241
+ replace(tr, content = Slice.empty) {
242
+ super.replace(tr, content);
243
+ if (content === Slice.empty) {
244
+ const marks = this.$from.marksAcrossOrNull(this.$to);
245
+ if (marks) tr.ensureMarks(marks);
246
+ }
247
+ }
248
+ eq(other) {
249
+ return other instanceof TextSelection && other.anchor === this.anchor && other.head === this.head;
250
+ }
251
+ getBookmark() {
252
+ return new TextBookmark(this.anchor, this.head);
253
+ }
254
+ toJSON() {
255
+ return {
256
+ type: "text",
257
+ anchor: this.anchor,
258
+ head: this.head
259
+ };
260
+ }
261
+ /** @internal */
262
+ static fromJSONUnsafe(doc, json) {
263
+ if (typeof json.anchor !== "number" || typeof json.head !== "number") throw new JSONError({ message: "Invalid input for TextSelection.fromJSON" });
264
+ return new TextSelection(doc.resolveUnsafe(json.anchor), doc.resolveUnsafe(json.head));
265
+ }
266
+ /** Create a text selection from non-resolved positions. */
267
+ static create(doc, anchor, head = anchor) {
268
+ return attempt(() => TextSelection.createUnsafe(doc, anchor, head), isModelError);
269
+ }
270
+ /** Like `create`, but throws a `PositionError` for positions outside of the document. */
271
+ static createUnsafe(doc, anchor, head = anchor) {
272
+ const $anchor = doc.resolveUnsafe(anchor);
273
+ return new TextSelection($anchor, head === anchor ? $anchor : doc.resolveUnsafe(head));
274
+ }
275
+ /**
276
+ * Return a text selection that spans the given positions or, if they
277
+ * aren't text positions, find a text selection near them. `bias`
278
+ * determines whether the method searches forward (default) or
279
+ * backwards (negative number) first. Will fall back to calling
280
+ * `Selection.near` when the document doesn't contain a valid text
281
+ * position.
282
+ */
283
+ static between($anchor, $head, bias) {
284
+ const dPos = $anchor.pos - $head.pos;
285
+ if (!bias || dPos) bias = dPos >= 0 ? 1 : -1;
286
+ if (!$head.parent.inlineContent) {
287
+ const found = Selection.findFromOrNull($head, bias, true) || Selection.findFromOrNull($head, -bias, true);
288
+ if (found) $head = found.$head;
289
+ else return Selection.near($head, bias);
290
+ }
291
+ if (!$anchor.parent.inlineContent) {
292
+ if (dPos === 0) $anchor = $head;
293
+ else {
294
+ $anchor = (Selection.findFromOrNull($anchor, -bias, true) || Selection.findFromOrNull($anchor, bias, true)).$anchor;
295
+ if ($anchor.pos < $head.pos !== dPos < 0) $anchor = $head;
296
+ }
297
+ }
298
+ return new TextSelection($anchor, $head);
299
+ }
300
+ };
301
+ Selection.jsonID("text", TextSelection);
302
+ var TextBookmark = class TextBookmark {
303
+ constructor(anchor, head) {
304
+ this.anchor = anchor;
305
+ this.head = head;
306
+ }
307
+ map(mapping) {
308
+ return new TextBookmark(mapping.map(this.anchor), mapping.map(this.head));
309
+ }
310
+ resolve(doc) {
311
+ return TextSelection.between(doc.resolveUnsafe(this.anchor), doc.resolveUnsafe(this.head));
312
+ }
313
+ };
314
+ /**
315
+ * A node selection is a selection that points at a single node. All
316
+ * nodes marked selectable can be the target of a node selection. In
317
+ * such a selection, `from` and `to` point directly before and after the
318
+ * selected node, `anchor` equals `from`, and `head` equals `to`.
319
+ *
320
+ * @since 0.1.0
321
+ * @category models
322
+ */
323
+ var NodeSelection = class NodeSelection extends Selection {
324
+ /**
325
+ * Create a node selection. Does not verify the validity of its
326
+ * argument beyond requiring a node after `$pos` (throwing a
327
+ * `SelectionError` otherwise).
328
+ */
329
+ constructor($pos) {
330
+ const node = $pos.nodeAfterOrNull;
331
+ if (!node) throw new SelectionError({ message: "No node after position " + $pos.pos + " for NodeSelection" });
332
+ const $end = $pos.node(0).resolveUnsafe($pos.pos + node.nodeSize);
333
+ super($pos, $end);
334
+ this.node = node;
335
+ }
336
+ get visible() {
337
+ return false;
338
+ }
339
+ map(doc, mapping) {
340
+ const { deleted, pos } = mapping.mapResult(this.anchor);
341
+ const $pos = doc.resolveUnsafe(pos);
342
+ if (deleted) return Selection.near($pos);
343
+ return new NodeSelection($pos);
344
+ }
345
+ content() {
346
+ return new Slice(Fragment.from(this.node), 0, 0);
347
+ }
348
+ eq(other) {
349
+ return other instanceof NodeSelection && other.anchor === this.anchor;
350
+ }
351
+ toJSON() {
352
+ return {
353
+ type: "node",
354
+ anchor: this.anchor
355
+ };
356
+ }
357
+ getBookmark() {
358
+ return new NodeBookmark(this.anchor);
359
+ }
360
+ /** @internal */
361
+ static fromJSONUnsafe(doc, json) {
362
+ if (typeof json.anchor !== "number") throw new JSONError({ message: "Invalid input for NodeSelection.fromJSON" });
363
+ return new NodeSelection(doc.resolveUnsafe(json.anchor));
364
+ }
365
+ /** Create a node selection from a non-resolved position. */
366
+ static create(doc, from) {
367
+ return attempt(() => NodeSelection.createUnsafe(doc, from), isPositionOrSelectionError);
368
+ }
369
+ /** Like `create`, but throws for invalid positions. */
370
+ static createUnsafe(doc, from) {
371
+ return new NodeSelection(doc.resolveUnsafe(from));
372
+ }
373
+ /** Determines whether the given node may be selected as a node selection. */
374
+ static isSelectable(node) {
375
+ return !node.isText && node.type.spec.selectable !== false;
376
+ }
377
+ };
378
+ Selection.jsonID("node", NodeSelection);
379
+ var NodeBookmark = class NodeBookmark {
380
+ constructor(anchor) {
381
+ this.anchor = anchor;
382
+ }
383
+ map(mapping) {
384
+ const { deleted, pos } = mapping.mapResult(this.anchor);
385
+ return deleted ? new TextBookmark(pos, pos) : new NodeBookmark(pos);
386
+ }
387
+ resolve(doc) {
388
+ const $pos = doc.resolveUnsafe(this.anchor), node = $pos.nodeAfterOrNull;
389
+ if (node && NodeSelection.isSelectable(node)) return new NodeSelection($pos);
390
+ return Selection.near($pos);
391
+ }
392
+ };
393
+ /**
394
+ * A selection type that represents selecting the whole document (which
395
+ * can not necessarily be expressed with a text selection, when there
396
+ * are for example leaf block nodes at the start or end of the
397
+ * document).
398
+ *
399
+ * @since 0.1.0
400
+ * @category models
401
+ */
402
+ var AllSelection = class AllSelection extends Selection {
403
+ /** Create an all-selection over the given document. */
404
+ constructor(doc) {
405
+ super(doc.resolveUnsafe(0), doc.resolveUnsafe(doc.content.size));
406
+ }
407
+ replace(tr, content = Slice.empty) {
408
+ if (content === Slice.empty) {
409
+ tr.deleteUnsafe(0, tr.doc.content.size);
410
+ const sel = Selection.atStart(tr.doc);
411
+ if (!sel.eq(tr.selection)) tr.setSelectionUnsafe(sel);
412
+ } else super.replace(tr, content);
413
+ }
414
+ toJSON() {
415
+ return { type: "all" };
416
+ }
417
+ /** @internal */
418
+ static fromJSONUnsafe(doc) {
419
+ return new AllSelection(doc);
420
+ }
421
+ map(doc) {
422
+ return new AllSelection(doc);
423
+ }
424
+ eq(other) {
425
+ return other instanceof AllSelection;
426
+ }
427
+ getBookmark() {
428
+ return AllBookmark;
429
+ }
430
+ };
431
+ Selection.jsonID("all", AllSelection);
432
+ const AllBookmark = {
433
+ map() {
434
+ return this;
435
+ },
436
+ resolve(doc) {
437
+ return new AllSelection(doc);
438
+ }
439
+ };
440
+ function findSelectionIn(doc, node, pos, index, dir, text = false) {
441
+ if (node.inlineContent) return TextSelection.createUnsafe(doc, pos);
442
+ for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
443
+ const child = node.content.content[i];
444
+ if (!child.isAtom) {
445
+ const inner = findSelectionIn(doc, child, pos + dir, dir < 0 ? child.childCount : 0, dir, text);
446
+ if (inner) return inner;
447
+ } else if (!text && NodeSelection.isSelectable(child)) return NodeSelection.createUnsafe(doc, pos - (dir < 0 ? child.nodeSize : 0));
448
+ pos += child.nodeSize * dir;
449
+ }
450
+ return null;
451
+ }
452
+ function selectionToInsertionEnd(tr, startLen, bias) {
453
+ const last = tr.steps.length - 1;
454
+ if (last < startLen) return;
455
+ const step = tr.steps[last];
456
+ if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) return;
457
+ const map = tr.mapping.maps[last];
458
+ let end;
459
+ map.forEach((_from, _to, _newFrom, newTo) => {
460
+ if (end == null) end = newTo;
461
+ });
462
+ tr.setSelectionUnsafe(Selection.near(tr.doc.resolveUnsafe(end), bias));
463
+ }
464
+ const SelectionJSONSchema = S.declare((u) => typeof u === "object" && u !== null && typeof u.type === "string", { title: "SelectionJSON" });
465
+ /**
466
+ * An Effect `Schema` that decodes the JSON representation of a
467
+ * selection (using the `Selection.jsonID` registry) into a selection in
468
+ * the given document, and encodes selections back to JSON.
469
+ *
470
+ * @since 0.1.0
471
+ * @category schemas
472
+ */
473
+ const SelectionFromJSON = (doc) => {
474
+ const target = S.declare((u) => u instanceof Selection && u.$anchor.doc === doc, { title: "Selection" });
475
+ return SelectionJSONSchema.pipe(S.decodeTo(target, {
476
+ decode: SchemaGetter.transformEffect((json) => Effect.suspend(() => {
477
+ try {
478
+ return Effect.succeed(Selection.fromJSONUnsafe(doc, json));
479
+ } catch (e) {
480
+ return Effect.fail(new SchemaIssue.InvalidValue({ message: Predicate.hasProperty(e, "message") ? String(e.message) : String(e) }, json));
481
+ }
482
+ })),
483
+ encode: SchemaGetter.transform((selection) => selection.toJSON())
484
+ }));
485
+ };
486
+ export { AllSelection, NodeSelection, Selection, SelectionFromJSON, SelectionRange, TextSelection };
@@ -0,0 +1,212 @@
1
+ import { ModelError } from "../model/errors.js";
2
+ import { AnySchema } from "../model/schema.js";
3
+ import { Mark, MarkJSON } from "../model/mark.js";
4
+ import { Node, NodeJSON } from "../model/node.js";
5
+ import "../model.js";
6
+ import { SelectionError, StateError } from "./errors.js";
7
+ import { AnyPlugin, Plugin, PluginErrors, PluginRequirements, StateField } from "./plugin.js";
8
+ import { Selection, SelectionJSON } from "./selection.js";
9
+ import { Transaction } from "./transaction.js";
10
+ import * as Effect from "effect/Effect";
11
+ import * as Option from "effect/Option";
12
+ /**
13
+ * The type of object passed to `EditorState.create`.
14
+ *
15
+ * @since 0.1.0
16
+ * @category models
17
+ */
18
+ export interface EditorStateConfig<Sc extends AnySchema = AnySchema, P extends ReadonlyArray<AnyPlugin> = ReadonlyArray<AnyPlugin>> {
19
+ /** The schema to use (only relevant if no `doc` is specified). */
20
+ readonly schema?: Sc | undefined;
21
+ /** The starting document. Either this or `schema` _must_ be provided. */
22
+ readonly doc?: Node<Sc> | undefined;
23
+ /** A valid selection in the document. */
24
+ readonly selection?: Selection<Sc> | undefined;
25
+ /** The initial set of stored marks. */
26
+ readonly storedMarks?: ReadonlyArray<Mark<Sc>> | null | undefined;
27
+ /** The plugins that should be active in this state. */
28
+ readonly plugins?: P | undefined;
29
+ }
30
+ /**
31
+ * The JSON representation of an editor state (see `EditorState.toJSON`).
32
+ *
33
+ * @since 0.1.0
34
+ * @category models
35
+ */
36
+ export interface EditorStateJSON {
37
+ readonly doc: NodeJSON;
38
+ readonly selection: SelectionJSON;
39
+ readonly storedMarks?: ReadonlyArray<MarkJSON>;
40
+ readonly [field: string]: unknown;
41
+ }
42
+ /**
43
+ * The result of `EditorState.applyTransaction`.
44
+ *
45
+ * @since 0.1.0
46
+ * @category models
47
+ */
48
+ export interface ApplyResult<Sc extends AnySchema, R, E> {
49
+ readonly state: EditorState<Sc, R, E>;
50
+ readonly transactions: ReadonlyArray<Transaction<Sc>>;
51
+ }
52
+ interface FieldDesc {
53
+ readonly name: string;
54
+ readonly self: unknown;
55
+ readonly init: StateField<any, any, any, any>["init"];
56
+ readonly apply: StateField<any, any, any, any>["apply"];
57
+ }
58
+ /**
59
+ * Object wrapping the part of a state object that stays the same across
60
+ * transactions. Stored in the state's `config` property.
61
+ *
62
+ * @internal
63
+ */
64
+ export declare class Configuration<Sc extends AnySchema> {
65
+ readonly schema: Sc;
66
+ readonly fields: Array<FieldDesc>;
67
+ readonly plugins: Array<AnyPlugin>;
68
+ readonly pluginsByKey: Record<string, AnyPlugin>;
69
+ constructor(schema: Sc, plugins?: ReadonlyArray<AnyPlugin>);
70
+ }
71
+ /** Marker returned by the synchronous fast path when a hook returned an Effect. */
72
+ declare const NeedsEffect: unique symbol;
73
+ /**
74
+ * Run an effect synchronously, throwing its (first) failure or defect.
75
+ *
76
+ * @internal
77
+ */
78
+ export declare const runSyncUnsafe: <A, E>(effect: Effect.Effect<A, E>) => A;
79
+ /**
80
+ * @since 0.1.0
81
+ * @category symbols
82
+ */
83
+ export declare const EditorStateTypeId: unique symbol;
84
+ /**
85
+ * @since 0.1.0
86
+ * @category symbols
87
+ */
88
+ export type EditorStateTypeId = typeof EditorStateTypeId;
89
+ /**
90
+ * The state of an editor is represented by an object of this type. A
91
+ * state is a persistent data structure—it isn't updated, but rather a
92
+ * new state value is computed from an old one using the `apply` method.
93
+ *
94
+ * A state holds a number of built-in fields, and plugins can define
95
+ * additional fields.
96
+ *
97
+ * `R` and `E` are the unions of the requirements and errors of the
98
+ * state's plugins: applying a transaction runs the plugins' hooks, so
99
+ * `apply` returns an `Effect<EditorState<Sc, R, E>, E | StateError, R>`.
100
+ * States whose plugins require no services (`R = never`) can also be
101
+ * updated synchronously with `applyUnsafe`.
102
+ *
103
+ * @since 0.1.0
104
+ * @category models
105
+ */
106
+ export declare class EditorState<Sc extends AnySchema = AnySchema, R = never, E = never> {
107
+ /** @internal */
108
+ readonly config: Configuration<Sc>;
109
+ /** @internal */
110
+ readonly slots: Record<string, any>;
111
+ readonly [EditorStateTypeId]: {
112
+ readonly _R: (_: never) => R;
113
+ readonly _E: (_: never) => E;
114
+ };
115
+ /** @internal */
116
+ constructor(
117
+ /** @internal */
118
+ config: Configuration<Sc>,
119
+ /** @internal */
120
+ slots?: Record<string, any>);
121
+ /** The current document. */
122
+ get doc(): Node<Sc>;
123
+ /** The selection. */
124
+ get selection(): Selection<Sc>;
125
+ /** A set of marks to apply to the next input. `None` when no explicit marks have been set. */
126
+ get storedMarks(): Option.Option<ReadonlyArray<Mark<Sc>>>;
127
+ /** @internal */
128
+ get storedMarksOrNull(): ReadonlyArray<Mark<Sc>> | null;
129
+ /** @internal */
130
+ get scrollToSelection(): number;
131
+ /** The schema of the state's document. */
132
+ get schema(): Sc;
133
+ /** The plugins that are active in this state. */
134
+ get plugins(): ReadonlyArray<Plugin<any, Sc, E, R>>;
135
+ /** Create a new transaction from this state. */
136
+ get tr(): Transaction<Sc>;
137
+ /** Apply the given transaction to produce a new state. */
138
+ apply(tr: Transaction<Sc>): Effect.Effect<EditorState<Sc, R, E>, E | StateError, R>;
139
+ /**
140
+ * Like `apply`, but runs synchronously, throwing failures. Only
141
+ * available for states whose plugins require no services; fails with
142
+ * a defect (thrown) when a plugin hook is asynchronous.
143
+ */
144
+ applyUnsafe(this: EditorState<Sc, never, E>, tr: Transaction<Sc>): EditorState<Sc, never, E>;
145
+ /** @internal */
146
+ filterTransaction(tr: Transaction<Sc>, ignore?: number): Effect.Effect<boolean, E, R>;
147
+ /**
148
+ * Verbose variant of `apply` that returns the precise transactions
149
+ * that were applied (which might be influenced by the transaction
150
+ * hooks of plugins) along with the new state.
151
+ */
152
+ applyTransaction(rootTr: Transaction<Sc>): Effect.Effect<ApplyResult<Sc, R, E>, E | StateError, R>;
153
+ /** @internal */
154
+ applyTransactionSync(rootTr: Transaction<Sc>): ApplyResult<Sc, R, E> | typeof NeedsEffect;
155
+ /** @internal */
156
+ filterTransactionSync(tr: Transaction<Sc>, ignore?: number): boolean | typeof NeedsEffect;
157
+ /** @internal */
158
+ applyInnerSync(tr: Transaction<Sc>): EditorState<Sc, R, E> | typeof NeedsEffect;
159
+ /** @internal */
160
+ applyTransactionEffect(rootTr: Transaction<Sc>): Effect.Effect<ApplyResult<Sc, R, E>, E | StateError, R>;
161
+ /** Like `applyTransaction`, but runs synchronously (see `applyUnsafe`). */
162
+ applyTransactionUnsafe(this: EditorState<Sc, never, E>, rootTr: Transaction<Sc>): ApplyResult<Sc, never, E>;
163
+ /** @internal */
164
+ applyInner(tr: Transaction<Sc>): Effect.Effect<EditorState<Sc, R, E>, E | StateError, R>;
165
+ /**
166
+ * Create a new state based on this one, but with an adjusted set of
167
+ * active plugins. State fields that exist in both sets of plugins are
168
+ * kept unchanged. Those that no longer exist are dropped, and those
169
+ * that are new are initialized using their `init` method, passing in
170
+ * the new configuration object.
171
+ */
172
+ reconfigure<const P extends ReadonlyArray<AnyPlugin> = readonly []>(config: {
173
+ readonly plugins?: P | undefined;
174
+ }): Effect.Effect<EditorState<Sc, PluginRequirements<P>, PluginErrors<P>>, PluginErrors<P> | StateError, PluginRequirements<P>>;
175
+ /** Like `reconfigure`, but runs synchronously (see `applyUnsafe`). */
176
+ reconfigureUnsafe<const P extends ReadonlyArray<Plugin<any, any, any, never>> = readonly []>(config: {
177
+ readonly plugins?: P | undefined;
178
+ }): EditorState<Sc, never, PluginErrors<P>>;
179
+ /**
180
+ * Serialize this state to JSON. If you want to serialize the state of
181
+ * plugins, pass an object mapping property names to use in the
182
+ * resulting JSON object to plugin objects. The argument may also be a
183
+ * string or number, in which case it is ignored, to support the way
184
+ * `JSON.stringify` calls `toJSON` methods. Throws a `StateError` when
185
+ * a plugin is mapped to the reserved `doc` or `selection` fields.
186
+ */
187
+ toJSON(pluginFields?: Record<string, AnyPlugin> | string | number): EditorStateJSON;
188
+ /** Create a new state. */
189
+ static create<Sc extends AnySchema, const P extends ReadonlyArray<AnyPlugin> = readonly []>(config: EditorStateConfig<Sc, P>): Effect.Effect<EditorState<Sc, PluginRequirements<P>, PluginErrors<P>>, PluginErrors<P> | StateError, PluginRequirements<P>>;
190
+ /**
191
+ * Like `create`, but runs synchronously. Only available when the
192
+ * plugins require no services; throws on failure.
193
+ */
194
+ static createUnsafe<Sc extends AnySchema, const P extends ReadonlyArray<Plugin<any, any, any, never>> = readonly []>(config: EditorStateConfig<Sc, P>): EditorState<Sc, never, PluginErrors<P>>;
195
+ /**
196
+ * Deserialize a JSON representation of a state. `config` should have
197
+ * at least a `schema` field, and should contain an array of plugins to
198
+ * initialize the state with. `pluginFields` can be used to deserialize
199
+ * the state of plugins, by associating plugin instances with the
200
+ * property names they use in the JSON object.
201
+ */
202
+ static fromJSON<Sc extends AnySchema, const P extends ReadonlyArray<AnyPlugin> = readonly []>(config: {
203
+ readonly schema: Sc;
204
+ readonly plugins?: P | undefined;
205
+ }, json: unknown, pluginFields?: Record<string, AnyPlugin>): Effect.Effect<EditorState<Sc, PluginRequirements<P>, PluginErrors<P>>, PluginErrors<P> | StateError | SelectionError | ModelError, PluginRequirements<P>>;
206
+ /** Like `fromJSON`, but runs synchronously (see `createUnsafe`). */
207
+ static fromJSONUnsafe<Sc extends AnySchema, const P extends ReadonlyArray<Plugin<any, any, any, never>> = readonly []>(config: {
208
+ readonly schema: Sc;
209
+ readonly plugins?: P | undefined;
210
+ }, json: unknown, pluginFields?: Record<string, AnyPlugin>): EditorState<Sc, never, PluginErrors<P>>;
211
+ }
212
+ export {};