@prosekit/core 0.8.3 → 0.8.5

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 (170) hide show
  1. package/dist/editor-KZlceNQ1.d.ts +722 -0
  2. package/dist/editor-KZlceNQ1.d.ts.map +1 -0
  3. package/dist/{editor-DlGlYOp-.js → editor-TvRTsFdO.js} +102 -196
  4. package/dist/editor-TvRTsFdO.js.map +1 -0
  5. package/dist/prosekit-core-test.d.ts +20 -19
  6. package/dist/prosekit-core-test.d.ts.map +1 -0
  7. package/dist/prosekit-core-test.js +5 -8
  8. package/dist/prosekit-core-test.js.map +1 -0
  9. package/dist/prosekit-core.d.ts +797 -792
  10. package/dist/prosekit-core.d.ts.map +1 -0
  11. package/dist/prosekit-core.js +42 -79
  12. package/dist/prosekit-core.js.map +1 -0
  13. package/package.json +14 -12
  14. package/src/commands/add-mark.ts +53 -0
  15. package/src/commands/expand-mark.ts +96 -0
  16. package/src/commands/insert-default-block.spec.ts +102 -0
  17. package/src/commands/insert-default-block.ts +49 -0
  18. package/src/commands/insert-node.ts +71 -0
  19. package/src/commands/insert-text.ts +24 -0
  20. package/src/commands/remove-mark.ts +54 -0
  21. package/src/commands/remove-node.ts +43 -0
  22. package/src/commands/select-all.ts +16 -0
  23. package/src/commands/set-block-type.ts +64 -0
  24. package/src/commands/set-node-attrs.ts +68 -0
  25. package/src/commands/toggle-mark.ts +65 -0
  26. package/src/commands/toggle-node.ts +47 -0
  27. package/src/commands/toggle-wrap.spec.ts +35 -0
  28. package/src/commands/toggle-wrap.ts +42 -0
  29. package/src/commands/unset-block-type.spec.ts +49 -0
  30. package/src/commands/unset-block-type.ts +84 -0
  31. package/src/commands/unset-mark.spec.ts +35 -0
  32. package/src/commands/unset-mark.ts +38 -0
  33. package/src/commands/wrap.ts +50 -0
  34. package/src/editor/action.spec.ts +143 -0
  35. package/src/editor/action.ts +248 -0
  36. package/src/editor/editor.spec.ts +186 -0
  37. package/src/editor/editor.ts +563 -0
  38. package/src/editor/union.spec.ts +108 -0
  39. package/src/editor/union.ts +47 -0
  40. package/src/editor/with-priority.ts +25 -0
  41. package/src/error.ts +28 -0
  42. package/src/extensions/clipboard-serializer.ts +107 -0
  43. package/src/extensions/command.ts +121 -0
  44. package/src/extensions/default-state.spec.ts +60 -0
  45. package/src/extensions/default-state.ts +76 -0
  46. package/src/extensions/doc.ts +31 -0
  47. package/src/extensions/events/doc-change.ts +34 -0
  48. package/src/extensions/events/dom-event.spec.ts +70 -0
  49. package/src/extensions/events/dom-event.ts +117 -0
  50. package/src/extensions/events/editor-event.ts +293 -0
  51. package/src/extensions/events/focus.spec.ts +50 -0
  52. package/src/extensions/events/focus.ts +28 -0
  53. package/src/extensions/events/plugin-view.ts +132 -0
  54. package/src/extensions/history.ts +81 -0
  55. package/src/extensions/keymap-base.ts +60 -0
  56. package/src/extensions/keymap.spec.ts +125 -0
  57. package/src/extensions/keymap.ts +96 -0
  58. package/src/extensions/mark-spec.spec.ts +177 -0
  59. package/src/extensions/mark-spec.ts +181 -0
  60. package/src/extensions/mark-view-effect.ts +85 -0
  61. package/src/extensions/mark-view.ts +43 -0
  62. package/src/extensions/node-spec.spec.ts +224 -0
  63. package/src/extensions/node-spec.ts +199 -0
  64. package/src/extensions/node-view-effect.ts +85 -0
  65. package/src/extensions/node-view.ts +43 -0
  66. package/src/extensions/paragraph.ts +61 -0
  67. package/src/extensions/plugin.spec.ts +153 -0
  68. package/src/extensions/plugin.ts +81 -0
  69. package/src/extensions/text.ts +34 -0
  70. package/src/facets/base-extension.ts +54 -0
  71. package/src/facets/command.ts +21 -0
  72. package/src/facets/facet-extension.spec.ts +173 -0
  73. package/src/facets/facet-extension.ts +53 -0
  74. package/src/facets/facet-node.spec.ts +265 -0
  75. package/src/facets/facet-node.ts +185 -0
  76. package/src/facets/facet-types.ts +9 -0
  77. package/src/facets/facet.spec.ts +76 -0
  78. package/src/facets/facet.ts +84 -0
  79. package/src/facets/root.ts +44 -0
  80. package/src/facets/schema-spec.ts +30 -0
  81. package/src/facets/schema.ts +26 -0
  82. package/src/facets/state.spec.ts +53 -0
  83. package/src/facets/state.ts +85 -0
  84. package/src/facets/union-extension.ts +41 -0
  85. package/src/index.ts +302 -0
  86. package/src/test/index.ts +4 -0
  87. package/src/test/test-builder.ts +68 -0
  88. package/src/test/test-editor.spec.ts +104 -0
  89. package/src/test/test-editor.ts +113 -0
  90. package/src/testing/index.ts +283 -0
  91. package/src/testing/keyboard.ts +5 -0
  92. package/src/types/any-function.ts +4 -0
  93. package/src/types/assert-type-equal.ts +8 -0
  94. package/src/types/attrs.ts +32 -0
  95. package/src/types/base-node-view-options.ts +33 -0
  96. package/src/types/dom-node.ts +1 -0
  97. package/src/types/extension-command.ts +52 -0
  98. package/src/types/extension-mark.ts +15 -0
  99. package/src/types/extension-node.ts +15 -0
  100. package/src/types/extension.spec.ts +56 -0
  101. package/src/types/extension.ts +168 -0
  102. package/src/types/model.ts +54 -0
  103. package/src/types/object-entries.ts +13 -0
  104. package/src/types/pick-string-literal.spec.ts +10 -0
  105. package/src/types/pick-string-literal.ts +6 -0
  106. package/src/types/pick-sub-type.spec.ts +20 -0
  107. package/src/types/pick-sub-type.ts +6 -0
  108. package/src/types/priority.ts +12 -0
  109. package/src/types/setter.ts +4 -0
  110. package/src/types/simplify-deeper.spec.ts +40 -0
  111. package/src/types/simplify-deeper.ts +6 -0
  112. package/src/types/simplify-union.spec.ts +21 -0
  113. package/src/types/simplify-union.ts +11 -0
  114. package/src/utils/array-grouping.spec.ts +29 -0
  115. package/src/utils/array-grouping.ts +25 -0
  116. package/src/utils/array.ts +21 -0
  117. package/src/utils/assert.ts +13 -0
  118. package/src/utils/attrs-match.ts +20 -0
  119. package/src/utils/can-use-regex-lookbehind.ts +12 -0
  120. package/src/utils/clsx.spec.ts +14 -0
  121. package/src/utils/clsx.ts +14 -0
  122. package/src/utils/collect-children.ts +21 -0
  123. package/src/utils/collect-nodes.ts +37 -0
  124. package/src/utils/combine-event-handlers.spec.ts +27 -0
  125. package/src/utils/combine-event-handlers.ts +27 -0
  126. package/src/utils/contains-inline-node.ts +17 -0
  127. package/src/utils/deep-equals.spec.ts +26 -0
  128. package/src/utils/deep-equals.ts +29 -0
  129. package/src/utils/default-block-at.ts +15 -0
  130. package/src/utils/editor-content.spec.ts +47 -0
  131. package/src/utils/editor-content.ts +77 -0
  132. package/src/utils/env.ts +6 -0
  133. package/src/utils/find-parent-node-of-type.ts +29 -0
  134. package/src/utils/find-parent-node.spec.ts +68 -0
  135. package/src/utils/find-parent-node.ts +55 -0
  136. package/src/utils/get-custom-selection.ts +19 -0
  137. package/src/utils/get-dom-api.ts +56 -0
  138. package/src/utils/get-id.spec.ts +14 -0
  139. package/src/utils/get-id.ts +13 -0
  140. package/src/utils/get-mark-type.ts +20 -0
  141. package/src/utils/get-node-type.ts +20 -0
  142. package/src/utils/get-node-types.ts +19 -0
  143. package/src/utils/includes-mark.ts +18 -0
  144. package/src/utils/is-at-block-start.ts +26 -0
  145. package/src/utils/is-in-code-block.ts +18 -0
  146. package/src/utils/is-mark-absent.spec.ts +53 -0
  147. package/src/utils/is-mark-absent.ts +42 -0
  148. package/src/utils/is-mark-active.ts +27 -0
  149. package/src/utils/is-node-active.ts +25 -0
  150. package/src/utils/is-subset.spec.ts +12 -0
  151. package/src/utils/is-subset.ts +11 -0
  152. package/src/utils/maybe-run.spec.ts +39 -0
  153. package/src/utils/maybe-run.ts +11 -0
  154. package/src/utils/merge-objects.spec.ts +30 -0
  155. package/src/utils/merge-objects.ts +11 -0
  156. package/src/utils/merge-specs.ts +35 -0
  157. package/src/utils/object-equal.spec.ts +26 -0
  158. package/src/utils/object-equal.ts +28 -0
  159. package/src/utils/output-spec.test.ts +95 -0
  160. package/src/utils/output-spec.ts +130 -0
  161. package/src/utils/parse.spec.ts +46 -0
  162. package/src/utils/parse.ts +321 -0
  163. package/src/utils/remove-undefined-values.spec.ts +15 -0
  164. package/src/utils/remove-undefined-values.ts +9 -0
  165. package/src/utils/set-selection-around.ts +11 -0
  166. package/src/utils/type-assertion.ts +91 -0
  167. package/src/utils/unicode.spec.ts +10 -0
  168. package/src/utils/unicode.ts +4 -0
  169. package/src/utils/with-skip-code-block.ts +15 -0
  170. package/dist/editor-OUH5V8BA.d.ts +0 -754
