upfront-editable
Version:
Friendly contenteditable API
185 lines (152 loc) • 4.95 kB
JavaScript
import * as parser from './parser'
import * as content from './content'
import log from './util/log'
import * as block from './block'
/**
* The Behavior module defines the behavior triggered in response to the Editable.JS
* events (see {{#crossLink "Editable"}}{{/crossLink}}).
* The behavior can be overwritten by a user with Editable.init() or on
* Editable.add() per element.
*
* @module core
* @submodule behavior
*/
export default function createDefaultBehavior (editable) {
const document = editable.win.document
/**
* Factory for the default behavior.
* Provides default behavior of the Editable.JS API.
*
* @static
*/
return {
focus (element) {
// Add a <br> element if the editable is empty to force it to have height
// E.g. Firefox does not render empty block elements and most browsers do
// not render empty inline elements.
if (!parser.isVoid(element)) return
const br = document.createElement('br')
br.setAttribute('data-editable', 'remove')
element.appendChild(br)
},
blur (element) {
content.cleanInternals(element)
},
selection (element, selection) {
log(selection ? 'Default selection behavior' : 'Default selection empty behavior')
},
cursor (element, cursor) {
log(cursor ? 'Default cursor behavior' : 'Default cursor empty behavior')
},
newline (element, cursor) {
if (cursor.isAtTextEnd()) {
const trailingBr = document.createElement('br')
trailingBr.setAttribute('data-editable', 'remove')
cursor.insertBefore(trailingBr)
} else {
cursor.insertBefore(document.createElement('br'))
}
cursor.setVisibleSelection()
},
insert (element, direction, cursor) {
const parent = element.parentNode
const newElement = element.cloneNode(false)
if (newElement.id) newElement.removeAttribute('id')
switch (direction) {
case 'before':
parent.insertBefore(newElement, element)
element.focus()
break
case 'after':
parent.insertBefore(newElement, element.nextSibling)
newElement.focus()
break
}
},
split (element, before, after, cursor) {
const newNode = element.cloneNode(false)
newNode.appendChild(before)
const parent = element.parentNode
parent.insertBefore(newNode, element)
while (element.firstChild) element.removeChild(element.firstChild)
element.appendChild(after)
content.tidyHtml(newNode)
content.tidyHtml(element)
element.focus()
},
merge (element, direction, cursor) {
let container, merger
switch (direction) {
case 'before':
container = block.previous(element)
merger = element
break
case 'after':
container = element
merger = block.next(element)
break
}
if (!(container && merger)) return
cursor = container.childNodes.length > 0
? editable.appendTo(container, merger.innerHTML)
: editable.prependTo(container, merger.innerHTML)
// remove merged node
merger.remove()
cursor.save()
content.tidyHtml(container)
cursor.restore()
cursor.setVisibleSelection()
},
empty (element) {
log('Default empty behavior')
},
switch (element, direction, cursor) {
switch (direction) {
case 'before':
const previous = block.previous(element)
if (previous) {
cursor.moveAtTextEnd(previous)
cursor.setVisibleSelection()
}
break
case 'after':
const next = block.next(element)
if (next) {
cursor.moveAtBeginning(next)
cursor.setVisibleSelection()
}
break
}
},
move (element, selection, direction) {
log('Default move behavior')
},
paste (element, blocks, cursor) {
if (blocks.length === 0) return
cursor.insertBefore(blocks.shift())
if (blocks.length === 0) return cursor.setVisibleSelection()
const parent = element.parentNode
let currentElement = element
blocks.forEach((str) => {
const newElement = element.cloneNode(false)
if (newElement.id) newElement.removeAttribute('id')
const fragment = content.createFragmentFromString(str)
newElement.appendChild(fragment)
parent.insertBefore(newElement, currentElement.nextSibling)
currentElement = newElement
})
// focus last element
editable.createCursorAtEnd(currentElement)
.setVisibleSelection()
},
clipboard (element, action, cursor) {
log('Default clipboard behavior')
},
toggleBold (selection) {
selection.toggleBold()
},
toggleEmphasis (selection) {
selection.toggleEmphasis()
}
}
}