@tiptap/core 3.0.0-next.4 → 3.0.0-next.6

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 (39) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/index.cjs +352 -238
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1084 -1431
  5. package/dist/index.d.ts +1084 -1431
  6. package/dist/index.js +344 -235
  7. package/dist/index.js.map +1 -1
  8. package/dist/jsx-runtime/jsx-runtime.cjs +56 -0
  9. package/dist/jsx-runtime/jsx-runtime.cjs.map +1 -0
  10. package/dist/jsx-runtime/jsx-runtime.d.cts +22 -0
  11. package/dist/jsx-runtime/jsx-runtime.d.ts +22 -0
  12. package/dist/jsx-runtime/jsx-runtime.js +26 -0
  13. package/dist/jsx-runtime/jsx-runtime.js.map +1 -0
  14. package/jsx-runtime/index.cjs +1 -0
  15. package/jsx-runtime/index.d.cts +1 -0
  16. package/jsx-runtime/index.d.ts +1 -0
  17. package/jsx-runtime/index.js +1 -0
  18. package/package.json +20 -3
  19. package/src/Editor.ts +104 -22
  20. package/src/Extendable.ts +483 -0
  21. package/src/Extension.ts +5 -490
  22. package/src/ExtensionManager.ts +55 -10
  23. package/src/Mark.ts +135 -623
  24. package/src/MarkView.ts +66 -0
  25. package/src/Node.ts +325 -829
  26. package/src/commands/clearContent.ts +9 -4
  27. package/src/commands/focus.ts +7 -1
  28. package/src/commands/insertContentAt.ts +6 -2
  29. package/src/commands/setContent.ts +15 -14
  30. package/src/extensions/delete.ts +89 -0
  31. package/src/extensions/index.ts +1 -0
  32. package/src/extensions/keymap.ts +4 -0
  33. package/src/helpers/getExtensionField.ts +10 -7
  34. package/src/index.ts +3 -7
  35. package/src/jsx-runtime.ts +64 -0
  36. package/src/types.ts +334 -19
  37. package/src/utilities/elementFromString.ts +3 -0
  38. package/src/utilities/index.ts +1 -0
  39. package/src/utilities/mergeAttributes.ts +1 -1
@@ -5,16 +5,21 @@ declare module '@tiptap/core' {
5
5
  clearContent: {
6
6
  /**
7
7
  * Clear the whole document.
8
- * @param emitUpdate Whether to emit an update event.
9
8
  * @example editor.commands.clearContent()
10
9
  */
11
- clearContent: (emitUpdate?: boolean) => ReturnType
10
+ clearContent: (
11
+ /**
12
+ * Whether to emit an update event.
13
+ * @default true
14
+ */
15
+ emitUpdate?: boolean,
16
+ ) => ReturnType
12
17
  }
13
18
  }
14
19
  }
15
20
 
16
21
  export const clearContent: RawCommands['clearContent'] =
17
- (emitUpdate = false) =>
22
+ (emitUpdate = true) =>
18
23
  ({ commands }) => {
19
- return commands.setContent('', emitUpdate)
24
+ return commands.setContent('', { emitUpdate })
20
25
  }
@@ -1,6 +1,8 @@
1
1
  import { isTextSelection } from '../helpers/isTextSelection.js'
2
2
  import { resolveFocusPosition } from '../helpers/resolveFocusPosition.js'
3
3
  import { FocusPosition, RawCommands } from '../types.js'
4
+ import { isAndroid } from '../utilities/isAndroid.js'
5
+ import { isiOS } from '../utilities/isiOS.js'
4
6
 
