@prosekit/core 0.8.2 → 0.8.4

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 (168) hide show
  1. package/dist/editor-CfkZ4TNU.d.ts +748 -0
  2. package/dist/editor-CfkZ4TNU.d.ts.map +1 -0
  3. package/dist/{editor-DbMrpnmL.js → editor-CizSwUN8.js} +102 -192
  4. package/dist/editor-CizSwUN8.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 +4 -5
  8. package/dist/prosekit-core-test.js.map +1 -0
  9. package/dist/prosekit-core.d.ts +782 -757
  10. package/dist/prosekit-core.d.ts.map +1 -0
  11. package/dist/prosekit-core.js +30 -45
  12. package/dist/prosekit-core.js.map +1 -0
  13. package/package.json +14 -11
  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 +89 -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.ts +91 -0
  68. package/src/extensions/text.ts +34 -0
  69. package/src/facets/base-extension.ts +54 -0
  70. package/src/facets/command.ts +21 -0
  71. package/src/facets/facet-extension.spec.ts +173 -0
  72. package/src/facets/facet-extension.ts +53 -0
  73. package/src/facets/facet-node.spec.ts +265 -0
  74. package/src/facets/facet-node.ts +185 -0
  75. package/src/facets/facet-types.ts +9 -0
  76. package/src/facets/facet.spec.ts +76 -0
  77. package/src/facets/facet.ts +84 -0
  78. package/src/facets/root.ts +44 -0
  79. package/src/facets/schema-spec.ts +30 -0
  80. package/src/facets/schema.ts +26 -0
  81. package/src/facets/state.ts +57 -0
  82. package/src/facets/union-extension.ts +41 -0
  83. package/src/index.ts +302 -0
  84. package/src/test/index.ts +4 -0
  85. package/src/test/test-builder.ts +68 -0
  86. package/src/test/test-editor.spec.ts +104 -0
  87. package/src/test/test-editor.ts +113 -0
  88. package/src/testing/index.ts +283 -0
  89. package/src/testing/keyboard.ts +5 -0
  90. package/src/types/any-function.ts +4 -0
  91. package/src/types/assert-type-equal.ts +8 -0
  92. package/src/types/attrs.ts +32 -0
  93. package/src/types/base-node-view-options.ts +33 -0
  94. package/src/types/dom-node.ts +1 -0
  95. package/src/types/extension-command.ts +52 -0
  96. package/src/types/extension-mark.ts +15 -0
  97. package/src/types/extension-node.ts +15 -0
  98. package/src/types/extension.spec.ts +56 -0
  99. package/src/types/extension.ts +168 -0
  100. package/src/types/model.ts +54 -0
  101. package/src/types/object-entries.ts +13 -0
  102. package/src/types/pick-string-literal.spec.ts +10 -0
  103. package/src/types/pick-string-literal.ts +6 -0
  104. package/src/types/pick-sub-type.spec.ts +20 -0
  105. package/src/types/pick-sub-type.ts +6 -0
  106. package/src/types/priority.ts +12 -0
  107. package/src/types/setter.ts +4 -0
  108. package/src/types/simplify-deeper.spec.ts +40 -0
  109. package/src/types/simplify-deeper.ts +6 -0
  110. package/src/types/simplify-union.spec.ts +21 -0
  111. package/src/types/simplify-union.ts +11 -0
  112. package/src/utils/array-grouping.spec.ts +29 -0
  113. package/src/utils/array-grouping.ts +25 -0
  114. package/src/utils/array.ts +21 -0
  115. package/src/utils/assert.ts +13 -0
  116. package/src/utils/attrs-match.ts +20 -0
  117. package/src/utils/can-use-regex-lookbehind.ts +12 -0
  118. package/src/utils/clsx.spec.ts +14 -0
  119. package/src/utils/clsx.ts +12 -0
  120. package/src/utils/collect-children.ts +21 -0
  121. package/src/utils/collect-nodes.ts +37 -0
  122. package/src/utils/combine-event-handlers.spec.ts +27 -0
  123. package/src/utils/combine-event-handlers.ts +27 -0
  124. package/src/utils/contains-inline-node.ts +17 -0
  125. package/src/utils/deep-equals.spec.ts +26 -0
  126. package/src/utils/deep-equals.ts +29 -0
  127. package/src/utils/default-block-at.ts +15 -0
  128. package/src/utils/editor-content.spec.ts +47 -0
  129. package/src/utils/editor-content.ts +77 -0
  130. package/src/utils/env.ts +6 -0
  131. package/src/utils/find-parent-node-of-type.ts +29 -0
  132. package/src/utils/find-parent-node.spec.ts +68 -0
  133. package/src/utils/find-parent-node.ts +55 -0
  134. package/src/utils/get-custom-selection.ts +19 -0
  135. package/src/utils/get-dom-api.ts +56 -0
  136. package/src/utils/get-id.spec.ts +14 -0
  137. package/src/utils/get-id.ts +13 -0
  138. package/src/utils/get-mark-type.ts +20 -0
  139. package/src/utils/get-node-type.ts +20 -0
  140. package/src/utils/get-node-types.ts +19 -0
  141. package/src/utils/includes-mark.ts +18 -0
  142. package/src/utils/is-at-block-start.ts +26 -0
  143. package/src/utils/is-in-code-block.ts +18 -0
  144. package/src/utils/is-mark-absent.spec.ts +53 -0
  145. package/src/utils/is-mark-absent.ts +42 -0
  146. package/src/utils/is-mark-active.ts +27 -0
  147. package/src/utils/is-node-active.ts +25 -0
  148. package/src/utils/is-subset.spec.ts +12 -0
  149. package/src/utils/is-subset.ts +11 -0
  150. package/src/utils/maybe-run.spec.ts +39 -0
  151. package/src/utils/maybe-run.ts +11 -0
  152. package/src/utils/merge-objects.spec.ts +30 -0
  153. package/src/utils/merge-objects.ts +11 -0
  154. package/src/utils/merge-specs.ts +35 -0
  155. package/src/utils/object-equal.spec.ts +26 -0
  156. package/src/utils/object-equal.ts +28 -0
  157. package/src/utils/output-spec.test.ts +95 -0
  158. package/src/utils/output-spec.ts +130 -0
  159. package/src/utils/parse.spec.ts +46 -0
  160. package/src/utils/parse.ts +321 -0
  161. package/src/utils/remove-undefined-values.spec.ts +15 -0
  162. package/src/utils/remove-undefined-values.ts +9 -0
  163. package/src/utils/set-selection-around.ts +11 -0
  164. package/src/utils/type-assertion.ts +91 -0
  165. package/src/utils/unicode.spec.ts +10 -0
  166. package/src/utils/unicode.ts +4 -0
  167. package/src/utils/with-skip-code-block.ts +15 -0
  168. package/dist/editor-CjVyjJqw.d.ts +0 -739