@@ -0,0 +1,265 @@
1
+ import {
2
+ expect,
3
+ test,
4
+ } from 'vitest'
5
+
6
+ import { Priority } from '../types/priority'
7
+
8
+ import { Facet } from './facet'
9
+ import {
10
+ FacetNode,
11
+ subtractFacetNode,
12
+ unionFacetNode,
13
+ } from './facet-node'
14
+
15
+ const sum = (input: number[]) => input.reduce((acc, cur) => acc + cur, 0)
16
+
17
+ const CounterFacet = new Facet<number, number>(null, false, sum)
18
+ const SingletonCounterFacet = new Facet<number, number>(null, true, sum)
19
+
20
+ const value1 = [0, 1, 2]
21
+ const value2 = [3, 4, 5]
22
+ const value3 = [6, 7, 8]
23
+
24
+ test('Root Facet Node', () => {
25
+ const rootNode = new FacetNode(CounterFacet, [
26
+ [...value1],
27
+ null,
28
+ null,
29
+ [...value1],
30
+ null,
31
+ ])
32
+ const childNode1 = new FacetNode(CounterFacet, [
33
+ null,
34
+ [...value2],
35
+ null,
36
+ [...value2],
37
+ null,
38
+ ])
39
+ const childNode2 = new FacetNode(CounterFacet, [
40
+ null,
41
+ null,
42
+ [...value3],
43
+ [...value3],
44
+ null,
45
+ ])
46
+
47
+ rootNode.children.set(0, childNode1)
48
+ rootNode.children.set(1, childNode2)
49
+
50
+ expect(rootNode.facet).toBe(CounterFacet)
51
+ expect(rootNode.children.size).toBe(2)
52
+ expect(rootNode.getOutput()).toEqual([
53
+ sum(value1),
54
+ sum(value2),
55
+ sum(value3),
56
+ sum([...value1, ...value2, ...value3]),
57
+ null,
58
+ ])
59
+ expect(() => rootNode.getRootOutput()).toThrow()
60
+ expect(rootNode.isRoot()).toBeTruthy()
61
+ })
62
+
63
+ test('Singleton Root Facet Node', () => {
64
+ const singletonRootNode = new FacetNode(SingletonCounterFacet, [
65
+ [...value1],
66
+ null,
67
+ null,
68
+ null,
69
+ null,
70
+ ])
71
+ const childNode1 = new FacetNode(CounterFacet, [
72
+ null,
73
+ [...value2],
74
+ null,
75
+ [...value2],
76
+ null,
77
+ ])
78
+ const childNode2 = new FacetNode(CounterFacet, [
79
+ null,
80
+ null,
81
+ [...value3],
82
+ [...value3],
83
+ null,
84
+ ])
85
+
86
+ singletonRootNode.children.set(0, childNode1)
87
+ singletonRootNode.children.set(1, childNode2)
88
+
89
+ expect(singletonRootNode.facet).toBe(SingletonCounterFacet)
90
+ expect(singletonRootNode.children.size).toBe(2)
91
+ expect(childNode1.getOutput()).toEqual([
92
+ null,
93
+ sum(value2),
94
+ null,
95
+ sum(value2),
96
+ null,
97
+ ])
98
+ expect(childNode2.getOutput()).toEqual([
99
+ null,
100
+ null,
101
+ sum(value3),
102
+ sum(value3),
103
+ null,
104
+ ])
105
+ expect(singletonRootNode.getOutput()).toEqual([
106
+ null,
107
+ null,
108
+ sum([...value1, sum(value2), sum(value3), sum(value2), sum(value3)]),
109
+ null,
110
+ null,
111
+ ])
112
+ expect(singletonRootNode.getRootOutput()).toEqual(
113
+ singletonRootNode.getOutput()[Priority.default],
114
+ )
115
+ })
116
+
117
+ test('Union Facet Node', () => {
118
+ const rootNode1 = new FacetNode(CounterFacet, [
119
+ [...value1],
120
+ null,
121
+ null,
122
+ [...value1, ...value3],
123
+ null,
124
+ ])
125
+ const rootNode2 = new FacetNode(CounterFacet, [
126
+ null,
127
+ [...value1],
128
+ null,
129
+ [...value1, ...value2],
130
+ null,
131
+ ])
132
+ const childNode1 = new FacetNode(CounterFacet, [
133
+ null,
134
+ null,
135
+ [...value2],
136
+ [...value2],
137
+ null,
138
+ ])
139
+ const childNode2 = new FacetNode(CounterFacet, [
140
+ null,
141
+ null,
142
+ [...value3],
143
+ [...value3],
144
+ null,
145
+ ])
146
+
147
+ rootNode1.children.set(0, childNode1)
148
+ rootNode2.children.set(0, childNode2)
149
+
150
+ const unioned = unionFacetNode(rootNode1, rootNode2)
151
+ expect(unioned.facet).toBe(CounterFacet)
152
+ expect(unioned.children.size).toBe(1)
153
+ expect(unioned.children.get(0)).toEqual(
154
+ unionFacetNode(childNode1, childNode2),
155
+ )
156
+ expect(unioned.inputs).toEqual([
157
+ [...value1],
158
+ [...value1],
159
+ null,
160
+ [...value1, ...value3, ...value2],
161
+ null,
162
+ ])
163
+ expect(unioned.children.get(0)?.inputs).toEqual([
164
+ null,
165
+ null,
166
+ [...value2, ...value3],
167
+ [...value2, ...value3],
168
+ null,
169
+ ])
170
+ })
171
+
172
+ test('Subtract Facet Node', () => {
173
+ const rootNode1 = new FacetNode(CounterFacet, [
174
+ [...value1],
175
+ null,
176
+ null,
177
+ [...value1, ...value3],
178
+ null,
179
+ ])
180
+ const rootNode2 = new FacetNode(CounterFacet, [
181
+ null,
182
+ [...value1],
183
+ null,
184
+ [...value1, ...value2],
185
+ null,
186
+ ])
187
+ const childNode1 = new FacetNode(CounterFacet, [
188
+ null,
189
+ null,
190
+ [...value2],
191
+ [...value2],
192
+ null,
193
+ ])
194
+ const childNode2 = new FacetNode(CounterFacet, [
195
+ null,
196
+ null,
197
+ [...value2],
198
+ [...value3],
199
+ null,
200
+ ])
201
+
202
+ rootNode1.children.set(0, childNode1)
203
+ rootNode2.children.set(0, childNode2)
204
+
205
+ const subtracted = subtractFacetNode(rootNode1, rootNode2)
206
+ expect(subtracted.facet).toBe(CounterFacet)
207
+ expect(subtracted.children.size).toBe(1)
208
+ expect(subtracted.children.get(0)).toEqual(
209
+ subtractFacetNode(childNode1, childNode2),
210
+ )
211
+
212
+ expect(subtracted.inputs).toEqual([
213
+ [...value1],
214
+ null,
215
+ null,
216
+ [...value3],
217
+ null,
218
+ ])
219
+
220
+ expect(subtracted.children.get(0)?.inputs).toEqual([
221
+ null,
222
+ null,
223
+ [],
224
+ [...value2],
225
+ null,
226
+ ])
227
+ })
228
+
229
+ test('Union Facet Node with Different Facet', () => {
230
+ const rootNode1 = new FacetNode(CounterFacet, [
231
+ [...value1],
232
+ null,
233
+ null,
234
+ [...value1, ...value3],
235
+ null,
236
+ ])
237
+ const rootNode2 = new FacetNode(SingletonCounterFacet, [
238
+ null,
239
+ [...value1],
240
+ null,
241
+ [...value1, ...value2],
242
+ null,
243
+ ])
244
+
245
+ expect(() => unionFacetNode(rootNode1, rootNode2)).toThrow()
246
+ })
247
+
248
+ test('Subtract Facet Node with Different Facet', () => {
249
+ const rootNode1 = new FacetNode(CounterFacet, [
250
+ [...value1],
251
+ null,
252
+ null,
253
+ [...value1, ...value3],
254
+ null,
255
+ ])
256
+ const rootNode2 = new FacetNode(SingletonCounterFacet, [
257
+ null,
258
+ [...value1],
259
+ null,
260
+ [...value1, ...value2],
261
+ null,
262
+ ])
263
+
264
+ expect(() => subtractFacetNode(rootNode1, rootNode2)).toThrow()
265
+ })
@@ -0,0 +1,185 @@
1
+ import { Priority } from '../types/priority'
2
+ import {
3
+ arraySubtract,
4
+ uniqPush,
5
+ } from '../utils/array'
6
+ import { assert } from '../utils/assert'
7
+ import { isNotNullish } from '../utils/type-assertion'
8
+
9
+ import type { Facet } from './facet'
10
+ import type {
11
+ FacetReducer,
12
+ Tuple5,
13
+ } from './facet-types'
14
+ import type { RootOutput } from './root'
15
+
16
+ function zip5<T>(
17
+ a: Tuple5<T>,
18
+ b: Tuple5<T>,
19
+ mapper: (a: T, b: T) => T,
20
+ ): Tuple5<T> {
21
+ return [
22
+ mapper(a[0], b[0]),
23
+ mapper(a[1], b[1]),
24
+ mapper(a[2], b[2]),
25
+ mapper(a[3], b[3]),
26
+ mapper(a[4], b[4]),
27
+ ]
28
+ }
29
+
30
+ function unionInput<T>(a: T[] | null, b: T[] | null): T[] | null {
31
+ if (!a && !b) return null
32
+ return uniqPush(a ?? [], b ?? [])
33
+ }
34
+
35
+ function subtractInput<T>(a: T[] | null, b: T[] | null): T[] | null {
36
+ if (!a) return null
37
+ if (!b) return [...a]
38
+ return arraySubtract(a, b)
39
+ }
40
+
41
+ function unionChildren(
42
+ a: Map<number, FacetNode>,
43
+ b: Map<number, FacetNode>,
44
+ ): Map<number, FacetNode> {
45
+ const merged = new Map(a)
46
+ for (const [key, valueB] of b.entries()) {
47
+ const valueA = a.get(key)
48
+ merged.set(key, valueA ? unionFacetNode(valueA, valueB) : valueB)
49
+ }
50
+ return merged
51
+ }
52
+
53
+ function subtractChildren(
54
+ a: Map<number, FacetNode>,
55
+ b: Map<number, FacetNode>,
56
+ ): Map<number, FacetNode> {
57
+ const merged = new Map(a)
58
+ for (const [key, valueB] of b.entries()) {
59
+ const valueA = a.get(key)
60
+ if (valueA) {
61
+ merged.set(key, subtractFacetNode(valueA, valueB))
62
+ }
63
+ }
64
+ return merged
65
+ }
66
+
67
+ /**
68
+ * Takes two facet nodes and returns a new facet node containing inputs and
69
+ * children from both nodes.
70
+ *
71
+ * The reducers of the first facet node will be reused.
72
+ *
73
+ * @internal
74
+ */
75
+ export function unionFacetNode<I, O>(
76
+ a: FacetNode<I, O>,
77
+ b: FacetNode<I, O>,
78
+ ): FacetNode<I, O> {
79
+ assert(a.facet === b.facet)
80
+ return new FacetNode(
81
+ a.facet,
82
+ zip5(a.inputs, b.inputs, unionInput),
83
+ unionChildren(a.children, b.children),
84
+ a.reducers,
85
+ )
86
+ }
87
+
88
+ /**
89
+ * Takes two facet nodes and returns a new facet node containing inputs and
90
+ * children from the first node but not the second.
91
+ *
92
+ * The reducers of the first facet node will be reused.
93
+ *
94
+ * @internal
95
+ */
96
+ export function subtractFacetNode<I, O>(
97
+ a: FacetNode<I, O>,
98
+ b: FacetNode<I, O>,
99
+ ): FacetNode<I, O> {
100
+ assert(a.facet === b.facet)
101
+ return new FacetNode(
102
+ a.facet,
103
+ zip5(a.inputs, b.inputs, subtractInput),
104
+ subtractChildren(a.children, b.children),
105
+ a.reducers,
106
+ )
107
+ }
108
+
109
+ export class FacetNode<I = any, O = any> {
110
+ output: Tuple5<O | null> | null = null
111
+
112
+ constructor(
113
+ readonly facet: Facet<I, O>,
114
+ readonly inputs: Tuple5<I[] | null> = [null, null, null, null, null],
115
+ readonly children: Map<number, FacetNode> = new Map(),
116
+ readonly reducers: Tuple5<FacetReducer<I, O> | null> = [
117
+ null,
118
+ null,
119
+ null,
120
+ null,
121
+ null,
122
+ ],
123
+ ) {}
124
+
125
+ private calcOutput(): Tuple5<O | null> {
126
+ const inputs: Tuple5<I[] | null> = [null, null, null, null, null]
127
+ const output: Tuple5<O | null> = [null, null, null, null, null]
128
+
129
+ for (let pri = 0; pri < 5; pri++) {
130
+ const input = this.inputs[pri]
131
+ if (input) {
132
+ inputs[pri] = [...input]
133
+ }
134
+ }
135
+
136
+ for (const child of this.children.values()) {
137
+ const childOutput = child.getOutput()
138
+ for (let pri = 0; pri < 5; pri++) {
139
+ if (childOutput[pri]) {
140
+ const input = (inputs[pri] ||= [])
141
+ input.push(childOutput[pri] as I)
142
+ }
143
+ }
144
+ }
145
+
146
+ if (this.facet.singleton) {
147
+ const reducer = (this.reducers[Priority.default] ||= this.facet.reducer)
148
+ const input: I[] = inputs.filter(isNotNullish).flat()
149
+ output[Priority.default] = reducer(input)
150
+ } else {
151
+ for (let pri = 0; pri < 5; pri++) {
152
+ const input = inputs[pri]
153
+ if (input) {
154
+ const reducer = (this.reducers[pri] ||= this.facet.reducer)
155
+ output[pri] = reducer(input)
156
+ }
157
+ }
158
+ }
159
+
160
+ return output
161
+ }
162
+
163
+ getOutput(): Tuple5<O | null> {
164
+ if (!this.output) {
165
+ this.output = this.calcOutput()
166
+ }
167
+ return this.output
168
+ }
169
+
170
+ getSingletonOutput(): O | null {
171
+ assert(this.facet.singleton)
172
+ return this.getOutput()[Priority.default]
173
+ }
174
+
175
+ getRootOutput(): RootOutput {
176
+ assert(this.isRoot())
177
+ const output = this.getSingletonOutput()
178
+ assert(output)
179
+ return output
180
+ }
181
+
182
+ isRoot(): boolean {
183
+ return !this.facet.parent
184
+ }
185
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @internal
3
+ */
4
+ export type Tuple5<T> = [T, T, T, T, T]
5
+
6
+ /**
7
+ * @internal
8
+ */
9
+ export type FacetReducer<Input, Output> = (input: Input[]) => Output
@@ -0,0 +1,76 @@
1
+ import {
2
+ expect,
3
+ test,
4
+ } from 'vitest'
5
+
6
+ import { Facet } from './facet'
7
+
8
+ type RootInput = {
9
+ count: number
10
+ }
11
+ type RootOutput = {
12
+ count: number
13
+ id: symbol
14
+ }
15
+ const id = Symbol('root-facet')
16
+ const rootFacet = new Facet<RootInput, RootOutput>(null, true, (input) => {
17
+ const count = input
18
+ .map(({ count }) => count)
19
+ .reduce((acc, cur) => {
20
+ return acc + cur
21
+ }, 0)
22
+
23
+ return {
24
+ count,
25
+ id,
26
+ }
27
+ })
28
+
29
+ const doubleFacet = new Facet<RootOutput, RootOutput>(
30
+ rootFacet,
31
+ false,
32
+ (input) => {
33
+ const id = input[0].id
34
+ const count = input
35
+ .map(({ count }) => count)
36
+ .reduce((acc, cur) => {
37
+ return acc + cur
38
+ }, 0)
39
+
40
+ return {
41
+ count: count * 2,
42
+ id,
43
+ }
44
+ },
45
+ )
46
+
47
+ test('Root Facet', () => {
48
+ expect(rootFacet.parent).toBe(null)
49
+ expect(rootFacet.singleton).toBe(true)
50
+ expect(rootFacet.path).toEqual([])
51
+ expect(
52
+ rootFacet.reducer([
53
+ { count: 1 },
54
+ { count: 2 },
55
+ { count: 3 },
56
+ { count: 4 },
57
+ { count: 0 },
58
+ ]),
59
+ ).toEqual({ count: 10, id })
60
+ })
61
+
62
+ test('Child Facet', () => {
63
+ expect(doubleFacet.parent).toBe(rootFacet)
64
+ expect(doubleFacet.singleton).toBe(false)
65
+ expect(doubleFacet.path).toEqual([1])
66
+ expect(
67
+ doubleFacet.reducer([
68
+ { count: 1, id },
69
+ { count: 2, id },
70
+ { count: 3, id },
71
+ ]),
72
+ ).toEqual({
73
+ count: 12,
74
+ id,
75
+ })
76
+ })
@@ -0,0 +1,84 @@
1
+ import { assert } from '../utils/assert'
2
+
3
+ import type { FacetReducer } from './facet-types'
4
+
5
+ let facetCount = 0
6
+
7
+ /**
8
+ * @internal
9
+ */
10
+ export class Facet<Input, Output> {
11
+ /**
12
+ * @internal
13
+ */
14
+ readonly index: number = facetCount++
15
+ /**
16
+ * @internal
17
+ */
18
+ readonly parent: Facet<Output, any> | null
19
+ /**
20
+ * @internal
21
+ */
22
+ readonly singleton: boolean
23
+ /**
24
+ * A index path to retrieve the current facet in a tree from the root.
25
+ *
26
+ * @internal
27
+ */
28
+ readonly path: number[]
29
+
30
+ /**
31
+ * @internal
32
+ */
33
+ constructor(
34
+ parent: Facet<Output, any> | null,
35
+ singleton: boolean,
36
+ private _reducer?: FacetReducer<Input, Output> | undefined,
37
+ private _reduce?: () => FacetReducer<Input, Output>,
38
+ ) {
39
+ // Only one of _reducer or _reduce can be defined
40
+ assert((_reduce || _reducer) && !(_reduce && _reducer))
41
+
42
+ this.parent = parent
43
+ this.singleton = singleton
44
+ this.path = parent ? [...parent.path, this.index] : []
45
+ }
46
+
47
+ get reducer(): FacetReducer<Input, Output> {
48
+ return (this._reducer ?? this._reduce?.())!
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @internal
54
+ */
55
+ export function defineFacet<Input, Output>(options: {
56
+ /**
57
+ * The parent facet in the tree.
58
+ */
59
+ parent: Facet<Output, any>
60
+
61
+ /**
62
+ * Set this to true if you only want to keep one facet payload. For example,
63
+ * this facet corresponds to a ProseMirror plugin with a key.
64
+ */
65
+ singleton?: boolean
66
+
67
+ /**
68
+ * A reducer is a function that accepts an array of input and produce a single
69
+ * output.
70
+ */
71
+ reducer?: FacetReducer<Input, Output>
72
+ /**
73
+ * A callback function that returns a reducer. This is useful if you want to
74
+ * store something in the closure.
75
+ */
76
+ reduce?: () => FacetReducer<Input, Output>
77
+ }): Facet<Input, Output> {
78
+ return new Facet(
79
+ options.parent,
80
+ options.singleton ?? false,
81
+ options.reducer,
82
+ options.reduce,
83
+ )
84
+ }
@@ -0,0 +1,44 @@
1
+ import type { Schema } from '@prosekit/pm/model'
2
+ import type { EditorStateConfig } from '@prosekit/pm/state'
3
+ import type { DirectEditorProps } from '@prosekit/pm/view'
4
+
5
+ import type { CommandCreators } from '../types/extension-command'
6
+
7
+ import { Facet } from './facet'
8
+
9
+ export type RootPayload = {
10
+ schema?: Schema | null
11
+ commands?: CommandCreators
12
+ state?: (ctx: { schema: Schema }) => EditorStateConfig
13
+ view?: Omit<DirectEditorProps, 'state'>
14
+ }
15
+
16
+ export type RootOutput = {
17
+ schema?: Schema | null
18
+ commands?: CommandCreators
19
+ state?: EditorStateConfig
20
+ view?: Omit<DirectEditorProps, 'state'>
21
+ }
22
+
23
+ function rootReducer(inputs: RootPayload[]): RootOutput {
24
+ let schema: Schema | undefined
25
+ let commands: CommandCreators | undefined
26
+ let stateFunc: ((ctx: { schema: Schema }) => EditorStateConfig) | undefined
27
+ let view: Omit<DirectEditorProps, 'state'> | undefined
28
+
29
+ for (const input of inputs) {
30
+ schema = input.schema || schema
31
+ commands = input.commands || commands
32
+ stateFunc = input.state || stateFunc
33
+ view = input.view || view
34
+ }
35
+
36
+ const state = schema && (stateFunc?.({ schema }) ?? { schema })
37
+
38
+ return { schema, state, commands, view }
39
+ }
40
+
41
+ export const rootFacet: Facet<RootPayload, RootOutput> = new Facet<
42
+ RootPayload,
43
+ RootOutput
44
+ >(null, true, rootReducer)
@@ -0,0 +1,30 @@
1
+ import type {
2
+ MarkSpec,
3
+ NodeSpec,
4
+ SchemaSpec,
5
+ } from '@prosekit/pm/model'
6
+ import OrderedMap from 'orderedmap'
7
+
8
+ import {
9
+ defineFacet,
10
+ type Facet,
11
+ } from './facet'
12
+ import { schemaFacet } from './schema'
13
+
14
+ export const schemaSpecFacet: Facet<SchemaSpec, SchemaSpec> = defineFacet({
15
+ reducer: (specs): SchemaSpec => {
16
+ let nodes = OrderedMap.from<NodeSpec>({})
17
+ let marks = OrderedMap.from<MarkSpec>({})
18
+ let topNode: string | undefined = undefined
19
+
20
+ for (const spec of specs) {
21
+ nodes = nodes.append(spec.nodes)
22
+ marks = marks.append(spec.marks ?? {})
23
+ topNode = topNode ?? spec.topNode
24
+ }
25
+
26
+ return { nodes, marks, topNode }
27
+ },
28
+ parent: schemaFacet,
29
+ singleton: true,
30
+ })
@@ -0,0 +1,26 @@
1
+ import {
2
+ Schema,
3
+ type SchemaSpec,
4
+ } from '@prosekit/pm/model'
5
+
6
+ import { assert } from '../utils/assert'
7
+
8
+ import {
9
+ defineFacet,
10
+ type Facet,
11
+ } from './facet'
12
+ import {
13
+ rootFacet,
14
+ type RootPayload,
15
+ } from './root'
16
+
17
+ export const schemaFacet: Facet<SchemaSpec, RootPayload> = defineFacet({
18
+ reducer: (specs) => {
19
+ assert(specs.length <= 1)
20
+ const spec = specs[0]
21
+ const schema = spec ? new Schema(spec) : null
22
+ return { schema }
23
+ },
24
+ parent: rootFacet,
25
+ singleton: true,
26
+ })