@tiptap/core 3.0.0-next.3 → 3.0.0-next.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 (128) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +5 -1
  3. package/dist/index.cjs +2402 -2540
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +1355 -1273
  6. package/dist/index.d.ts +1355 -1273
  7. package/dist/index.js +2408 -2562
  8. package/dist/index.js.map +1 -1
  9. package/package.json +9 -5
  10. package/src/CommandManager.ts +2 -9
  11. package/src/Editor.ts +87 -72
  12. package/src/EventEmitter.ts +7 -10
  13. package/src/Extension.ts +8 -14
  14. package/src/ExtensionManager.ts +26 -125
  15. package/src/InputRule.ts +35 -48
  16. package/src/Mark.ts +9 -9
  17. package/src/Node.ts +9 -9
  18. package/src/NodePos.ts +1 -3
  19. package/src/NodeView.ts +10 -20
  20. package/src/PasteRule.ts +43 -55
  21. package/src/Tracker.ts +7 -9
  22. package/src/commands/blur.ts +14 -12
  23. package/src/commands/clearContent.ts +6 -4
  24. package/src/commands/clearNodes.ts +32 -30
  25. package/src/commands/command.ts +1 -1
  26. package/src/commands/createParagraphNear.ts +5 -3
  27. package/src/commands/cut.ts +12 -10
  28. package/src/commands/deleteCurrentNode.ts +23 -21
  29. package/src/commands/deleteNode.ts +18 -16
  30. package/src/commands/deleteRange.ts +10 -8
  31. package/src/commands/deleteSelection.ts +5 -3
  32. package/src/commands/enter.ts +6 -4
  33. package/src/commands/exitCode.ts +5 -3
  34. package/src/commands/extendMarkRange.ts +14 -12
  35. package/src/commands/first.ts +2 -4
  36. package/src/commands/focus.ts +45 -48
  37. package/src/commands/forEach.ts +2 -2
  38. package/src/commands/insertContent.ts +12 -14
  39. package/src/commands/insertContentAt.ts +101 -98
  40. package/src/commands/join.ts +20 -12
  41. package/src/commands/joinItemBackward.ts +16 -18
  42. package/src/commands/joinItemForward.ts +16 -18
  43. package/src/commands/joinTextblockBackward.ts +5 -3
  44. package/src/commands/joinTextblockForward.ts +5 -3
  45. package/src/commands/keyboardShortcut.ts +29 -34
  46. package/src/commands/lift.ts +10 -8
  47. package/src/commands/liftEmptyBlock.ts +6 -4
  48. package/src/commands/liftListItem.ts +6 -4
  49. package/src/commands/newlineInCode.ts +5 -3
  50. package/src/commands/resetAttributes.ts +36 -41
  51. package/src/commands/scrollIntoView.ts +9 -7
  52. package/src/commands/selectAll.ts +10 -8
  53. package/src/commands/selectNodeBackward.ts +5 -3
  54. package/src/commands/selectNodeForward.ts +5 -3
  55. package/src/commands/selectParentNode.ts +5 -3
  56. package/src/commands/selectTextblockEnd.ts +5 -3
  57. package/src/commands/selectTextblockStart.ts +5 -3
  58. package/src/commands/setContent.ts +25 -25
  59. package/src/commands/setMark.ts +55 -57
  60. package/src/commands/setMeta.ts +7 -5
  61. package/src/commands/setNode.ts +32 -30
  62. package/src/commands/setNodeSelection.ts +11 -9
  63. package/src/commands/setTextSelection.ts +15 -13
  64. package/src/commands/sinkListItem.ts +6 -4
  65. package/src/commands/splitBlock.ts +67 -76
  66. package/src/commands/splitListItem.ts +93 -106
  67. package/src/commands/toggleList.ts +73 -71
  68. package/src/commands/toggleMark.ts +11 -9
  69. package/src/commands/toggleNode.ts +18 -16
  70. package/src/commands/toggleWrap.ts +10 -8
  71. package/src/commands/undoInputRule.ts +31 -29
  72. package/src/commands/unsetAllMarks.ts +16 -14
  73. package/src/commands/unsetMark.ts +27 -25
  74. package/src/commands/updateAttributes.ts +92 -100
  75. package/src/commands/wrapIn.ts +6 -4
  76. package/src/commands/wrapInList.ts +6 -4
  77. package/src/extensions/clipboardTextSerializer.ts +2 -4
  78. package/src/extensions/focusEvents.ts +2 -6
  79. package/src/extensions/keymap.ts +54 -50
  80. package/src/extensions/paste.ts +0 -1
  81. package/src/extensions/tabindex.ts +1 -1
  82. package/src/helpers/combineTransactionSteps.ts +1 -4
  83. package/src/helpers/createChainableState.ts +1 -4
  84. package/src/helpers/createDocument.ts +1 -3
  85. package/src/helpers/createNodeFromContent.ts +4 -10
  86. package/src/helpers/findChildrenInRange.ts +1 -5
  87. package/src/helpers/findParentNode.ts +3 -1
  88. package/src/helpers/flattenExtensions.ts +30 -0
  89. package/src/helpers/getAttributes.ts +1 -4
  90. package/src/helpers/getAttributesFromExtensions.ts +28 -37
  91. package/src/helpers/getChangedRanges.ts +13 -11
  92. package/src/helpers/getExtensionField.ts +1 -4
  93. package/src/helpers/getMarkAttributes.ts +1 -4
  94. package/src/helpers/getMarkRange.ts +5 -15
  95. package/src/helpers/getMarkType.ts +1 -3
  96. package/src/helpers/getNodeAttributes.ts +1 -4
  97. package/src/helpers/getNodeType.ts +1 -3
  98. package/src/helpers/getRenderedAttributes.ts +1 -3
  99. package/src/helpers/getSchema.ts +2 -2
  100. package/src/helpers/getSchemaByResolvedExtensions.ts +45 -77
  101. package/src/helpers/getSplittedAttributes.ts +4 -4
  102. package/src/helpers/getTextContentFromNodes.ts +8 -11
  103. package/src/helpers/index.ts +4 -0
  104. package/src/helpers/injectExtensionAttributesToParseRule.ts +1 -1
  105. package/src/helpers/isActive.ts +1 -5
  106. package/src/helpers/isExtensionRulesEnabled.ts +1 -3
  107. package/src/helpers/isNodeEmpty.ts +2 -2
  108. package/src/helpers/resolveExtensions.ts +25 -0
  109. package/src/helpers/resolveFocusPosition.ts +3 -14
  110. package/src/helpers/rewriteUnknownContent.ts +149 -0
  111. package/src/helpers/sortExtensions.ts +26 -0
  112. package/src/inputRules/markInputRule.ts +1 -5
  113. package/src/inputRules/nodeInputRule.ts +2 -9
  114. package/src/inputRules/textInputRule.ts +1 -4
  115. package/src/inputRules/textblockTypeInputRule.ts +2 -8
  116. package/src/inputRules/wrappingInputRule.ts +13 -19
  117. package/src/pasteRules/markPasteRule.ts +1 -3
  118. package/src/pasteRules/nodePasteRule.ts +2 -8
  119. package/src/pasteRules/textPasteRule.ts +1 -4
  120. package/src/types.ts +212 -172
  121. package/src/utilities/createStyleTag.ts +3 -1
  122. package/src/utilities/deleteProps.ts +7 -11
  123. package/src/utilities/findDuplicates.ts +4 -1
  124. package/src/utilities/isFunction.ts +1 -0
  125. package/src/utilities/isMacOS.ts +1 -3
  126. package/src/utilities/isiOS.ts +5 -10
  127. package/src/utilities/mergeAttributes.ts +16 -6
  128. package/src/utilities/removeDuplicates.ts +1 -3