@@ -0,0 +1,25 @@
1
+ import type { Extension } from '../types/extension'
2
+ import type { Priority } from '../types/priority'
3
+
4
+ import { union } from './union'
5
+
6
+ /**
7
+ * Return an new extension with the given priority.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { Priority, withPriority } from 'prosekit/core'
12
+ *
13
+ * const extension = withPriority(defineMyExtension(), Priority.high)
14
+ * ```
15
+ *
16
+ * @public
17
+ */
18
+ export function withPriority<T extends Extension>(
19
+ extension: T,
20
+ priority: Priority,
21
+ ): T {
22
+ const result = union(extension)
23
+ result.priority = priority
24
+ return result as T
25
+ }
package/src/error.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Base class for all ProseKit errors.
3
+ *
4
+ * @internal
5
+ */
6
+ export class ProseKitError extends Error {}
7
+
8
+ /**
9
+ * @internal
10
+ */
11
+ export class EditorNotFoundError extends ProseKitError {
12
+ constructor() {
13
+ super(
14
+ 'Unable to find editor. Pass it as an argument or call this function inside a ProseKit component.',
15
+ )
16
+ }
17
+ }
18
+
19
+ /**
20
+ * @internal
21
+ */
22
+ export class DOMDocumentNotFoundError extends ProseKitError {
23
+ constructor() {
24
+ super(
25
+ 'Unable to find browser Document. When not in the browser environment, you need to pass a DOM Document.',
26
+ )
27
+ }
28
+ }
@@ -0,0 +1,107 @@
1
+ import {
2
+ DOMSerializer,
3
+ type DOMOutputSpec,
4
+ type Mark,
5
+ type ProseMirrorNode,
6
+ type Schema,
7
+ } from '@prosekit/pm/model'
8
+ import {
9
+ PluginKey,
10
+ ProseMirrorPlugin,
11
+ } from '@prosekit/pm/state'
12
+
13
+ import { defineFacet } from '../facets/facet'
14
+ import { defineFacetPayload } from '../facets/facet-extension'
15
+ import type { AnyFunction } from '../types/any-function'
16
+ import type { PlainExtension } from '../types/extension'
17
+ import { isNotNullish } from '../utils/type-assertion'
18
+
19
+ import {
20
+ pluginFacet,
21
+ type PluginPayload,
22
+ } from './plugin'
23
+
24
+ type SerializeFragmentFunction = typeof DOMSerializer.prototype.serializeFragment
25
+ type SerializeNodeFunction = typeof DOMSerializer.prototype.serializeNode
26
+ type NodesFromSchemaFunction = typeof DOMSerializer.nodesFromSchema
27
+ type MarksFromSchemaFunction = typeof DOMSerializer.marksFromSchema
28
+
29
+ type FunctionWrapper<T extends AnyFunction> = (fn: T) => T
30
+
31
+ /**
32
+ * @internal
33
+ */
34
+ export interface ClipboardSerializerOptions {
35
+ serializeFragmentWrapper?: FunctionWrapper<SerializeFragmentFunction>
36
+ serializeNodeWrapper?: FunctionWrapper<SerializeNodeFunction>
37
+ nodesFromSchemaWrapper?: FunctionWrapper<NodesFromSchemaFunction>
38
+ marksFromSchemaWrapper?: FunctionWrapper<MarksFromSchemaFunction>
39
+ }
40
+
41
+ function mergeWrappers<T extends AnyFunction>(wrappers: Array<FunctionWrapper<T> | undefined | null>): FunctionWrapper<T> {
42
+ return (fn: T) => wrappers.filter(isNotNullish).reduce((fn, wrapper) => wrapper(fn), fn)
43
+ }
44
+
45
+ function wrapFunction<T extends AnyFunction>(fn: T, wrapper?: FunctionWrapper<T>): T {
46
+ return wrapper ? wrapper(fn) : fn
47
+ }
48
+
49
+ class CustomDOMSerializer extends DOMSerializer {
50
+ constructor(
51
+ nodes: Record<string, (node: ProseMirrorNode) => DOMOutputSpec>,
52
+ marks: Record<string, (mark: Mark, inline: boolean) => DOMOutputSpec>,
53
+ private serializeFragmentWrapper?: FunctionWrapper<SerializeFragmentFunction>,
54
+ private serializeNodeWrapper?: FunctionWrapper<SerializeNodeFunction>,
55
+ ) {
56
+ super(nodes, marks)
57
+ }
58
+
59
+ override serializeFragment(...args: Parameters<SerializeFragmentFunction>): ReturnType<SerializeFragmentFunction> {
60
+ const fn: SerializeFragmentFunction = (...args) => super.serializeFragment(...args)
61
+ return wrapFunction(fn, this.serializeFragmentWrapper)(...args)
62
+ }
63
+
64
+ override serializeNode(...args: Parameters<SerializeNodeFunction>): ReturnType<SerializeNodeFunction> {
65
+ const fn: SerializeNodeFunction = (...args) => super.serializeNode(...args)
66
+ return wrapFunction(fn, this.serializeNodeWrapper)(...args)
67
+ }
68
+ }
69
+
70
+ function createCustomDOMSerializer(schema: Schema, options: ClipboardSerializerOptions) {
71
+ const nodesFromSchema: NodesFromSchemaFunction = (...args) => DOMSerializer.nodesFromSchema(...args)
72
+ const marksFromSchema: MarksFromSchemaFunction = (...args) => DOMSerializer.marksFromSchema(...args)
73
+ const nodes = wrapFunction(nodesFromSchema, options.nodesFromSchemaWrapper)(schema)
74
+ const marks = wrapFunction(marksFromSchema, options.marksFromSchemaWrapper)(schema)
75
+ return new CustomDOMSerializer(nodes, marks, options.serializeFragmentWrapper, options.serializeNodeWrapper)
76
+ }
77
+
78
+ const clipboardSerializerFacet = defineFacet<ClipboardSerializerOptions, PluginPayload>({
79
+ reducer: (inputs: ClipboardSerializerOptions[]): PluginPayload => {
80
+ const options: ClipboardSerializerOptions = {
81
+ serializeFragmentWrapper: mergeWrappers(inputs.map((input) => input.serializeFragmentWrapper)),
82
+ serializeNodeWrapper: mergeWrappers(inputs.map((input) => input.serializeNodeWrapper)),
83
+ nodesFromSchemaWrapper: mergeWrappers(inputs.map((input) => input.nodesFromSchemaWrapper)),
84
+ marksFromSchemaWrapper: mergeWrappers(inputs.map((input) => input.marksFromSchemaWrapper)),
85
+ }
86
+
87
+ return ({ schema }) => {
88
+ const clipboardSerializer = createCustomDOMSerializer(schema, options)
89
+
90
+ return [
91
+ new ProseMirrorPlugin({
92
+ key: new PluginKey('prosekit-clipboard-serializer'),
93
+ props: { clipboardSerializer },
94
+ }),
95
+ ]
96
+ }
97
+ },
98
+ singleton: true,
99
+ parent: pluginFacet,
100
+ })
101
+
102
+ /**
103
+ * @internal
104
+ */
105
+ export function defineClipboardSerializer(options: ClipboardSerializerOptions): PlainExtension {
106
+ return defineFacetPayload(clipboardSerializerFacet, [options]) as PlainExtension
107
+ }
@@ -0,0 +1,121 @@
1
+ import {
2
+ addMark,
3
+ type AddMarkOptions,
4
+ } from '../commands/add-mark'
5
+ import {
6
+ insertDefaultBlock,
7
+ type InsertDefaultBlockOptions,
8
+ } from '../commands/insert-default-block'
9
+ import {
10
+ insertNode,
11
+ type InsertNodeOptions,
12
+ } from '../commands/insert-node'
13
+ import {
14
+ insertText,
15
+ type InsertTextOptions,
16
+ } from '../commands/insert-text'
17
+ import {
18
+ removeMark,
19
+ type RemoveMarkOptions,
20
+ } from '../commands/remove-mark'
21
+ import {
22
+ removeNode,
23
+ type RemoveNodeOptions,
24
+ } from '../commands/remove-node'
25
+ import { selectAll } from '../commands/select-all'
26
+ import {
27
+ setBlockType,
28
+ type SetBlockTypeOptions,
29
+ } from '../commands/set-block-type'
30
+ import {
31
+ setNodeAttrs,
32
+ type SetNodeAttrsOptions,
33
+ } from '../commands/set-node-attrs'
34
+ import {
35
+ toggleWrap,
36
+ type ToggleWrapOptions,
37
+ } from '../commands/toggle-wrap'
38
+ import {
39
+ unsetBlockType,
40
+ type UnsetBlockTypeOptions,
41
+ } from '../commands/unset-block-type'
42
+ import {
43
+ unsetMark,
44
+ type UnsetMarkOptions,
45
+ } from '../commands/unset-mark'
46
+ import {
47
+ wrap,
48
+ type WrapOptions,
49
+ } from '../commands/wrap'
50
+ import { commandFacet } from '../facets/command'
51
+ import { defineFacetPayload } from '../facets/facet-extension'
52
+ import type { Extension } from '../types/extension'
53
+ import type { CommandCreator } from '../types/extension-command'
54
+
55
+ export function defineCommands<
56
+ T extends Record<string, CommandCreator> = Record<string, CommandCreator>,
57
+ >(
58
+ commands: T,
59
+ ): Extension<{
60
+ Commands: { [K in keyof T]: Parameters<T[K]> }
61
+ }> {
62
+ return defineFacetPayload(commandFacet, [commands]) as Extension<{
63
+ Commands: { [K in keyof T]: Parameters<T[K]> }
64
+ }>
65
+ }
66
+
67
+ /**
68
+ * @internal
69
+ */
70
+ export type BaseCommandsExtension = Extension<{
71
+ Commands: {
72
+ insertText: [options: InsertTextOptions]
73
+ insertNode: [options: InsertNodeOptions]
74
+ removeNode: [options: RemoveNodeOptions]
75
+ wrap: [options: WrapOptions]
76
+ toggleWrap: [options: ToggleWrapOptions]
77
+ setBlockType: [options: SetBlockTypeOptions]
78
+ setNodeAttrs: [options: SetNodeAttrsOptions]
79
+ insertDefaultBlock: [options?: InsertDefaultBlockOptions]
80
+ selectAll: []
81
+ addMark: [options: AddMarkOptions]
82
+ removeMark: [options: RemoveMarkOptions]
83
+ unsetBlockType: [options?: UnsetBlockTypeOptions]
84
+ unsetMark: [options?: UnsetMarkOptions]
85
+ }
86
+ }>
87
+
88
+ /**
89
+ * Add some base commands
90
+ *
91
+ * @public
92
+ */
93
+ export function defineBaseCommands(): BaseCommandsExtension {
94
+ return defineCommands({
95
+ insertText,
96
+
97
+ insertNode,
98
+
99
+ removeNode,
100
+
101
+ wrap,
102
+
103
+ toggleWrap,
104
+
105
+ setBlockType,
106
+
107
+ setNodeAttrs,
108
+
109
+ insertDefaultBlock,
110
+
111
+ selectAll,
112
+
113
+ addMark,
114
+
115
+ removeMark,
116
+
117
+ unsetBlockType,
118
+
119
+ unsetMark,
120
+ })
121
+ }
@@ -0,0 +1,60 @@
1
+ import {
2
+ describe,
3
+ expect,
4
+ it,
5
+ } from 'vitest'
6
+
7
+ import { createEditor } from '../editor/editor'
8
+ import { defineTestExtension } from '../testing'
9
+
10
+ import type { DefaultStateOptions } from './default-state'
11
+
12
+ describe('defineDefaultState', () => {
13
+ const docJSON = {
14
+ type: 'doc',
15
+ content: [
16
+ { type: 'paragraph', content: [{ type: 'text', text: 'docJSON' }] },
17
+ ],
18
+ }
19
+ const docHTMLString = '<p>docHTMLString</p>'
20
+ const docHTMLElement = document.createElement('p')
21
+ docHTMLElement.innerHTML = 'docHTMLElement'
22
+
23
+ it('can set the default document', () => {
24
+ const run = (options: DefaultStateOptions): string => {
25
+ const extension = defineTestExtension()
26
+ const editor = createEditor({ extension, ...options })
27
+ return editor.state.doc.toString()
28
+ }
29
+
30
+ expect(run({ defaultDoc: docJSON })).toContain('docJSON')
31
+ expect(run({ defaultHTML: docHTMLString })).toContain('docHTMLString')
32
+ expect(run({ defaultHTML: docHTMLElement })).toContain('docHTMLElement')
33
+
34
+ expect(run({ defaultContent: docJSON })).toContain('docJSON')
35
+ expect(run({ defaultContent: docHTMLString })).toContain('docHTMLString')
36
+ expect(run({ defaultContent: docHTMLElement })).toContain('docHTMLElement')
37
+ })
38
+
39
+ it('can set the default selection', () => {
40
+ const extension = defineTestExtension()
41
+ const editor = createEditor({
42
+ extension,
43
+ defaultContent: docJSON,
44
+ defaultSelection: {
45
+ anchor: 4,
46
+ head: 1,
47
+ type: 'text',
48
+ },
49
+ })
50
+
51
+ expect(editor.state.selection.toJSON()).toEqual({
52
+ anchor: 4,
53
+ head: 1,
54
+ type: 'text',
55
+ })
56
+ expect(editor.state.selection.content().content.toString()).toEqual(
57
+ '<paragraph("doc")>',
58
+ )
59
+ })
60
+ })
@@ -0,0 +1,76 @@
1
+ import {
2
+ Selection,
3
+ type EditorStateConfig,
4
+ } from '@prosekit/pm/state'
5
+
6
+ import { defineFacetPayload } from '../facets/facet-extension'
7
+ import { stateFacet } from '../facets/state'
8
+ import type { PlainExtension } from '../types/extension'
9
+ import type {
10
+ NodeJSON,
11
+ SelectionJSON,
12
+ } from '../types/model'
13
+ import { getEditorContentJSON } from '../utils/editor-content'
14
+
15
+ /**
16
+ * @public
17
+ */
18
+ export interface DefaultStateOptions {
19
+ /**
20
+ * The starting document to use when creating the editor. It can be a
21
+ * ProseMirror node JSON object, a HTML string, or a HTML element instance.
22
+ */
23
+ defaultContent?: NodeJSON | string | HTMLElement
24
+
25
+ /**
26
+ * A JSON object representing the starting document to use when creating the
27
+ * editor.
28
+ *
29
+ * @deprecated Use `defaultContent` instead.
30
+ */
31
+ defaultDoc?: NodeJSON
32
+
33
+ /**
34
+ * A HTML element or a HTML string representing the starting document to use
35
+ * when creating the editor.
36
+ *
37
+ * @deprecated Use `defaultContent` instead.
38
+ */
39
+ defaultHTML?: string | HTMLElement
40
+
41
+ /**
42
+ * A JSON object representing the starting selection to use when creating the
43
+ * editor. It's only used when `defaultContent` is also provided.
44
+ */
45
+ defaultSelection?: SelectionJSON
46
+ }
47
+
48
+ /**
49
+ * Define a default state for the editor.
50
+ *
51
+ * @param options
52
+ *
53
+ * @public
54
+ */
55
+ export function defineDefaultState({
56
+ defaultSelection,
57
+ defaultContent,
58
+ defaultDoc,
59
+ defaultHTML,
60
+ }: DefaultStateOptions): PlainExtension {
61
+ const defaultDocContent = defaultContent || defaultDoc || defaultHTML
62
+
63
+ return defineFacetPayload(stateFacet, [
64
+ ({ schema }) => {
65
+ const config: EditorStateConfig = {}
66
+ if (defaultDocContent) {
67
+ const json = getEditorContentJSON(schema, defaultDocContent)
68
+ config.doc = schema.nodeFromJSON(json)
69
+ if (defaultSelection) {
70
+ config.selection = Selection.fromJSON(config.doc, defaultSelection)
71
+ }
72
+ }
73
+ return config
74
+ },
75
+ ]) as PlainExtension
76
+ }
@@ -0,0 +1,31 @@
1
+ import type { Attrs } from '@prosekit/pm/model'
2
+
3
+ import type { Extension } from '../types/extension'
4
+
5
+ import { defineNodeSpec } from './node-spec'
6
+
7
+ /**
8
+ * @internal
9
+ */
10
+ export type DocExtension = Extension<{ Nodes: { doc: Attrs } }>
11
+
12
+ /**
13
+ * @public
14
+ *
15
+ * @deprecated Use the following import instead:
16
+ *
17
+ * ```ts
18
+ * import { defineDoc } from 'prosekit/extensions/doc'
19
+ * ```
20
+ */
21
+ export function defineDoc(): DocExtension {
22
+ console.warn(
23
+ '[prosekit] The `defineDoc` function from `prosekit/core` is deprecated. Use the following import instead: `import { defineDoc } from "prosekit/extensions/doc"`.',
24
+ )
25
+
26
+ return defineNodeSpec({
27
+ name: 'doc',
28
+ content: 'block+',
29
+ topNode: true,
30
+ })
31
+ }
@@ -0,0 +1,34 @@
1
+ import type { EditorState } from '@prosekit/pm/state'
2
+ import type { EditorView } from '@prosekit/pm/view'
3
+
4
+ import type { PlainExtension } from '../../types/extension'
5
+
6
+ import { defineUpdateHandler } from './plugin-view'
7
+
8
+ /**
9
+ * A function that is called when the editor document is changed.
10
+ *
11
+ * @param view - The editor view.
12
+ * @param prevState - The previous editor state.
13
+ *
14
+ * @public
15
+ */
16
+ export type DocChangeHandler = (
17
+ view: EditorView,
18
+ prevState: EditorState,
19
+ ) => void
20
+
21
+ /**
22
+ * Registers a event handler that is called when the editor document is changed.
23
+ *
24
+ * @public
25
+ */
26
+ export function defineDocChangeHandler(
27
+ handler: DocChangeHandler,
28
+ ): PlainExtension {
29
+ return defineUpdateHandler((view, prevState) => {
30
+ if (!view.state.doc.eq(prevState.doc)) {
31
+ handler(view, prevState)
32
+ }
33
+ })
34
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ describe,
3
+ expect,
4
+ it,
5
+ vi,
6
+ } from 'vitest'
7
+
8
+ import { createEditor } from '../../editor/editor'
9
+ import { defineTestExtension } from '../../testing'
10
+
11
+ import { defineDOMEventHandler } from './dom-event'
12
+
13
+ describe('defineDOMEventHandler', () => {
14
+ it('should register and unregister event handlers dynamically', () => {
15
+ const div = document.body.appendChild(document.createElement('div'))
16
+ const handleFocus = vi.fn()
17
+ const handleBlur = vi.fn()
18
+
19
+ const extension = defineTestExtension()
20
+ const editor = createEditor({ extension })
21
+ editor.mount(div)
22
+
23
+ expect(handleFocus).toHaveBeenCalledTimes(0)
24
+ expect(handleBlur).toHaveBeenCalledTimes(0)
25
+
26
+ const disposeFocus = editor.use(defineFocusEventHandler(handleFocus))
27
+ const disposeBlur = editor.use(defineBlurEventHandler(handleBlur))
28
+ expect(handleFocus).toHaveBeenCalledTimes(0)
29
+ expect(handleBlur).toHaveBeenCalledTimes(0)
30
+
31
+ editor.focus()
32
+ expect(handleFocus).toHaveBeenCalledTimes(1)
33
+ expect(handleBlur).toHaveBeenCalledTimes(0)
34
+
35
+ editor.blur()
36
+ expect(handleFocus).toHaveBeenCalledTimes(1)
37
+ expect(handleBlur).toHaveBeenCalledTimes(1)
38
+
39
+ disposeFocus()
40
+ expect(handleFocus).toHaveBeenCalledTimes(1)
41
+ expect(handleBlur).toHaveBeenCalledTimes(1)
42
+
43
+ editor.focus()
44
+ expect(handleFocus).toHaveBeenCalledTimes(1)
45
+ expect(handleBlur).toHaveBeenCalledTimes(1)
46
+
47
+ editor.blur()
48
+ expect(handleFocus).toHaveBeenCalledTimes(1)
49
+ expect(handleBlur).toHaveBeenCalledTimes(2)
50
+
51
+ disposeBlur()
52
+ expect(handleFocus).toHaveBeenCalledTimes(1)
53
+ expect(handleBlur).toHaveBeenCalledTimes(2)
54
+
55
+ editor.focus()
56
+ editor.blur()
57
+ editor.focus()
58
+ editor.blur()
59
+ expect(handleFocus).toHaveBeenCalledTimes(1)
60
+ expect(handleBlur).toHaveBeenCalledTimes(2)
61
+ })
62
+ })
63
+
64
+ function defineFocusEventHandler(handler: VoidFunction) {
65
+ return defineDOMEventHandler('focus', handler)
66
+ }
67
+
68
+ function defineBlurEventHandler(handler: VoidFunction) {
69
+ return defineDOMEventHandler('blur', handler)
70
+ }
@@ -0,0 +1,117 @@
1
+ import {
2
+ PluginKey,
3
+ ProseMirrorPlugin,
4
+ } from '@prosekit/pm/state'
5
+ import type {
6
+ DOMEventMap,
7
+ EditorView,
8
+ } from '@prosekit/pm/view'
9
+
10
+ import {
11
+ defineFacet,
12
+ type Facet,
13
+ } from '../../facets/facet'
14
+ import { defineFacetPayload } from '../../facets/facet-extension'
15
+ import type { PlainExtension } from '../../types/extension'
16
+ import type { Setter } from '../../types/setter'
17
+ import { groupEntries } from '../../utils/array-grouping'
18
+ import { combineEventHandlers } from '../../utils/combine-event-handlers'
19
+ import {
20
+ pluginFacet,
21
+ type PluginPayload,
22
+ } from '../plugin'
23
+
24
+ /**
25
+ * A function to handle the events fired on the editable DOM element. Returns
26
+ * `true` to indicate that it handled the given event. When returning `true`,
27
+ * you are responsible for calling `event.preventDefault()` yourself (or not, if
28
+ * you want to allow the default behavior).
29
+ *
30
+ * @public
31
+ */
32
+ export type DOMEventHandler<Event extends keyof DOMEventMap = string> = (
33
+ view: EditorView,
34
+ event: DOMEventMap[Event],
35
+ ) => boolean | void
36
+
37
+ /**
38
+ * @internal
39
+ */
40
+ export function defineDomEventFacetPayload(
41
+ ...payloads: DOMEventPayload[]
42
+ ): PlainExtension {
43
+ return defineFacetPayload<DOMEventPayload>(
44
+ domEventFacet,
45
+ payloads,
46
+ ) as PlainExtension
47
+ }
48
+
49
+ /**
50
+ * Register a new event handler for the given event type.
51
+ *
52
+ * @public
53
+ */
54
+ export function defineDOMEventHandler<Event extends keyof DOMEventMap = string>(
55
+ event: Event,
56
+ handler: DOMEventHandler<Event>,
57
+ ): PlainExtension {
58
+ return defineDomEventFacetPayload([
59
+ event as string,
60
+ handler as DOMEventHandler,
61
+ ])
62
+ }
63
+
64
+ /**
65
+ * @internal
66
+ */
67
+ type DOMEventPayload = [event: string, handler: DOMEventHandler]
68
+
69
+ /**
70
+ * @internal
71
+ */
72
+ const domEventFacet: Facet<DOMEventPayload, PluginPayload> = defineFacet(
73
+ {
74
+ reduce: () => {
75
+ const setHandlersMap: Record<string, Setter<DOMEventHandler[]>> = {}
76
+ const combinedHandlerMap: Record<string, DOMEventHandler> = {}
77
+
78
+ let plugin: ProseMirrorPlugin | undefined
79
+
80
+ const update = (payloads: DOMEventPayload[]): void => {
81
+ let hasNewEvent = false
82
+
83
+ for (const [event] of payloads) {
84
+ if (!setHandlersMap[event]) {
85
+ hasNewEvent = true
86
+ const [setHandlers, combinedHandler] = combineEventHandlers<DOMEventHandler>()
87
+ setHandlersMap[event] = setHandlers
88
+ const e: DOMEventHandler = (view, eventObject) => {
89
+ return combinedHandler(view, eventObject)
90
+ }
91
+ combinedHandlerMap[event] = e
92
+ }
93
+ }
94
+
95
+ const map: Record<string, DOMEventHandler[] | undefined> = groupEntries<DOMEventMap>(payloads)
96
+ for (const [event, setHandlers] of Object.entries(setHandlersMap)) {
97
+ const handlers = map[event] ?? []
98
+ setHandlers(handlers)
99
+ }
100
+
101
+ if (hasNewEvent) {
102
+ plugin = new ProseMirrorPlugin({
103
+ key: new PluginKey('prosekit-dom-event-handler'),
104
+ props: { handleDOMEvents: combinedHandlerMap },
105
+ })
106
+ }
107
+ }
108
+
109
+ return function reducer(inputs) {
110
+ update(inputs)
111
+ return plugin ?? []
112
+ }
113
+ },
114
+ parent: pluginFacet,
115
+ singleton: true,
116
+ },
117
+ )