5
7
  declare module '@tiptap/core' {
6
8
  interface Commands<ReturnType> {
@@ -39,7 +41,11 @@ export const focus: RawCommands['focus'] =
39
41
  }
40
42
 
41
43
  const delayedFocus = () => {
42
- ;(view.dom as HTMLElement).focus()
44
+ // focus within `requestAnimationFrame` breaks focus on iOS and Android
45
+ // so we have to call this
46
+ if (isiOS() || isAndroid()) {
47
+ ;(view.dom as HTMLElement).focus()
48
+ }
43
49
 
44
50
  // For React we have to focus asynchronously. Otherwise wild things happen.
45
51
  // see: https://github.com/ueberdosis/tiptap/issues/1520
@@ -88,8 +88,12 @@ export const insertContentAt: RawCommands['insertContentAt'] =
88
88
  editor,
89
89
  error: e as Error,
90
90
  disableCollaboration: () => {
91
- if (editor.storage.collaboration) {
92
- editor.storage.collaboration.isDisabled = true
91
+ if (
92
+ 'collaboration' in editor.storage &&
93
+ typeof editor.storage.collaboration === 'object' &&
94
+ editor.storage.collaboration
95
+ ) {
96
+ ;(editor.storage.collaboration as any).isDisabled = true
93
97
  }
94
98
  },
95
99
  })
@@ -19,25 +19,26 @@ declare module '@tiptap/core' {
19
19
  */
20
20
  content: Content | Fragment | ProseMirrorNode,
21
21
 
22
- /**
23
- * Whether to emit an update event.
24
- * @default false
25
- */
26
- emitUpdate?: boolean,
27
-
28
- /**
29
- * Options for parsing the content.
30
- * @default {}
31
- */
32
- parseOptions?: ParseOptions,
33
22
  /**
34
23
  * Options for `setContent`.
35
24
  */
36
25
  options?: {
26
+ /**
27
+ * Options for parsing the content.
28
+ * @default {}
29
+ */
30
+ parseOptions?: ParseOptions
31
+
37
32
  /**
38
33
  * Whether to throw an error if the content is invalid.
39
34
  */
40
35
  errorOnInvalidContent?: boolean
36
+
37
+ /**
38
+ * Whether to emit an update event.
39
+ * @default true
40
+ */
41
+ emitUpdate?: boolean
41
42
  },
42
43
  ) => ReturnType
43
44
  }
@@ -45,7 +46,7 @@ declare module '@tiptap/core' {
45
46
  }
46
47
 
47
48
  export const setContent: RawCommands['setContent'] =