@@ -4,10 +4,7 @@ import { Selection, TextSelection } from '@tiptap/pm/state'
4
4
  import { FocusPosition } from '../types.js'
5
5
  import { minMax } from '../utilities/minMax.js'
6
6
 
7
- export function resolveFocusPosition(
8
- doc: ProseMirrorNode,
9
- position: FocusPosition = null,
10
- ): Selection | null {
7
+ export function resolveFocusPosition(doc: ProseMirrorNode, position: FocusPosition = null): Selection | null {
11
8
  if (!position) {
12
9
  return null
13
10
  }
@@ -27,16 +24,8 @@ export function resolveFocusPosition(
27
24
  const maxPos = selectionAtEnd.to
28
25
 
29
26
  if (position === 'all') {
30
- return TextSelection.create(
31
- doc,
32
- minMax(0, minPos, maxPos),
33
- minMax(doc.content.size, minPos, maxPos),
34
- )
27
+ return TextSelection.create(doc, minMax(0, minPos, maxPos), minMax(doc.content.size, minPos, maxPos))
35
28
  }
36
29
 
37
- return TextSelection.create(
38
- doc,
39
- minMax(position, minPos, maxPos),
40
- minMax(position, minPos, maxPos),
41
- )
30
+ return TextSelection.create(doc, minMax(position, minPos, maxPos), minMax(position, minPos, maxPos))
42
31
  }
@@ -0,0 +1,149 @@
1
+ import type { Schema } from '@tiptap/pm/model'
2
+
3
+ import type { JSONContent } from '../types.js'
4
+
5
+ type RewriteUnknownContentOptions = {
6
+ /**
7
+ * If true, unknown nodes will be treated as paragraphs
8
+ * @default true
9
+ */
10
+ fallbackToParagraph?: boolean
11
+ }
12
+
13
+ type RewrittenContent = {
14
+ /**
15
+ * The original JSON content that was rewritten
16
+ */
17
+ original: JSONContent
18
+ /**
19
+ * The name of the node or mark that was unsupported
20
+ */
21
+ unsupported: string
22
+ }[]
23
+
24
+ /**
25
+ * The actual implementation of the rewriteUnknownContent function
26
+ */
27
+ function rewriteUnknownContentInner({
28
+ json,
29
+ validMarks,
30
+ validNodes,
31
+ options,
32
+ rewrittenContent = [],
33
+ }: {
34
+ json: JSONContent
35
+ validMarks: Set<string>
36
+ validNodes: Set<string>
37
+ options?: RewriteUnknownContentOptions
38
+ rewrittenContent?: RewrittenContent
39
+ }): {
40
+ /**
41
+ * The cleaned JSON content
42
+ */
43
+ json: JSONContent | null
44
+ /**
45
+ * The array of nodes and marks that were rewritten
46
+ */
47
+ rewrittenContent: RewrittenContent
48
+ } {
49
+ if (json.marks && Array.isArray(json.marks)) {
50
+ json.marks = json.marks.filter(mark => {
51
+ const name = typeof mark === 'string' ? mark : mark.type
52
+
53
+ if (validMarks.has(name)) {
54
+ return true
55
+ }
56
+
57
+ rewrittenContent.push({
58
+ original: JSON.parse(JSON.stringify(mark)),
59
+ unsupported: name,
60
+ })
61
+ // Just ignore any unknown marks
62
+ return false
63
+ })
64
+ }
65
+
66
+ if (json.content && Array.isArray(json.content)) {
67
+ json.content = json.content
68
+ .map(
69
+ value =>
70
+ rewriteUnknownContentInner({
71
+ json: value,
72
+ validMarks,
73
+ validNodes,
74
+ options,
75
+ rewrittenContent,
76
+ }).json,
77
+ )
78
+ .filter(a => a !== null && a !== undefined)
79
+ }
80
+
81
+ if (json.type && !validNodes.has(json.type)) {
82
+ rewrittenContent.push({
83
+ original: JSON.parse(JSON.stringify(json)),
84
+ unsupported: json.type,
85
+ })
86
+
87
+ if (json.content && Array.isArray(json.content) && options?.fallbackToParagraph !== false) {
88
+ // Just treat it like a paragraph and hope for the best
89
+ json.type = 'paragraph'
90
+
91
+ return {
92
+ json,
93
+ rewrittenContent,
94
+ }
95
+ }
96
+
97
+ // or just omit it entirely
98
+ return {
99
+ json: null,
100
+ rewrittenContent,
101
+ }
102
+ }
103
+
104
+ return { json, rewrittenContent }
105
+ }
106
+
107
+ /**
108
+ * Rewrite unknown nodes and marks within JSON content
109
+ * Allowing for user within the editor
110
+ */
111
+ export function rewriteUnknownContent(
112
+ /**
113
+ * The JSON content to clean of unknown nodes and marks
114
+ */
115
+ json: JSONContent,
116
+ /**
117
+ * The schema to use for validation
118
+ */
119
+ schema: Schema,
120
+ /**
121
+ * Options for the cleaning process
122
+ */
123
+ options?: RewriteUnknownContentOptions,
124
+ ): {
125
+ /**
126
+ * The cleaned JSON content
127
+ */
128
+ json: JSONContent | null
129
+ /**
130
+ * The array of nodes and marks that were rewritten
131
+ */
132
+ rewrittenContent: {
133
+ /**
134
+ * The original JSON content that was rewritten
135
+ */
136
+ original: JSONContent
137
+ /**
138
+ * The name of the node or mark that was unsupported
139
+ */
140
+ unsupported: string
141
+ }[]
142
+ } {
143
+ return rewriteUnknownContentInner({
144
+ json,
145
+ validNodes: new Set(Object.keys(schema.nodes)),
146
+ validMarks: new Set(Object.keys(schema.marks)),
147
+ options,
148
+ })
149
+ }
@@ -0,0 +1,26 @@
1
+ import { AnyConfig, Extensions } from '../types.js'
2
+ import { getExtensionField } from './getExtensionField.js'
3
+
4
+ /**
5
+ * Sort extensions by priority.
6
+ * @param extensions An array of Tiptap extensions
7
+ * @returns A sorted array of Tiptap extensions by priority
8
+ */
9
+ export function sortExtensions(extensions: Extensions): Extensions {
10
+ const defaultPriority = 100
11
+
12
+ return extensions.sort((a, b) => {
13
+ const priorityA = getExtensionField<AnyConfig['priority']>(a, 'priority') || defaultPriority
14
+ const priorityB = getExtensionField<AnyConfig['priority']>(b, 'priority') || defaultPriority
15
+
16
+ if (priorityA > priorityB) {
17
+ return -1
18
+ }
19
+
20
+ if (priorityA < priorityB) {
21
+ return 1
22
+ }
23
+
24
+ return 0
25
+ })
26
+ }
@@ -13,11 +13,7 @@ import { callOrReturn } from '../utilities/callOrReturn.js'
13
13
  export function markInputRule(config: {
14
14
  find: InputRuleFinder
15
15
  type: MarkType
16
- getAttributes?:
17
- | Record<string, any>
18
- | ((match: ExtendedRegExpMatchArray) => Record<string, any>)
19
- | false
20
- | null
16
+ getAttributes?: Record<string, any> | ((match: ExtendedRegExpMatchArray) => Record<string, any>) | false | null
21
17
  }) {
22
18
  return new InputRule({
23
19
  find: config.find,
@@ -24,11 +24,7 @@ export function nodeInputRule(config: {
24
24
  * A function that returns the attributes for the node
25
25
  * can also be an object of attributes
26
26
  */
27
- getAttributes?:
28
- | Record<string, any>
29
- | ((match: ExtendedRegExpMatchArray) => Record<string, any>)
30
- | false
31
- | null
27
+ getAttributes?: Record<string, any> | ((match: ExtendedRegExpMatchArray) => Record<string, any>) | false | null
32
28
  }) {
33
29
  return new InputRule({
34
30
  find: config.find,
@@ -60,10 +56,7 @@ export function nodeInputRule(config: {
60
56
  } else if (match[0]) {
61
57
  const insertionStart = config.type.isInline ? start : start - 1
62
58
 
63
- tr.insert(insertionStart, config.type.create(attributes)).delete(
64
- tr.mapping.map(start),
65
- tr.mapping.map(end),
66
- )
59
+ tr.insert(insertionStart, config.type.create(attributes)).delete(tr.mapping.map(start), tr.mapping.map(end))
67
60
  }
68
61
 
69
62
  tr.scrollIntoView()
@@ -5,10 +5,7 @@ import { InputRule, InputRuleFinder } from '../InputRule.js'
5
5
  * matched text is typed into it.
6
6
  * @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
7
7
  */
8
- export function textInputRule(config: {
9
- find: InputRuleFinder,
10
- replace: string,
11
- }) {
8
+ export function textInputRule(config: { find: InputRuleFinder; replace: string }) {
12
9
  return new InputRule({
13
10
  find: config.find,
14
11
  handler: ({ state, range, match }) => {
@@ -14,11 +14,7 @@ import { callOrReturn } from '../utilities/callOrReturn.js'
14
14
  export function textblockTypeInputRule(config: {
15
15
  find: InputRuleFinder
16
16
  type: NodeType
17
- getAttributes?:
18
- | Record<string, any>
19
- | ((match: ExtendedRegExpMatchArray) => Record<string, any>)
20
- | false
21
- | null
17
+ getAttributes?: Record<string, any> | ((match: ExtendedRegExpMatchArray) => Record<string, any>) | false | null
22
18
  }) {
23
19
  return new InputRule({
24
20
  find: config.find,
@@ -30,9 +26,7 @@ export function textblockTypeInputRule(config: {
30
26
  return null
31
27
  }
32
28
 
33
- state.tr
34
- .delete(range.from, range.to)
35
- .setBlockType(range.from, range.from, config.type, attributes)
29
+ state.tr.delete(range.from, range.to).setBlockType(range.from, range.from, config.type, attributes)
36
30
  },
37
31
  })
38
32
  }
@@ -22,24 +22,17 @@ import { callOrReturn } from '../utilities/callOrReturn.js'
22
22
  * @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
23
23
  */
24
24
  export function wrappingInputRule(config: {
25
- find: InputRuleFinder,
26
- type: NodeType,
27
- keepMarks?: boolean,
28
- keepAttributes?: boolean,
25
+ find: InputRuleFinder
26
+ type: NodeType
27
+ keepMarks?: boolean
28
+ keepAttributes?: boolean
29
29
  editor?: Editor
30
- getAttributes?:
31
- | Record<string, any>
32
- | ((match: ExtendedRegExpMatchArray) => Record<string, any>)
33
- | false
34
- | null
35
- ,
36
- joinPredicate?: (match: ExtendedRegExpMatchArray, node: ProseMirrorNode) => boolean,
30
+ getAttributes?: Record<string, any> | ((match: ExtendedRegExpMatchArray) => Record<string, any>) | false | null
31
+ joinPredicate?: (match: ExtendedRegExpMatchArray, node: ProseMirrorNode) => boolean
37
32
  }) {
38
33
  return new InputRule({
39
34
  find: config.find,
40
- handler: ({
41
- state, range, match, chain,
42
- }) => {
35
+ handler: ({ state, range, match, chain }) => {
43
36
  const attributes = callOrReturn(config.getAttributes, undefined, match) || {}
44
37
  const tr = state.tr.delete(range.from, range.to)
45
38
  const $start = tr.doc.resolve(range.from)
@@ -65,7 +58,8 @@ export function wrappingInputRule(config: {
65
58
  }
66
59
  if (config.keepAttributes) {
67
60
  /** If the nodeType is `bulletList` or `orderedList` set the `nodeType` as `listItem` */
68
- const nodeType = config.type.name === 'bulletList' || config.type.name === 'orderedList' ? 'listItem' : 'taskList'
61
+ const nodeType =
62
+ config.type.name === 'bulletList' || config.type.name === 'orderedList' ? 'listItem' : 'taskList'
69
63
 
70
64
  chain().updateAttributes(nodeType, attributes).run()
71
65
  }
@@ -73,10 +67,10 @@ export function wrappingInputRule(config: {
73
67
  const before = tr.doc.resolve(range.from - 1).nodeBefore
74
68
 
75
69
  if (
76
- before
77
- && before.type === config.type
78
- && canJoin(tr.doc, range.from - 1)
79
- && (!config.joinPredicate || config.joinPredicate(match, before))
70
+ before &&
71
+ before.type === config.type &&
72
+ canJoin(tr.doc, range.from - 1) &&
73
+ (!config.joinPredicate || config.joinPredicate(match, before))
80
74
  ) {
81
75
  tr.join(range.from - 1)
82
76
  }
@@ -21,9 +21,7 @@ export function markPasteRule(config: {
21
21
  }) {
22
22
  return new PasteRule({
23
23
  find: config.find,
24
- handler: ({
25
- state, range, match, pasteEvent,
26
- }) => {
24
+ handler: ({ state, range, match, pasteEvent }) => {
27
25
  const attributes = callOrReturn(config.getAttributes, undefined, match, pasteEvent)
28
26
 
29
27
  if (attributes === false || attributes === null) {
@@ -17,17 +17,11 @@ export function nodePasteRule(config: {
17
17
  | ((match: ExtendedRegExpMatchArray, event: ClipboardEvent) => Record<string, any>)
18
18
  | false
19
19
  | null
20
- getContent?:
21
- | JSONContent[]
22
- | ((attrs: Record<string, any>) => JSONContent[])
23
- | false
24
- | null
20
+ getContent?: JSONContent[] | ((attrs: Record<string, any>) => JSONContent[]) | false | null
25
21
  }) {
26
22
  return new PasteRule({
27
23
  find: config.find,
28
- handler({
29
- match, chain, range, pasteEvent,
30
- }) {
24
+ handler({ match, chain, range, pasteEvent }) {
31
25
  const attributes = callOrReturn(config.getAttributes, undefined, match, pasteEvent)
32
26
  const content = callOrReturn(config.getContent, undefined, attributes)
33
27
 
@@ -5,10 +5,7 @@ import { PasteRule, PasteRuleFinder } from '../PasteRule.js'
5
5
  * matched text is pasted into it.
6
6
  * @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#paste-rules
7
7
  */
8
- export function textPasteRule(config: {
9
- find: PasteRuleFinder,
10
- replace: string,
11
- }) {
8
+ export function textPasteRule(config: { find: PasteRuleFinder; replace: string }) {
12
9
  return new PasteRule({
13
10
  find: config.find,
14
11
  handler: ({ state, range, match }) => {