diff --git a/docs/src/demos/Examples/Focus/index.spec.js b/docs/src/demos/Examples/Focus/index.spec.js
index c5ebe63e..e98d7d12 100644
--- a/docs/src/demos/Examples/Focus/index.spec.js
+++ b/docs/src/demos/Examples/Focus/index.spec.js
@@ -6,6 +6,9 @@ context('focus', () => {
describe('focus class', () => {
it('should have class', () => {
cy.get('.ProseMirror').window().then(window => {
+ const { editor } = window
+ editor.focus('start')
+
cy.get('.ProseMirror p:first').should('have.class', 'has-focus')
})
})
diff --git a/docs/src/demos/Examples/Focus/index.vue b/docs/src/demos/Examples/Focus/index.vue
index 4bd6400e..c7c08a5f 100644
--- a/docs/src/demos/Examples/Focus/index.vue
+++ b/docs/src/demos/Examples/Focus/index.vue
@@ -15,7 +15,7 @@ import Italic from '@tiptap/extension-italic'
import Code from '@tiptap/extension-code'
import CodeBlock from '@tiptap/extension-codeblock'
import Heading from '@tiptap/extension-heading'
-// import Focus from '@tiptap/extension-focus'
+import Focus from '@tiptap/extension-focus'
export default {
components: {
@@ -40,12 +40,12 @@ export default {
new Code(),
new CodeBlock(),
new Heading(),
- // new Focus({
- // className: 'has-focus',
- // nested: true,
- // }),
+ new Focus({
+ className: 'has-focus',
+ nested: true,
+ }),
],
- // autoFocus: true,
+ autoFocus: true,
content: `
With the focus extension you can add custom classes to focused nodes. Default options:
@@ -65,14 +65,6 @@ export default {
window.editor = this.editor
},
- watch: {
- editable() {
- this.editor.setOptions({
- editable: this.editable,
- })
- },
- },
-
beforeDestroy() {
this.editor.destroy()
},
diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts
index 887fb581..8e469281 100644
--- a/packages/core/src/Editor.ts
+++ b/packages/core/src/Editor.ts
@@ -22,6 +22,7 @@ import Mark from './Mark'
import EventEmitter from './EventEmitter'
import ComponentRenderer from './ComponentRenderer'
+// commands
import clearContent from './commands/clearContent'
import deleteSelection from './commands/deleteSelection'
import focus from './commands/focus'
@@ -37,6 +38,9 @@ import toggleMark from './commands/toggleMark'
import toggleNode from './commands/toggleNode'
import updateMark from './commands/updateMark'
+// plugins
+import focusPlugin from './plugins/focus'
+
export type Command = (next: Function, editor: Editor) => (...args: any) => any
export interface CommandSpec {
@@ -70,6 +74,8 @@ export class Editor extends EventEmitter {
injectCSS: true,
extensions: [],
}
+ public isFocused = false
+ public isEditable = true
constructor(options: Partial = {}) {
super()
@@ -175,6 +181,7 @@ export class Editor extends EventEmitter {
keymap(baseKeymap),
dropCursor(),
gapCursor(),
+ focusPlugin(this.proxy),
]
}
diff --git a/packages/core/src/plugins/focus.ts b/packages/core/src/plugins/focus.ts
new file mode 100644
index 00000000..07fb7243
--- /dev/null
+++ b/packages/core/src/plugins/focus.ts
@@ -0,0 +1,28 @@
+import { Plugin } from 'prosemirror-state'
+import Editor from '../..'
+
+export default (editor: Editor) => new Plugin({
+ props: {
+ attributes: {
+ tabindex: '0',
+ },
+ handleDOMEvents: {
+ focus: () => {
+ editor.isFocused = true
+
+ const transaction = editor.state.tr.setMeta('focused', true)
+ editor.view.dispatch(transaction)
+
+ return true
+ },
+ blur: () => {
+ editor.isFocused = false
+
+ const transaction = editor.state.tr.setMeta('focused', false)
+ editor.view.dispatch(transaction)
+
+ return true
+ },
+ },
+ },
+})
\ No newline at end of file
diff --git a/packages/extension-focus/index.ts b/packages/extension-focus/index.ts
new file mode 100644
index 00000000..a0a5ac2a
--- /dev/null
+++ b/packages/extension-focus/index.ts
@@ -0,0 +1,50 @@
+import { Extension } from '@tiptap/core'
+import { Plugin } from 'prosemirror-state'
+import { DecorationSet, Decoration } from 'prosemirror-view'
+
+export default class Focus extends Extension {
+
+ name = 'focus'
+
+ defaultOptions() {
+ return {
+ className: 'has-focus',
+ nested: false,
+ }
+ }
+
+ plugins() {
+ return [
+ new Plugin({
+ props: {
+ decorations: ({ doc, plugins, selection }) => {
+ const { isFocused, isEditable } = this.editor
+ const isActive = isEditable && this.options.className
+ const { anchor } = selection
+ const decorations: Decoration[] = []
+
+ if (!isActive || !isFocused) {
+ return
+ }
+
+ doc.descendants((node, pos) => {
+ const hasAnchor = anchor >= pos && anchor <= (pos + node.nodeSize)
+
+ if (hasAnchor && !node.isText) {
+ const decoration = Decoration.node(pos, pos + node.nodeSize, {
+ class: this.options.className,
+ })
+ decorations.push(decoration)
+ }
+
+ return this.options.nested
+ })
+
+ return DecorationSet.create(doc, decorations)
+ },
+ },
+ }),
+ ]
+ }
+
+}
diff --git a/packages/extension-focus/package.json b/packages/extension-focus/package.json
new file mode 100644
index 00000000..dbcb9e4b
--- /dev/null
+++ b/packages/extension-focus/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@tiptap/extension-focus",
+ "version": "1.0.0",
+ "source": "index.ts",
+ "main": "dist/tiptap-extension-focus.js",
+ "umd:main": "dist/tiptap-extension-focus.umd.js",
+ "module": "dist/tiptap-extension-focus.mjs",
+ "unpkg": "dist/tiptap-extension-focus.js",
+ "jsdelivr": "dist/tiptap-extension-focus.js",
+ "files": [
+ "src",
+ "dist"
+ ],
+ "peerDependencies": {
+ "@tiptap/core": "2.x",
+ "prosemirror-state": "^1.3.3"
+ }
+}