Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/many-houses-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiptap/core": minor
---

Add configurable tabindex option to allow customizing the tabindex attribute on the editor element via coreExtensionOptions
74 changes: 74 additions & 0 deletions packages/core/__tests__/tabindex.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Editor } from '@tiptap/core'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { describe, expect, it } from 'vitest'

describe('tabindex extension', () => {
it('should set tabindex="0" on editable editor by default', () => {
const editor = new Editor({
extensions: [Document, Paragraph, Text],
})

expect(editor.view.dom.getAttribute('tabindex')).toBe('0')

editor.destroy()
})

it('should not set tabindex on non-editable editor by default', () => {
const editor = new Editor({
extensions: [Document, Paragraph, Text],
editable: false,
})

expect(editor.view.dom.getAttribute('tabindex')).toBeNull()

editor.destroy()
})

it('should set custom tabindex on editable editor when configured', () => {
const editor = new Editor({
extensions: [Document, Paragraph, Text],
coreExtensionOptions: {
tabindex: {
value: '4',
},
},
})

expect(editor.view.dom.getAttribute('tabindex')).toBe('4')

editor.destroy()
})

it('should set custom tabindex on non-editable editor when configured', () => {
const editor = new Editor({
extensions: [Document, Paragraph, Text],
editable: false,
coreExtensionOptions: {
tabindex: {
value: '-1',
},
},
})

expect(editor.view.dom.getAttribute('tabindex')).toBe('-1')

editor.destroy()
})

it('should set tabindex="0" when value is explicitly undefined', () => {
const editor = new Editor({
extensions: [Document, Paragraph, Text],
coreExtensionOptions: {
tabindex: {
value: undefined,
},
},
})

expect(editor.view.dom.getAttribute('tabindex')).toBe('0')

editor.destroy()
})
})
4 changes: 3 additions & 1 deletion packages/core/src/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,9 @@ export class Editor extends EventEmitter<EditorEvents> {
Commands,
FocusEvents,
Keymap,
Tabindex,
Tabindex.configure({
value: this.options.coreExtensionOptions?.tabindex?.value,
}),
Drop,
Paste,
Delete,
Expand Down
44 changes: 42 additions & 2 deletions packages/core/src/extensions/tabindex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,55 @@ import { Plugin, PluginKey } from '@tiptap/pm/state'

import { Extension } from '../Extension.js'

export const Tabindex = Extension.create({
/**
* Options for the Tabindex extension.
*/
export type TabindexOptions = {
/**
* The value for the `tabindex` attribute on the editor element.
* When undefined, editable editors default to `0` and non-editable editors get no tabindex.
*/
value?: string
}

/**
* The Tabindex extension adds a configurable tabindex attribute to the editor.
*
* By default, the editor gets tabindex="0" when editable. This can be customized
* via coreExtensionOptions to support specific focus ordering requirements in forms
* or to enable focusing on non-editable editors.
*
* @example
* ```ts
* new Editor({
* coreExtensionOptions: {
* tabindex: {
* value: '-1',
* },
* },
* })
* ```
*/
export const Tabindex = Extension.create<TabindexOptions>({
name: 'tabindex',

addOptions() {
return {
value: undefined,
}
},

addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('tabindex'),
props: {
attributes: (): { [name: string]: string } => (this.editor.isEditable ? { tabindex: '0' } : {}),
attributes: (): { [name: string]: string } => {
if (!this.editor.isEditable && this.options.value === undefined) {
return {}
}
return { tabindex: this.options.value ?? '0' }
},
},
}),
]
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,15 @@ export interface EditorOptions {
clipboardTextSerializer?: {
blockSeparator?: string
}
/**
* Options for the `tabindex` core extension.
*/
tabindex?: {
/**
* The value for the `tabindex` attribute on the editor element.
*/
value?: string
}
delete?: {
/**
* Whether the `delete` extension should be called asynchronously to avoid blocking the editor while processing deletions
Expand Down