48
- (content, emitUpdate = false, parseOptions = {}, options = {}) =>
49
+ (content, { errorOnInvalidContent, emitUpdate = true, parseOptions = {} } = {}) =>
49
50
  ({ editor, tr, dispatch, commands }) => {
50
51
  const { doc } = tr
51
52
 
@@ -53,7 +54,7 @@ export const setContent: RawCommands['setContent'] =
53
54
  // TODO remove this in the next major version
54
55
  if (parseOptions.preserveWhitespace !== 'full') {
55
56
  const document = createDocument(content, editor.schema, parseOptions, {
56
- errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
57
+ errorOnInvalidContent: errorOnInvalidContent ?? editor.options.enableContentCheck,
57
58
  })
58
59
 
59
60
  if (dispatch) {
@@ -68,6 +69,6 @@ export const setContent: RawCommands['setContent'] =
68
69
 
69
70
  return commands.insertContentAt({ from: 0, to: doc.content.size }, content, {
70
71
  parseOptions,
71
- errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
72
+ errorOnInvalidContent: errorOnInvalidContent ?? editor.options.enableContentCheck,
72
73
  })
73
74
  }
@@ -0,0 +1,89 @@
1
+ import { RemoveMarkStep } from '@tiptap/pm/transform'
2
+
3
+ import { Extension } from '../Extension.js'
4
+ import { combineTransactionSteps, getChangedRanges } from '../helpers/index.js'
5
+
6
+ /**
7
+ * This extension allows you to be notified when the user deletes content you are interested in.
8
+ */
9
+ export const Delete = Extension.create({
10
+ name: 'delete',
11
+
12
+ onUpdate({ transaction, appendedTransactions }) {
13
+ const callback = () => {
14
+ if (
15
+ this.editor.options.coreExtensionOptions?.delete?.filterTransaction?.(transaction) ??
16
+ transaction.getMeta('y-sync$')
17
+ ) {
18
+ return
19
+ }
20
+ const nextTransaction = combineTransactionSteps(transaction.before, [transaction, ...appendedTransactions])
21
+ const changes = getChangedRanges(nextTransaction)
22
+
23
+ changes.forEach(change => {
24
+ if (
25
+ nextTransaction.mapping.mapResult(change.oldRange.from).deletedAfter &&
26
+ nextTransaction.mapping.mapResult(change.oldRange.to).deletedBefore
27
+ ) {
28
+ nextTransaction.before.nodesBetween(change.oldRange.from, change.oldRange.to, (node, from) => {
29
+ const to = from + node.nodeSize - 2
30
+ const isFullyWithinRange = change.oldRange.from <= from && to <= change.oldRange.to
31
+
32
+ this.editor.emit('delete', {
33
+ type: 'node',
34
+ node,
35
+ from,
36
+ to,
37
+ newFrom: nextTransaction.mapping.map(from),
38
+ newTo: nextTransaction.mapping.map(to),
39
+ deletedRange: change.oldRange,
40
+ newRange: change.newRange,
41
+ partial: !isFullyWithinRange,
42
+ editor: this.editor,
43
+ transaction,
44
+ combinedTransform: nextTransaction,
45
+ })
46
+ })
47
+ }
48
+ })
49
+
50
+ const mapping = nextTransaction.mapping
51
+ nextTransaction.steps.forEach((step, index) => {
52
+ if (step instanceof RemoveMarkStep) {
53
+ const newStart = mapping.slice(index).map(step.from, -1)
54
+ const newEnd = mapping.slice(index).map(step.to)
55
+ const oldStart = mapping.invert().map(newStart, -1)
56
+ const oldEnd = mapping.invert().map(newEnd)
57
+
58
+ const foundBeforeMark = nextTransaction.doc.nodeAt(newStart - 1)?.marks.some(mark => mark.eq(step.mark))
59
+ const foundAfterMark = nextTransaction.doc.nodeAt(newEnd)?.marks.some(mark => mark.eq(step.mark))
60
+
61
+ this.editor.emit('delete', {
62
+ type: 'mark',
63
+ mark: step.mark,
64
+ from: step.from,
65
+ to: step.to,
66
+ deletedRange: {
67
+ from: oldStart,
68
+ to: oldEnd,
69
+ },
70
+ newRange: {
71
+ from: newStart,
72
+ to: newEnd,
73
+ },
74
+ partial: Boolean(foundAfterMark || foundBeforeMark),
75
+ editor: this.editor,
76
+ transaction,
77
+ combinedTransform: nextTransaction,
78
+ })
79
+ }
80
+ })
81
+ }
82
+
83
+ if (this.editor.options.coreExtensionOptions?.delete?.async ?? true) {
84
+ setTimeout(callback, 0)
85
+ } else {
86
+ callback()
87
+ }
88
+ },
89
+ })
@@ -1,5 +1,6 @@
1
1
  export { ClipboardTextSerializer } from './clipboardTextSerializer.js'
2
2
  export { Commands } from './commands.js'
3
+ export { Delete } from './delete.js'
3
4
  export { Drop } from './drop.js'
4
5
  export { Editable } from './editable.js'
5
6
  export { FocusEvents } from './focusEvents.js'
@@ -109,6 +109,10 @@ export const Keymap = Extension.create({
109
109
  new Plugin({
110
110
  key: new PluginKey('clearDocument'),
111
111
  appendTransaction: (transactions, oldState, newState) => {
112
+ if (transactions.some(tr => tr.getMeta('composition'))) {
113
+ return
114
+ }
115
+
112
116
  const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)
113
117
 
114
118
  const ignoreTr = transactions.some(transaction => transaction.getMeta('preventClearDocument'))
@@ -1,3 +1,6 @@
1
+ import { ExtensionConfig } from '../Extension.js'
2
+ import { MarkConfig } from '../Mark.js'
3
+ import { NodeConfig } from '../Node.js'
1
4
  import { AnyExtension, MaybeThisParameterType, RemoveThis } from '../types.js'
2
5
 
3
6
  /**
@@ -7,17 +10,17 @@ import { AnyExtension, MaybeThisParameterType, RemoveThis } from '../types.js'
7
10
  * @param context The context object that should be passed as `this` into the function
8
11
  * @returns The field value
9
12
  */
10
- export function getExtensionField<T = any>(
11
- extension: AnyExtension,
12
- field: string,
13
+ export function getExtensionField<T = any, E extends AnyExtension = any>(
14
+ extension: E,
15
+ field: keyof ExtensionConfig | keyof MarkConfig | keyof NodeConfig,
13
16
  context?: Omit<MaybeThisParameterType<T>, 'parent'>,
14
17
  ): RemoveThis<T> {
15
- if (extension.config[field] === undefined && extension.parent) {
18
+ if (extension.config[field as keyof typeof extension.config] === undefined && extension.parent) {
16
19
  return getExtensionField(extension.parent, field, context)
17
20
  }
18
21
 
19
- if (typeof extension.config[field] === 'function') {
20
- const value = extension.config[field].bind({
22
+ if (typeof extension.config[field as keyof typeof extension.config] === 'function') {
23
+ const value = (extension.config[field as keyof typeof extension.config] as any).bind({
21
24
  ...context,
22
25
  parent: extension.parent ? getExtensionField(extension.parent, field, context) : null,
23
26
  })
@@ -25,5 +28,5 @@ export function getExtensionField<T = any>(
25
28
  return value
26
29
  }
27
30
 
28
- return extension.config[field]
31
+ return extension.config[field as keyof typeof extension.config] as RemoveThis<T>
29
32
  }
package/src/index.ts CHANGED
@@ -5,7 +5,9 @@ export * as extensions from './extensions/index.js'
5
5
  export * from './helpers/index.js'
6
6
  export * from './InputRule.js'
7
7
  export * from './inputRules/index.js'
8
+ export { createElement, Fragment, createElement as h } from './jsx-runtime.js'
8
9
  export * from './Mark.js'
10
+ export * from './MarkView.js'
9
11
  export * from './Node.js'
10
12
  export * from './NodePos.js'
11
13
  export * from './NodeView.js'
@@ -19,10 +21,4 @@ export * from './utilities/index.js'
19
21
  export interface Commands<ReturnType = any> {}
20
22
 
21
23
  // eslint-disable-next-line
22
- export interface ExtensionConfig<Options = any, Storage = any> {}
23
-
24
- // eslint-disable-next-line
25
- export interface NodeConfig<Options = any, Storage = any> {}
26
-
27
- // eslint-disable-next-line
28
- export interface MarkConfig<Options = any, Storage = any> {}
24
+ export interface Storage {}
@@ -0,0 +1,64 @@
1
+ export type Attributes = Record<string, any>
2
+
3
+ export type DOMOutputSpecElement = 0 | Attributes | DOMOutputSpecArray
4
+ /**
5
+ * Better describes the output of a `renderHTML` function in prosemirror
6
+ * @see https://prosemirror.net/docs/ref/#model.DOMOutputSpec
7
+ */
8
+ export type DOMOutputSpecArray =
9
+ | [string]
10
+ | [string, Attributes]
11
+ | [string, 0]
12
+ | [string, Attributes, 0]
13
+ | [string, Attributes, DOMOutputSpecArray | 0]
14
+ | [string, DOMOutputSpecArray]
15
+
16
+ declare global {
17
+ // eslint-disable-next-line @typescript-eslint/no-namespace
18
+ namespace JSX {
19
+ // @ts-ignore - conflict with React typings
20
+ type Element = [string, ...any[]]
21
+ // @ts-ignore - conflict with React typings
22
+ interface IntrinsicElements {
23
+ // @ts-ignore - conflict with React typings
24
+ [key: string]: any
25
+ }
26
+ }
27
+ }
28
+
29
+ export type JSXRenderer = (
30
+ tag: 'slot' | string | ((props?: Attributes) => DOMOutputSpecArray | DOMOutputSpecElement),
31
+ props?: Attributes,
32
+ ...children: JSXRenderer[]
33
+ ) => DOMOutputSpecArray | DOMOutputSpecElement
34
+
35
+ export function Fragment(props: { children: JSXRenderer[] }) {
36
+ return props.children
37
+ }
38
+
39
+ export const h: JSXRenderer = (tag, attributes) => {
40
+ // Treat the slot tag as the Prosemirror hole to render content into
41
+ if (tag === 'slot') {
42
+ return 0
43
+ }
44
+
45
+ // If the tag is a function, call it with the props
46
+ if (tag instanceof Function) {
47
+ return tag(attributes)
48
+ }
49
+
50
+ const { children, ...rest } = attributes ?? {}
51
+
52
+ if (tag === 'svg') {
53
+ throw new Error('SVG elements are not supported in the JSX syntax, use the array syntax instead')
54
+ }
55
+
56
+ // Otherwise, return the tag, attributes, and children
57
+ return [tag, rest, children]
58
+ }
59
+
60
+ // See
61
+ // https://esbuild.github.io/api/#jsx-import-source
62
+ // https://www.typescriptlang.org/tsconfig/#jsxImportSource
63
+
64
+ export { h as createElement, h as jsx, h as jsxDEV, h as jsxs }