add focus extension

This commit is contained in:
Philipp Kühn
2020-08-21 17:32:47 +02:00
parent 0513fd6ce2
commit 71c3927b28
6 changed files with 112 additions and 14 deletions

View File

@@ -6,6 +6,9 @@ context('focus', () => {
describe('focus class', () => { describe('focus class', () => {
it('should have class', () => { it('should have class', () => {
cy.get('.ProseMirror').window().then(window => { cy.get('.ProseMirror').window().then(window => {
const { editor } = window
editor.focus('start')
cy.get('.ProseMirror p:first').should('have.class', 'has-focus') cy.get('.ProseMirror p:first').should('have.class', 'has-focus')
}) })
}) })

View File

@@ -15,7 +15,7 @@ import Italic from '@tiptap/extension-italic'
import Code from '@tiptap/extension-code' import Code from '@tiptap/extension-code'
import CodeBlock from '@tiptap/extension-codeblock' import CodeBlock from '@tiptap/extension-codeblock'
import Heading from '@tiptap/extension-heading' import Heading from '@tiptap/extension-heading'
// import Focus from '@tiptap/extension-focus' import Focus from '@tiptap/extension-focus'
export default { export default {
components: { components: {
@@ -40,12 +40,12 @@ export default {
new Code(), new Code(),
new CodeBlock(), new CodeBlock(),
new Heading(), new Heading(),
// new Focus({ new Focus({
// className: 'has-focus', className: 'has-focus',
// nested: true, nested: true,
// }), }),
], ],
// autoFocus: true, autoFocus: true,
content: ` content: `
<p> <p>
With the focus extension you can add custom classes to focused nodes. Default options: With the focus extension you can add custom classes to focused nodes. Default options:
@@ -65,14 +65,6 @@ export default {
window.editor = this.editor window.editor = this.editor
}, },
watch: {
editable() {
this.editor.setOptions({
editable: this.editable,
})
},
},
beforeDestroy() { beforeDestroy() {
this.editor.destroy() this.editor.destroy()
}, },

View File

@@ -22,6 +22,7 @@ import Mark from './Mark'
import EventEmitter from './EventEmitter' import EventEmitter from './EventEmitter'
import ComponentRenderer from './ComponentRenderer' import ComponentRenderer from './ComponentRenderer'
// commands
import clearContent from './commands/clearContent' import clearContent from './commands/clearContent'
import deleteSelection from './commands/deleteSelection' import deleteSelection from './commands/deleteSelection'
import focus from './commands/focus' import focus from './commands/focus'
@@ -37,6 +38,9 @@ import toggleMark from './commands/toggleMark'
import toggleNode from './commands/toggleNode' import toggleNode from './commands/toggleNode'
import updateMark from './commands/updateMark' import updateMark from './commands/updateMark'
// plugins
import focusPlugin from './plugins/focus'
export type Command = (next: Function, editor: Editor) => (...args: any) => any export type Command = (next: Function, editor: Editor) => (...args: any) => any
export interface CommandSpec { export interface CommandSpec {
@@ -70,6 +74,8 @@ export class Editor extends EventEmitter {
injectCSS: true, injectCSS: true,
extensions: [], extensions: [],
} }
public isFocused = false
public isEditable = true
constructor(options: Partial<EditorOptions> = {}) { constructor(options: Partial<EditorOptions> = {}) {
super() super()
@@ -175,6 +181,7 @@ export class Editor extends EventEmitter {
keymap(baseKeymap), keymap(baseKeymap),
dropCursor(), dropCursor(),
gapCursor(), gapCursor(),
focusPlugin(this.proxy),
] ]
} }

View File

@@ -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
},
},
},
})

View File

@@ -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)
},
},
}),
]
}
}

View File

@@ -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"
}
}