@tiptap/core 3.0.0-beta.2 → 3.0.0-beta.21

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/src/Node.ts CHANGED
@@ -340,8 +340,14 @@ export interface NodeConfig<Options = any, Storage = any>
340
340
  export class Node<Options = any, Storage = any> extends Extendable<Options, Storage, NodeConfig<Options, Storage>> {
341
341
  type = 'node'
342
342
 
343
- static create<O = any, S = any>(config: Partial<NodeConfig<O, S>> = {}) {
344
- return new Node<O, S>(config)
343
+ /**
344
+ * Create a new Node instance
345
+ * @param config - Node configuration object or a function that returns a configuration object
346
+ */
347
+ static create<O = any, S = any>(config: Partial<NodeConfig<O, S>> | (() => Partial<NodeConfig<O, S>>) = {}) {
348
+ // If the config is a function, execute it to get the configuration object
349
+ const resolvedConfig = typeof config === 'function' ? config() : config
350
+ return new Node<O, S>(resolvedConfig)
345
351
  }
346
352
 
347
353
  configure(options?: Partial<Options>) {
@@ -352,7 +358,9 @@ export class Node<Options = any, Storage = any> extends Extendable<Options, Stor
352
358
  ExtendedOptions = Options,
353
359
  ExtendedStorage = Storage,
354
360
  ExtendedConfig = NodeConfig<ExtendedOptions, ExtendedStorage>,
355
- >(extendedConfig?: Partial<ExtendedConfig>) {
356
- return super.extend(extendedConfig) as Node<ExtendedOptions, ExtendedStorage>
361
+ >(extendedConfig?: Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)) {
362
+ // If the extended config is a function, execute it to get the configuration object
363
+ const resolvedConfig = typeof extendedConfig === 'function' ? extendedConfig() : extendedConfig
364
+ return super.extend(resolvedConfig) as Node<ExtendedOptions, ExtendedStorage>
357
365
  }
358
366
  }
package/src/NodePos.ts CHANGED
@@ -136,6 +136,12 @@ export class NodePos {
136
136
  const isNonTextAtom = node.isAtom && !node.isText
137
137
 
138
138
  const targetPos = this.pos + offset + (isNonTextAtom ? 0 : 1)
139
+
140
+ // Check if targetPos is within valid document range
141
+ if (targetPos < 0 || targetPos > this.resolvedPos.doc.nodeSize - 2) {
142
+ return
143
+ }
144
+
139
145
  const $pos = this.resolvedPos.doc.resolve(targetPos)
140
146
 
141
147
  if (!isBlock && $pos.depth <= this.depth) {
package/src/PasteRule.ts CHANGED
@@ -267,7 +267,7 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
267
267
  if (!isDroppedFromProseMirror) {
268
268
  const dragFromOtherEditor = tiptapDragFromOtherEditor
269
269
 
270
- if (dragFromOtherEditor) {
270
+ if (dragFromOtherEditor?.isEditable) {
271
271
  // setTimeout to avoid the wrong content after drop, timeout arg can't be empty or 0
272
272
  setTimeout(() => {
273
273
  const selection = dragFromOtherEditor.state.selection
@@ -30,7 +30,7 @@ export const cut: RawCommands['cut'] =
30
30
 
31
31
  tr.insert(newPos, contentSlice.content)
32
32
 
33
- tr.setSelection(new TextSelection(tr.doc.resolve(newPos - 1)))
33
+ tr.setSelection(new TextSelection(tr.doc.resolve(Math.max(newPos - 1, 0))))
34
34
 
35
35
  return true
36
36
  }
@@ -76,18 +76,10 @@ export const insertContentAt: RawCommands['insertContentAt'] =
76
76
  let content: Fragment | ProseMirrorNode
77
77
  const { selection } = editor.state
78
78
 
79
- try {
80
- content = createNodeFromContent(value, editor.schema, {
81
- parseOptions: {
82
- preserveWhitespace: 'full',
83
- ...options.parseOptions,
84
- },
85
- errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
86
- })
87
- } catch (e) {
79
+ const emitContentError = (error: Error) => {
88
80
  editor.emit('contentError', {
89
81
  editor,
90
- error: e as Error,
82
+ error,
91
83
  disableCollaboration: () => {
92
84
  if (
93
85
  'collaboration' in editor.storage &&
@@ -98,6 +90,33 @@ export const insertContentAt: RawCommands['insertContentAt'] =
98
90
  }
99
91
  },
100
92
  })
93
+ }
94
+
95
+ const parseOptions: ParseOptions = {
96
+ preserveWhitespace: 'full',
97
+ ...options.parseOptions,
98
+ }
99
+
100
+ // If `emitContentError` is enabled, we want to check the content for errors
101
+ // but ignore them (do not remove the invalid content from the document)
102
+ if (!options.errorOnInvalidContent && !editor.options.enableContentCheck && editor.options.emitContentError) {
103
+ try {
104
+ createNodeFromContent(value, editor.schema, {
105
+ parseOptions,
106
+ errorOnInvalidContent: true,
107
+ })
108
+ } catch (e) {
109
+ emitContentError(e as Error)
110
+ }
111
+ }
112
+
113
+ try {
114
+ content = createNodeFromContent(value, editor.schema, {
115
+ parseOptions,
116
+ errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
117
+ })
118
+ } catch (e) {
119
+ emitContentError(e as Error)
101
120
  return false
102
121
  }
103
122
 
@@ -163,8 +182,9 @@ export const insertContentAt: RawCommands['insertContentAt'] =
163
182
 
164
183
  const fromSelectionAtStart = selection.$from.parentOffset === 0
165
184
  const isTextSelection = selection.$from.node().isText || selection.$from.node().isTextblock
185
+ const hasContent = selection.$from.node().content.size > 0
166
186
 
167
- if (fromSelectionAtStart && isTextSelection) {
187
+ if (fromSelectionAtStart && isTextSelection && hasContent) {
168
188
  from = Math.max(0, from - 1)
169
189
  }
170
190
 
package/src/types.ts CHANGED
@@ -354,6 +354,27 @@ export interface EditorOptions {
354
354
  * @default false
355
355
  */
356
356
  enableContentCheck: boolean
357
+ /**
358
+ * If `true`, the editor will emit the `contentError` event if invalid content is
359
+ * encountered but `enableContentCheck` is `false`. This lets you preserve the
360
+ * invalid editor content while still showing a warning or error message to
361
+ * the user.
362
+ *
363
+ * @default false
364
+ */
365
+ emitContentError: boolean
366
+ /**
367
+ * Enable a lazy-loaded Prosemirror DevTools integration.
368
+ *
369
+ * Requires having the `prosemirror-dev-tools` npm package installed.
370
+ * @type boolean
371
+ * @default false
372
+ * @example
373
+ * ```js
374
+ * enableDevTools: true
375
+ * ```
376
+ */
377
+ enableDevTools: boolean
357
378
  /**
358
379
  * Called before the editor is constructed.
359
380
  */
@@ -0,0 +1,30 @@
1
+ import type { NodeType } from '@tiptap/pm/model'
2
+ import { type EditorState, NodeSelection } from '@tiptap/pm/state'
3
+
4
+ export function canInsertNode(state: EditorState, nodeType: NodeType): boolean {
5
+ const { selection } = state
6
+ const { $from } = selection
7
+
8
+ // Special handling for NodeSelection
9
+ if (selection instanceof NodeSelection) {
10
+ const index = $from.index()
11
+ const parent = $from.parent
12
+
13
+ // Can we replace the selected node with the horizontal rule?
14
+ return parent.canReplaceWith(index, index + 1, nodeType)
15
+ }
16
+
17
+ // Default: check if we can insert at the current position
18
+ let depth = $from.depth
19
+
20
+ while (depth >= 0) {
21
+ const index = $from.index(depth)
22
+ const parent = $from.node(depth)
23
+ const match = parent.contentMatchAt(index)
24
+ if (match.matchType(nodeType)) {
25
+ return true
26
+ }
27
+ depth -= 1
28
+ }
29
+ return false
30
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './callOrReturn.js'
2
+ export * from './canInsertNode.js'
2
3
  export * from './createStyleTag.js'
3
4
  export * from './deleteProps.js'
4
5
  export * from './elementFromString.js'