@tiptap/core 3.0.0-next.4 → 3.0.0-next.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.
- package/LICENSE.md +1 -1
- package/dist/index.cjs +352 -238
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1084 -1431
- package/dist/index.d.ts +1084 -1431
- package/dist/index.js +344 -235
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime/jsx-runtime.cjs +56 -0
- package/dist/jsx-runtime/jsx-runtime.cjs.map +1 -0
- package/dist/jsx-runtime/jsx-runtime.d.cts +22 -0
- package/dist/jsx-runtime/jsx-runtime.d.ts +22 -0
- package/dist/jsx-runtime/jsx-runtime.js +26 -0
- package/dist/jsx-runtime/jsx-runtime.js.map +1 -0
- package/jsx-runtime/index.cjs +1 -0
- package/jsx-runtime/index.d.cts +1 -0
- package/jsx-runtime/index.d.ts +1 -0
- package/jsx-runtime/index.js +1 -0
- package/package.json +20 -3
- package/src/Editor.ts +104 -22
- package/src/Extendable.ts +483 -0
- package/src/Extension.ts +5 -490
- package/src/ExtensionManager.ts +55 -10
- package/src/Mark.ts +135 -623
- package/src/MarkView.ts +66 -0
- package/src/Node.ts +325 -829
- package/src/commands/clearContent.ts +9 -4
- package/src/commands/focus.ts +7 -1
- package/src/commands/insertContentAt.ts +6 -2
- package/src/commands/setContent.ts +15 -14
- package/src/extensions/delete.ts +89 -0
- package/src/extensions/index.ts +1 -0
- package/src/extensions/keymap.ts +4 -0
- package/src/helpers/getExtensionField.ts +10 -7
- package/src/index.ts +3 -7
- package/src/jsx-runtime.ts +64 -0
- package/src/types.ts +334 -19
- package/src/utilities/elementFromString.ts +3 -0
- package/src/utilities/index.ts +1 -0
- 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: (
|
|
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 =
|
|
22
|
+
(emitUpdate = true) =>
|
|
18
23
|
({ commands }) => {
|
|
19
|
-
return commands.setContent('', emitUpdate)
|
|
24
|
+
return commands.setContent('', { emitUpdate })
|
|
20
25
|
}
|
package/src/commands/focus.ts
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
92
|
-
editor.storage
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
+
})
|
package/src/extensions/index.ts
CHANGED
|
@@ -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'
|
package/src/extensions/keymap.ts
CHANGED
|
@@ -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:
|
|
12
|
-
field:
|
|
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
|
|
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 }
|