@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,233 @@
1
+ import { keymap } from "../keymap/keymap.js";
2
+ import * as Arr from "effect/Array";
3
+ import * as Order from "effect/Order";
4
+ // src/extensions/extension.ts
5
+ /**
6
+ * Extensions: composable, strongly typed bundles of schema specs,
7
+ * plugins, keymaps and commands.
8
+ *
9
+ * @since 0.1.0
10
+ */
11
+ /**
12
+ * Extension priorities. Within a merged extension, facets with a higher
13
+ * priority come first: their node and mark specs come earlier in the
14
+ * schema (which matters for the default node of a group and for parse
15
+ * rule precedence), their plugins and keymaps come earlier in the
16
+ * plugin list (so their key bindings and props take precedence), and
17
+ * their commands win over commands with the same name. Facets with the
18
+ * same priority keep their declaration order.
19
+ *
20
+ * @since 0.1.0
21
+ * @category models
22
+ */
23
+ const Priority = {
24
+ lowest: 0,
25
+ low: 1,
26
+ default: 2,
27
+ high: 3,
28
+ highest: 4
29
+ };
30
+ /**
31
+ * @since 0.1.0
32
+ * @category symbols
33
+ */
34
+ const ExtensionTypeId = Symbol.for("effect-richtext/extensions/Extension");
35
+ /**
36
+ * An extension: a tree of facets (node and mark specs, plugins,
37
+ * keymaps and commands), each with an optional priority. `T` records,
38
+ * at the type level, what the extension contributes. Extensions are
39
+ * immutable values; combine them with `union` and turn them into an
40
+ * editor with `createEditor`.
41
+ *
42
+ * @since 0.1.0
43
+ * @category models
44
+ */
45
+ var Extension = class {
46
+ /** @internal */
47
+ constructor(spec, children, priority) {
48
+ this.spec = spec;
49
+ this.children = children;
50
+ this.priority = priority;
51
+ }
52
+ };
53
+ /**
54
+ * Whether a value is an `Extension`.
55
+ *
56
+ * @since 0.1.0
57
+ * @category guards
58
+ */
59
+ const isExtension = (u) => u instanceof Extension;
60
+ /**
61
+ * Define an extension from node and mark specs, plugins, a keymap and
62
+ * commands. Every field is optional; the types of all of them are
63
+ * recorded in the extension's type.
64
+ *
65
+ * ```ts skip-type-checking
66
+ * const bold = defineExtension({
67
+ * marks: { strong: { parseDOM: [{ tag: "strong" }], toDOM: () => ["strong", 0] } },
68
+ * commands: { toggleBold: () => (state, dispatch) => toggleMark(state.schema.marks.strong!)(state, dispatch) },
69
+ * keymap: { "Mod-b": (state, dispatch) => toggleMark(state.schema.marks.strong!)(state, dispatch) }
70
+ * })
71
+ * ```
72
+ *
73
+ * Throws a `KeymapError` when a key name in `keymap` is invalid.
74
+ *
75
+ * @since 0.1.0
76
+ * @category constructors
77
+ */
78
+ const defineExtension = (spec) => {
79
+ const plugins = spec.plugins ? [...spec.plugins] : [];
80
+ if (spec.keymap) plugins.push(keymap(spec.keymap));
81
+ return new Extension({
82
+ nodes: spec.nodes,
83
+ marks: spec.marks,
84
+ plugins,
85
+ commands: spec.commands
86
+ }, [], void 0);
87
+ };
88
+ /**
89
+ * Merge extensions. The result contributes everything its parts
90
+ * contribute: node, mark and command types are intersected, `E` and `R`
91
+ * are unions. At runtime facets are ordered by priority, then by
92
+ * declaration order (depth first); including the same extension value
93
+ * more than once only uses its first occurrence.
94
+ *
95
+ * @since 0.1.0
96
+ * @category combinators
97
+ */
98
+ const union = (...extensions) => new Extension(void 0, extensions, void 0);
99
+ /**
100
+ * Give an extension (and every part of it that doesn't set its own
101
+ * priority) a priority.
102
+ *
103
+ * @since 0.1.0
104
+ * @category combinators
105
+ */
106
+ const withPriority = (extension, priority) => new Extension(void 0, [extension], priority);
107
+ /**
108
+ * Define an extension adding a single node spec.
109
+ *
110
+ * @since 0.1.0
111
+ * @category constructors
112
+ */
113
+ const defineNodeSpec = (name, spec) => new Extension({ nodes: { [name]: spec } }, [], void 0);
114
+ /**
115
+ * Define an extension adding a single mark spec.
116
+ *
117
+ * @since 0.1.0
118
+ * @category constructors
119
+ */
120
+ const defineMarkSpec = (name, spec) => new Extension({ marks: { [name]: spec } }, [], void 0);
121
+ /**
122
+ * Define an extension adding plugins: a plugin, an array of plugins, or
123
+ * a function creating them from the editor's schema.
124
+ *
125
+ * @since 0.1.0
126
+ * @category constructors
127
+ */
128
+ const definePlugin = (plugin) => new Extension({ plugins: [plugin] }, [], void 0);
129
+ /**
130
+ * Define an extension adding a keymap (see `keymap`). Throws a
131
+ * `KeymapError` when a key name is invalid or bound twice.
132
+ *
133
+ * @since 0.1.0
134
+ * @category constructors
135
+ */
136
+ const defineKeymap = (bindings) => defineExtension({ keymap: bindings });
137
+ /**
138
+ * Define an extension adding commands. Each command is given as a
139
+ * function from the command's arguments to a `Command`; the argument
140
+ * types become part of the editor's `commands`, `can()` and `chain()`
141
+ * APIs.
142
+ *
143
+ * ```ts skip-type-checking
144
+ * const ext = defineCommands({
145
+ * insertHello: (times: number) => (state, dispatch) => ...
146
+ * })
147
+ * ```
148
+ *
149
+ * For commands whose argument types depend on the final schema, declare
150
+ * a `CommandSignature` and cast the extension (see `CommandsExtension`).
151
+ *
152
+ * @since 0.1.0
153
+ * @category constructors
154
+ */
155
+ const defineCommands = (commands) => new Extension({ commands }, [], void 0);
156
+ const mergeSpec = (base, over) => {
157
+ if (!base) return over;
158
+ const attrs = base.attrs || over.attrs ? {
159
+ ...base.attrs,
160
+ ...over.attrs
161
+ } : void 0;
162
+ const merged = {
163
+ ...base,
164
+ ...over
165
+ };
166
+ return attrs ? {
167
+ ...merged,
168
+ attrs
169
+ } : merged;
170
+ };
171
+ const byPriorityDesc = (a, b) => Order.Number(b.priority, a.priority);
172
+ /**
173
+ * Flatten an extension tree. Facets are sorted by priority (highest
174
+ * first), keeping declaration order for equal priorities. Node and mark
175
+ * specs with the same name are shallow-merged (attributes merged), with
176
+ * the higher-priority spec winning, at the position of the first
177
+ * occurrence; for commands with the same name the higher-priority one
178
+ * wins.
179
+ *
180
+ * @internal
181
+ */
182
+ const flatten = (extension) => {
183
+ const collected = [];
184
+ const seen = /* @__PURE__ */ new WeakSet();
185
+ const visit = (ext, inherited) => {
186
+ if (seen.has(ext)) return;
187
+ seen.add(ext);
188
+ const priority = ext.priority ?? inherited;
189
+ if (ext.spec) collected.push({
190
+ spec: ext.spec,
191
+ priority
192
+ });
193
+ for (const child of ext.children) visit(child, priority);
194
+ };
195
+ visit(extension, Priority.default);
196
+ const facets = Arr.sort(collected, byPriorityDesc);
197
+ const nodes = {};
198
+ const marks = {};
199
+ const plugins = [];
200
+ const commands = {};
201
+ for (const { spec } of facets) {
202
+ for (const name in spec.nodes) if (!(name in nodes)) nodes[name] = void 0;
203
+ for (const name in spec.marks) if (!(name in marks)) marks[name] = void 0;
204
+ if (spec.plugins) plugins.push(...spec.plugins);
205
+ for (const name in spec.commands) if (!(name in commands)) commands[name] = spec.commands[name];
206
+ }
207
+ for (let i = facets.length - 1; i >= 0; i--) {
208
+ const spec = facets[i].spec;
209
+ for (const name in spec.nodes) nodes[name] = mergeSpec(nodes[name], spec.nodes[name]);
210
+ for (const name in spec.marks) marks[name] = mergeSpec(marks[name], spec.marks[name]);
211
+ }
212
+ return {
213
+ nodes,
214
+ marks,
215
+ plugins,
216
+ commands
217
+ };
218
+ };
219
+ /**
220
+ * Resolve plugin inputs against a schema.
221
+ *
222
+ * @internal
223
+ */
224
+ const resolvePlugins = (inputs, schema) => {
225
+ const result = [];
226
+ const add = (input) => {
227
+ if (Array.isArray(input)) result.push(...input);
228
+ else result.push(input);
229
+ };
230
+ for (const input of inputs) add(typeof input === "function" ? input(schema) : input);
231
+ return result;
232
+ };
233
+ export { Extension, ExtensionTypeId, Priority, defineCommands, defineExtension, defineKeymap, defineMarkSpec, defineNodeSpec, definePlugin, flatten, isExtension, resolvePlugins, union, withPriority };
@@ -0,0 +1,4 @@
1
+ import { BaseCommands, ListCommands, NodeTypeCommand, ToggleMarkCommand, defineBaseCommands, defineBaseKeymap, defineBasicExtension, defineBlockquote, defineCode, defineCodeBlock, defineDoc, defineEm, defineHardBreak, defineHeading, defineHistory, defineHorizontalRule, defineImage, defineLink, defineList, defineParagraph, defineStrong, defineText } from "./extensions/defaults.js";
2
+ import { CanCommands, ChainRun, ChainedCommands, CommandFn, CreateEditorError, Editor, EditorCommands, EditorContent, EditorOptions, EditorTypeId, MarkBuilder, MarkBuilders, MountProps, NodeBuilder, NodeBuilders, NodeChild, SchemaOf, createEditor, createEditorUnsafe } from "./extensions/editor.js";
3
+ import { AnyExtension, CommandArgsOf, CommandCreator, CommandCreators, CommandSignature, CommandType, CommandTypes, CommandsError, CommandsExtension, CommandsRequirements, EmptyTypes, Extension, ExtensionCommands, ExtensionMarks, ExtensionNodes, ExtensionSpec, ExtensionTypeId, ExtensionTypes, MergeTypes, PluginInput, Priority, SpecTypes, TypesOf, defineCommands, defineExtension, defineKeymap, defineMarkSpec, defineNodeSpec, definePlugin, isExtension, union, withPriority } from "./extensions/extension.js";
4
+ export { type AnyExtension, BaseCommands, CanCommands, ChainRun, ChainedCommands, type CommandArgsOf, type CommandCreator, type CommandCreators, CommandFn, type CommandSignature, type CommandType, type CommandTypes, type CommandsError, type CommandsExtension, type CommandsRequirements, CreateEditorError, Editor, EditorCommands, EditorContent, EditorOptions, EditorTypeId, type EmptyTypes, Extension, type ExtensionCommands, type ExtensionMarks, type ExtensionNodes, type ExtensionSpec, ExtensionTypeId, type ExtensionTypes, ListCommands, MarkBuilder, MarkBuilders, type MergeTypes, MountProps, NodeBuilder, NodeBuilders, NodeChild, NodeTypeCommand, type PluginInput, Priority, SchemaOf, type SpecTypes, ToggleMarkCommand, type TypesOf, createEditor, createEditorUnsafe, defineBaseCommands, defineBaseKeymap, defineBasicExtension, defineBlockquote, defineCode, defineCodeBlock, defineCommands, defineDoc, defineEm, defineExtension, defineHardBreak, defineHeading, defineHistory, defineHorizontalRule, defineImage, defineKeymap, defineLink, defineList, defineMarkSpec, defineNodeSpec, defineParagraph, definePlugin, defineStrong, defineText, isExtension, union, withPriority };
@@ -0,0 +1,4 @@
1
+ import { defineBaseCommands, defineBaseKeymap, defineBasicExtension, defineBlockquote, defineCode, defineCodeBlock, defineDoc, defineEm, defineHardBreak, defineHeading, defineHistory, defineHorizontalRule, defineImage, defineLink, defineList, defineParagraph, defineStrong, defineText } from "./extensions/defaults.js";
2
+ import { Editor, EditorTypeId, createEditor, createEditorUnsafe } from "./extensions/editor.js";
3
+ import { Extension, ExtensionTypeId, Priority, defineCommands, defineExtension, defineKeymap, defineMarkSpec, defineNodeSpec, definePlugin, isExtension, union, withPriority } from "./extensions/extension.js";
4
+ export { Editor, EditorTypeId, Extension, ExtensionTypeId, Priority, createEditor, createEditorUnsafe, defineBaseCommands, defineBaseKeymap, defineBasicExtension, defineBlockquote, defineCode, defineCodeBlock, defineCommands, defineDoc, defineEm, defineExtension, defineHardBreak, defineHeading, defineHistory, defineHorizontalRule, defineImage, defineKeymap, defineLink, defineList, defineMarkSpec, defineNodeSpec, defineParagraph, definePlugin, defineStrong, defineText, isExtension, union, withPriority };
@@ -0,0 +1,66 @@
1
+ import { ResolvedPos } from "./model/resolvedpos.js";
2
+ import { Slice } from "./model/replace.js";
3
+ import { AnySchema } from "./model/schema.js";
4
+ import { Node } from "./model/node.js";
5
+ import "./model.js";
6
+ import { Mappable } from "./transform/map.js";
7
+ import "./state.js";
8
+ import { Selection, SelectionBookmark, SelectionJSON } from "./state/selection.js";
9
+ import { Plugin } from "./state/plugin.js";
10
+ import "./transform.js";
11
+ import * as Option from "effect/Option";
12
+ /**
13
+ * Gap cursor selections are represented using this class. Its `$anchor`
14
+ * and `$head` properties both point at the cursor position.
15
+ *
16
+ * Node specs may set `allowGapCursor` to explicitly allow or disallow
17
+ * gap cursors inside nodes of that type, and `createGapCursor` to make
18
+ * the positions around nodes of that type valid gap cursor positions.
19
+ *
20
+ * @since 0.1.0
21
+ * @category models
22
+ */
23
+ export declare class GapCursor<Sc extends AnySchema = AnySchema> extends Selection<Sc> {
24
+ /** Create a gap cursor. */
25
+ constructor($pos: ResolvedPos<Sc>);
26
+ get visible(): boolean;
27
+ map(doc: Node<Sc>, mapping: Mappable): Selection<Sc>;
28
+ content(): Slice<Sc>;
29
+ eq(other: Selection<any>): boolean;
30
+ toJSON(): SelectionJSON;
31
+ /** @internal */
32
+ static fromJSONUnsafe<Sc extends AnySchema>(doc: Node<Sc>, json: any): GapCursor<Sc>;
33
+ getBookmark(): SelectionBookmark<Sc>;
34
+ /** Whether a gap cursor is valid at the given position. */
35
+ static valid($pos: ResolvedPos<any>): boolean;
36
+ /**
37
+ * Find a valid gap cursor position starting at `$pos` and searching in
38
+ * direction `dir` (negative is backwards). When `mustMove` is true,
39
+ * `$pos` itself is not considered.
40
+ */
41
+ static findGapCursorFrom<Sc extends AnySchema>($pos: ResolvedPos<Sc>, dir: number, mustMove?: boolean): Option.Option<ResolvedPos<Sc>>;
42
+ /** @internal */
43
+ static findGapCursorFromOrNull<Sc extends AnySchema>($pos: ResolvedPos<Sc>, dir: number, mustMove?: boolean): ResolvedPos<Sc> | null;
44
+ }
45
+ /**
46
+ * Create a gap cursor plugin. When enabled, this will capture clicks
47
+ * near and arrow-key-motion past places that don't have a normally
48
+ * selectable position nearby, and create a gap cursor selection for
49
+ * them. The cursor is drawn as an element with class
50
+ * `ProseMirror-gapcursor`. You can either include `gapCursorCSS` or add
51
+ * your own styles to make it visible.
52
+ *
53
+ * @since 0.1.0
54
+ * @category constructors
55
+ */
56
+ export declare const gapCursor: () => Plugin<undefined>;
57
+ /**
58
+ * The gap cursor's stylesheet (from `prosemirror-gapcursor`'s
59
+ * `style/gapcursor.css`). Add it to the page (for example with
60
+ * `injectCSS` from `@thomasfosterau/effect-richtext/view-css`) or provide your own
61
+ * styles for `.ProseMirror-gapcursor`.
62
+ *
63
+ * @since 0.1.0
64
+ * @category styles
65
+ */
66
+ export declare const gapCursorCSS: string;
@@ -0,0 +1,273 @@
1
+ import { keydownHandler } from "./keymap/keymap.js";
2
+ import { Fragment } from "./model/fragment.js";
3
+ import { JSONError } from "./model/errors.js";
4
+ import { Slice } from "./model/replace.js";
5
+ import { NodeSelection, Selection, TextSelection } from "./state/selection.js";
6
+ import { Plugin } from "./state/plugin.js";
7
+ import { Decoration, DecorationSet } from "./view/decoration.js";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Option from "effect/Option";
10
+ import * as Predicate from "effect/Predicate";
11
+ // src/gapcursor.ts
12
+ /**
13
+ * Gap cursors: a selection type (and plugin) for focusing places that
14
+ * don't allow regular selection, such as positions between two
15
+ * adjacent block nodes that aren't textblocks. Ported from
16
+ * `prosemirror-gapcursor`.
17
+ *
18
+ * Importing this module registers the `GapCursor` selection class for
19
+ * JSON deserialization (under the ID `"gapcursor"`).
20
+ *
21
+ * @since 0.1.0
22
+ */
23
+ const child = (node, index) => node.content.content[index];
24
+ /**
25
+ * Gap cursor selections are represented using this class. Its `$anchor`
26
+ * and `$head` properties both point at the cursor position.
27
+ *
28
+ * Node specs may set `allowGapCursor` to explicitly allow or disallow
29
+ * gap cursors inside nodes of that type, and `createGapCursor` to make
30
+ * the positions around nodes of that type valid gap cursor positions.
31
+ *
32
+ * @since 0.1.0
33
+ * @category models
34
+ */
35
+ var GapCursor = class GapCursor extends Selection {
36
+ /** Create a gap cursor. */
37
+ constructor($pos) {
38
+ super($pos, $pos);
39
+ }
40
+ get visible() {
41
+ return false;
42
+ }
43
+ map(doc, mapping) {
44
+ const $pos = doc.resolveUnsafe(mapping.map(this.head));
45
+ return GapCursor.valid($pos) ? new GapCursor($pos) : Selection.near($pos);
46
+ }
47
+ content() {
48
+ return Slice.empty;
49
+ }
50
+ eq(other) {
51
+ return other instanceof GapCursor && other.head === this.head;
52
+ }
53
+ toJSON() {
54
+ return {
55
+ type: "gapcursor",
56
+ pos: this.head
57
+ };
58
+ }
59
+ /** @internal */
60
+ static fromJSONUnsafe(doc, json) {
61
+ if (!Predicate.isNumber(json.pos)) throw new JSONError({ message: "Invalid input for GapCursor.fromJSON" });
62
+ return new GapCursor(doc.resolveUnsafe(json.pos));
63
+ }
64
+ getBookmark() {
65
+ return new GapBookmark(this.anchor);
66
+ }
67
+ /** Whether a gap cursor is valid at the given position. */
68
+ static valid($pos) {
69
+ const parent = $pos.parent;
70
+ if (parent.inlineContent || !closedBefore($pos) || !closedAfter($pos)) return false;
71
+ const override = parent.type.spec.allowGapCursor;
72
+ if (override != null) return !!override;
73
+ const deflt = parent.contentMatchAtUnsafe($pos.index()).defaultTypeOrNull;
74
+ return !!deflt && deflt.isTextblock;
75
+ }
76
+ /**
77
+ * Find a valid gap cursor position starting at `$pos` and searching in
78
+ * direction `dir` (negative is backwards). When `mustMove` is true,
79
+ * `$pos` itself is not considered.
80
+ */
81
+ static findGapCursorFrom($pos, dir, mustMove = false) {
82
+ return Option.fromNullishOr(GapCursor.findGapCursorFromOrNull($pos, dir, mustMove));
83
+ }
84
+ /** @internal */
85
+ static findGapCursorFromOrNull($pos, dir, mustMove = false) {
86
+ search: for (;;) {
87
+ if (!mustMove && GapCursor.valid($pos)) return $pos;
88
+ let pos = $pos.pos, next = null;
89
+ for (let d = $pos.depth;; d--) {
90
+ const parent = $pos.node(d);
91
+ if (dir > 0 ? $pos.indexAfter(d) < parent.childCount : $pos.index(d) > 0) {
92
+ next = child(parent, dir > 0 ? $pos.indexAfter(d) : $pos.index(d) - 1);
93
+ break;
94
+ } else if (d === 0) return null;
95
+ pos += dir;
96
+ const $cur = $pos.doc.resolveUnsafe(pos);
97
+ if (GapCursor.valid($cur)) return $cur;
98
+ }
99
+ for (;;) {
100
+ const inside = dir > 0 ? next.content.content[0] : next.content.content[next.childCount - 1];
101
+ if (!inside) {
102
+ if (next.isAtom && !next.isText && !NodeSelection.isSelectable(next)) {
103
+ $pos = $pos.doc.resolveUnsafe(pos + next.nodeSize * dir);
104
+ mustMove = false;
105
+ continue search;
106
+ }
107
+ break;
108
+ }
109
+ next = inside;
110
+ pos += dir;
111
+ const $cur = $pos.doc.resolveUnsafe(pos);
112
+ if (GapCursor.valid($cur)) return $cur;
113
+ }
114
+ return null;
115
+ }
116
+ }
117
+ };
118
+ Selection.jsonID("gapcursor", GapCursor);
119
+ var GapBookmark = class GapBookmark {
120
+ constructor(pos) {
121
+ this.pos = pos;
122
+ }
123
+ map(mapping) {
124
+ return new GapBookmark(mapping.map(this.pos));
125
+ }
126
+ resolve(doc) {
127
+ const $pos = doc.resolveUnsafe(this.pos);
128
+ return GapCursor.valid($pos) ? new GapCursor($pos) : Selection.near($pos);
129
+ }
130
+ };
131
+ function needsGap(type) {
132
+ return type.isAtom || !!type.spec.isolating || !!type.spec.createGapCursor;
133
+ }
134
+ function closedBefore($pos) {
135
+ for (let d = $pos.depth; d >= 0; d--) {
136
+ const index = $pos.index(d), parent = $pos.node(d);
137
+ if (index === 0) {
138
+ if (parent.type.spec.isolating) return true;
139
+ continue;
140
+ }
141
+ for (let before = child(parent, index - 1);; before = child(before, before.childCount - 1)) {
142
+ if (before.childCount === 0 && !before.inlineContent || needsGap(before.type)) return true;
143
+ if (before.inlineContent) return false;
144
+ }
145
+ }
146
+ return true;
147
+ }
148
+ function closedAfter($pos) {
149
+ for (let d = $pos.depth; d >= 0; d--) {
150
+ const index = $pos.indexAfter(d), parent = $pos.node(d);
151
+ if (index === parent.childCount) {
152
+ if (parent.type.spec.isolating) return true;
153
+ continue;
154
+ }
155
+ for (let after = child(parent, index);; after = child(after, 0)) {
156
+ if (after.childCount === 0 && !after.inlineContent || needsGap(after.type)) return true;
157
+ if (after.inlineContent) return false;
158
+ }
159
+ }
160
+ return true;
161
+ }
162
+ /**
163
+ * Create a gap cursor plugin. When enabled, this will capture clicks
164
+ * near and arrow-key-motion past places that don't have a normally
165
+ * selectable position nearby, and create a gap cursor selection for
166
+ * them. The cursor is drawn as an element with class
167
+ * `ProseMirror-gapcursor`. You can either include `gapCursorCSS` or add
168
+ * your own styles to make it visible.
169
+ *
170
+ * @since 0.1.0
171
+ * @category constructors
172
+ */
173
+ const gapCursor = () => new Plugin({ props: {
174
+ decorations: drawGapCursor,
175
+ createSelectionBetween(_view, $anchor, $head) {
176
+ return $anchor.pos === $head.pos && GapCursor.valid($head) ? new GapCursor($head) : null;
177
+ },
178
+ handleClick,
179
+ handleKeyDown,
180
+ handleDOMEvents: { beforeinput }
181
+ } });
182
+ function arrow(axis, dir) {
183
+ const dirStr = axis === "vert" ? dir > 0 ? "down" : "up" : dir > 0 ? "right" : "left";
184
+ return (state, dispatch, view) => Effect.suspend(() => {
185
+ const sel = state.selection;
186
+ let $start = dir > 0 ? sel.$to : sel.$from, mustMove = sel.empty;
187
+ if (sel instanceof TextSelection) {
188
+ if (!view || !view.endOfTextblock(dirStr) || $start.depth === 0) return Effect.succeed(false);
189
+ mustMove = false;
190
+ $start = state.doc.resolveUnsafe(dir > 0 ? $start.afterUnsafe() : $start.beforeUnsafe());
191
+ }
192
+ const $found = GapCursor.findGapCursorFromOrNull($start, dir, mustMove);
193
+ if (!$found) return Effect.succeed(false);
194
+ if (!dispatch) return Effect.succeed(true);
195
+ return Effect.as(dispatch(state.tr.setSelectionUnsafe(new GapCursor($found))), true);
196
+ });
197
+ }
198
+ const handleKeyDown = keydownHandler({
199
+ ArrowLeft: arrow("horiz", -1),
200
+ ArrowRight: arrow("horiz", 1),
201
+ ArrowUp: arrow("vert", -1),
202
+ ArrowDown: arrow("vert", 1)
203
+ });
204
+ function handleClick(view, pos, event) {
205
+ if (!view || !view.editable) return false;
206
+ const $pos = view.state.doc.resolveUnsafe(pos);
207
+ if (!GapCursor.valid($pos)) return false;
208
+ const clickPos = Option.getOrNull(view.posAtCoords({
209
+ left: event.clientX,
210
+ top: event.clientY
211
+ }));
212
+ if (clickPos && clickPos.inside > -1) {
213
+ const node = view.state.doc.nodeAtOrNull(clickPos.inside);
214
+ if (node && NodeSelection.isSelectable(node)) return false;
215
+ }
216
+ return Effect.as(view.dispatch(view.state.tr.setSelectionUnsafe(new GapCursor($pos))), true);
217
+ }
218
+ function beforeinput(view, event) {
219
+ if (event.inputType !== "insertCompositionText" || !(view.state.selection instanceof GapCursor)) return false;
220
+ const { $from } = view.state.selection;
221
+ const insert = $from.parent.contentMatchAtUnsafe($from.index()).findWrappingOrNull(view.state.schema.nodes.text);
222
+ if (!insert) return false;
223
+ let frag = Fragment.empty;
224
+ for (let i = insert.length - 1; i >= 0; i--) frag = Fragment.from(insert[i].createAndFillOrNull(null, frag));
225
+ const tr = view.state.tr.replaceUnsafe($from.pos, $from.pos, new Slice(frag, 0, 0));
226
+ tr.setSelectionUnsafe(TextSelection.near(tr.doc.resolveUnsafe($from.pos + 1)));
227
+ return Effect.as(view.dispatch(tr), false);
228
+ }
229
+ function drawGapCursor(state) {
230
+ if (!(state.selection instanceof GapCursor)) return null;
231
+ const widget = Decoration.widget(state.selection.head, (view) => {
232
+ const node = view.dom.ownerDocument.createElement("div");
233
+ node.className = "ProseMirror-gapcursor";
234
+ return node;
235
+ }, { key: "gapcursor" });
236
+ return DecorationSet.create(state.doc, [widget]);
237
+ }
238
+ /**
239
+ * The gap cursor's stylesheet (from `prosemirror-gapcursor`'s
240
+ * `style/gapcursor.css`). Add it to the page (for example with
241
+ * `injectCSS` from `@thomasfosterau/effect-richtext/view-css`) or provide your own
242
+ * styles for `.ProseMirror-gapcursor`.
243
+ *
244
+ * @since 0.1.0
245
+ * @category styles
246
+ */
247
+ const gapCursorCSS = `.ProseMirror-gapcursor {
248
+ display: none;
249
+ pointer-events: none;
250
+ position: absolute;
251
+ }
252
+
253
+ .ProseMirror-gapcursor:after {
254
+ content: "";
255
+ display: block;
256
+ position: absolute;
257
+ top: -2px;
258
+ width: 20px;
259
+ border-top: 1px solid black;
260
+ animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
261
+ }
262
+
263
+ @keyframes ProseMirror-cursor-blink {
264
+ to {
265
+ visibility: hidden;
266
+ }
267
+ }
268
+
269
+ .ProseMirror-focused .ProseMirror-gapcursor {
270
+ display: block;
271
+ }
272
+ `;
273
+ export { GapCursor, gapCursor, gapCursorCSS };