@tiptap/core 3.10.8 → 3.11.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/core",
3
3
  "description": "headless rich text editor",
4
- "version": "3.10.8",
4
+ "version": "3.11.0",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -52,10 +52,10 @@
52
52
  "jsx-dev-runtime"
53
53
  ],
54
54
  "devDependencies": {
55
- "@tiptap/pm": "^3.10.8"
55
+ "@tiptap/pm": "^3.11.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@tiptap/pm": "^3.10.8"
58
+ "@tiptap/pm": "^3.11.0"
59
59
  },
60
60
  "repository": {
61
61
  "type": "git",
package/src/Editor.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  Keymap,
18
18
  Paste,
19
19
  Tabindex,
20
+ TextDirection,
20
21
  } from './extensions/index.js'
21
22
  import { createDocument } from './helpers/createDocument.js'
22
23
  import { getAttributes } from './helpers/getAttributes.js'
@@ -87,6 +88,7 @@ export class Editor extends EventEmitter<EditorEvents> {
87
88
  extensions: [],
88
89
  autofocus: false,
89
90
  editable: true,
91
+ textDirection: undefined,
90
92
  editorProps: {},
91
93
  parseOptions: {},
92
94
  coreExtensionOptions: {},
@@ -430,6 +432,9 @@ export class Editor extends EventEmitter<EditorEvents> {
430
432
  Drop,
431
433
  Paste,
432
434
  Delete,
435
+ TextDirection.configure({
436
+ direction: this.options.textDirection,
437
+ }),
433
438
  ].filter(ext => {
434
439
  if (typeof this.options.enableCoreExtensions === 'object') {
435
440
  return (
@@ -39,6 +39,7 @@ export * from './setMark.js'
39
39
  export * from './setMeta.js'
40
40
  export * from './setNode.js'
41
41
  export * from './setNodeSelection.js'
42
+ export * from './setTextDirection.js'
42
43
  export * from './setTextSelection.js'
43
44
  export * from './sinkListItem.js'
44
45
  export * from './splitBlock.js'
@@ -50,6 +51,7 @@ export * from './toggleWrap.js'
50
51
  export * from './undoInputRule.js'
51
52
  export * from './unsetAllMarks.js'
52
53
  export * from './unsetMark.js'
54
+ export * from './unsetTextDirection.js'
53
55
  export * from './updateAttributes.js'
54
56
  export * from './wrapIn.js'
55
57
  export * from './wrapInList.js'
@@ -0,0 +1,51 @@
1
+ import type { Range, RawCommands } from '../types.js'
2
+
3
+ declare module '@tiptap/core' {
4
+ interface Commands<ReturnType> {
5
+ setTextDirection: {
6
+ /**
7
+ * Set the text direction for nodes.
8
+ * If no position is provided, it will use the current selection.
9
+ * @param direction The text direction to set ('ltr', 'rtl', or 'auto')
10
+ * @param position Optional position or range to apply the direction to
11
+ * @example editor.commands.setTextDirection('rtl')
12
+ * @example editor.commands.setTextDirection('ltr', { from: 0, to: 10 })
13
+ */
14
+ setTextDirection: (direction: 'ltr' | 'rtl' | 'auto', position?: number | Range) => ReturnType
15
+ }
16
+ }
17
+ }
18
+
19
+ export const setTextDirection: RawCommands['setTextDirection'] =
20
+ (direction, position) =>
21
+ ({ tr, state, dispatch }) => {
22
+ const { selection } = state
23
+ let from: number
24
+ let to: number
25
+
26
+ if (typeof position === 'number') {
27
+ from = position
28
+ to = position
29
+ } else if (position && 'from' in position && 'to' in position) {
30
+ from = position.from
31
+ to = position.to
32
+ } else {
33
+ from = selection.from
34
+ to = selection.to
35
+ }
36
+
37
+ if (dispatch) {
38
+ tr.doc.nodesBetween(from, to, (node, pos) => {
39
+ if (node.isText) {
40
+ return
41
+ }
42
+
43
+ tr.setNodeMarkup(pos, undefined, {
44
+ ...node.attrs,
45
+ dir: direction,
46
+ })
47
+ })
48
+ }
49
+
50
+ return true
51
+ }
@@ -0,0 +1,51 @@
1
+ import type { Range, RawCommands } from '../types.js'
2
+
3
+ declare module '@tiptap/core' {
4
+ interface Commands<ReturnType> {
5
+ unsetTextDirection: {
6
+ /**
7
+ * Remove the text direction attribute from nodes.
8
+ * If no position is provided, it will use the current selection.
9
+ * @param position Optional position or range to remove the direction from
10
+ * @example editor.commands.unsetTextDirection()
11
+ * @example editor.commands.unsetTextDirection({ from: 0, to: 10 })
12
+ */
13
+ unsetTextDirection: (position?: number | Range) => ReturnType
14
+ }
15
+ }
16
+ }
17
+
18
+ export const unsetTextDirection: RawCommands['unsetTextDirection'] =
19
+ position =>
20
+ ({ tr, state, dispatch }) => {
21
+ const { selection } = state
22
+ let from: number
23
+ let to: number
24
+
25
+ if (typeof position === 'number') {
26
+ from = position
27
+ to = position
28
+ } else if (position && 'from' in position && 'to' in position) {
29
+ from = position.from
30
+ to = position.to
31
+ } else {
32
+ from = selection.from
33
+ to = selection.to
34
+ }
35
+
36
+ if (dispatch) {
37
+ tr.doc.nodesBetween(from, to, (node, pos) => {
38
+ if (node.isText) {
39
+ return
40
+ }
41
+
42
+ const newAttrs = { ...node.attrs }
43
+
44
+ delete newAttrs.dir
45
+
46
+ tr.setNodeMarkup(pos, undefined, newAttrs)
47
+ })
48
+ }
49
+
50
+ return true
51
+ }
@@ -7,3 +7,4 @@ export { FocusEvents, focusEventsPluginKey } from './focusEvents.js'
7
7
  export { Keymap } from './keymap.js'
8
8
  export { Paste } from './paste.js'
9
9
  export { Tabindex } from './tabindex.js'
10
+ export { TextDirection } from './textDirection.js'
@@ -0,0 +1,86 @@
1
+ import { Plugin, PluginKey } from '@tiptap/pm/state'
2
+
3
+ import { Extension } from '../Extension.js'
4
+ import { splitExtensions } from '../helpers/splitExtensions.js'
5
+
6
+ export interface TextDirectionOptions {
7
+ direction: 'ltr' | 'rtl' | 'auto' | undefined
8
+ }
9
+
10
+ /**
11
+ * The TextDirection extension adds support for setting text direction (LTR/RTL/auto)
12
+ * on all nodes in the editor.
13
+ *
14
+ * This extension adds a global `dir` attribute to all node types, which can be used
15
+ * to control bidirectional text rendering. The direction can be set globally via
16
+ * editor options or per-node using commands.
17
+ */
18
+ export const TextDirection = Extension.create<TextDirectionOptions>({
19
+ name: 'textDirection',
20
+
21
+ addOptions() {
22
+ return {
23
+ direction: undefined,
24
+ }
25
+ },
26
+
27
+ addGlobalAttributes() {
28
+ // Only add the dir attribute to nodes if text direction is configured
29
+ // This prevents null/undefined values from appearing in JSON exports
30
+ if (!this.options.direction) {
31
+ return []
32
+ }
33
+
34
+ const { nodeExtensions } = splitExtensions(this.extensions)
35
+
36
+ return [
37
+ {
38
+ types: nodeExtensions.filter(extension => extension.name !== 'text').map(extension => extension.name),
39
+ attributes: {
40
+ dir: {
41
+ default: this.options.direction,
42
+ parseHTML: element => {
43
+ const dir = element.getAttribute('dir')
44
+
45
+ if (dir && (dir === 'ltr' || dir === 'rtl' || dir === 'auto')) {
46
+ return dir
47
+ }
48
+
49
+ return this.options.direction
50
+ },
51
+ renderHTML: attributes => {
52
+ if (!attributes.dir) {
53
+ return {}
54
+ }
55
+
56
+ return {
57
+ dir: attributes.dir,
58
+ }
59
+ },
60
+ },
61
+ },
62
+ },
63
+ ]
64
+ },
65
+
66
+ addProseMirrorPlugins() {
67
+ return [
68
+ new Plugin({
69
+ key: new PluginKey('textDirection'),
70
+ props: {
71
+ attributes: (): { [name: string]: string } => {
72
+ const direction = this.options.direction
73
+
74
+ if (!direction) {
75
+ return {}
76
+ }
77
+
78
+ return {
79
+ dir: direction,
80
+ }
81
+ },
82
+ },
83
+ }),
84
+ ]
85
+ },
86
+ })
package/src/types.ts CHANGED
@@ -292,6 +292,14 @@ export interface EditorOptions {
292
292
  * Whether the editor is editable
293
293
  */
294
294
  editable: boolean
295
+ /**
296
+ * The default text direction for all content in the editor.
297
+ * When set to 'ltr' or 'rtl', all nodes will have the corresponding dir attribute.
298
+ * When set to 'auto', the dir attribute will be set based on content detection.
299
+ * When undefined, no dir attribute will be added.
300
+ * @default undefined
301
+ */
302
+ textDirection?: 'ltr' | 'rtl' | 'auto'
295
303
  /**
296
304
  * The editor's props
297
305
  */
@@ -357,7 +365,8 @@ export interface EditorOptions {
357
365
  | 'tabindex'
358
366
  | 'drop'
359
367
  | 'paste'
360
- | 'delete',
368
+ | 'delete'
369
+ | 'textDirection',
361
370
  false
362
371
  >
363
372
  >