upfront-editable
Version:
Friendly contenteditable API
272 lines (219 loc) • 7.64 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _from = require('babel-runtime/core-js/array/from');
var _from2 = _interopRequireDefault(_from);
exports.getHost = getHost;
exports.getNodeIndex = getNodeIndex;
exports.isVoid = isVoid;
exports.isVoidTextNode = isVoidTextNode;
exports.isWhitespaceOnly = isWhitespaceOnly;
exports.isLinebreak = isLinebreak;
exports.lastOffsetWithContent = lastOffsetWithContent;
exports.isBeginningOfHost = isBeginningOfHost;
exports.isEndOfHost = isEndOfHost;
exports.isStartOffset = isStartOffset;
exports.isEndOffset = isEndOffset;
exports.isTextEndOfHost = isTextEndOfHost;
exports.isTextEndOffset = isTextEndOffset;
exports.isSameNode = isSameNode;
exports.latestChild = latestChild;
exports.isDocumentFragmentWithoutChildren = isDocumentFragmentWithoutChildren;
exports.isInlineElement = isInlineElement;
var _jquery = require('jquery');
var _jquery2 = _interopRequireDefault(_jquery);
var _string = require('./util/string');
var string = _interopRequireWildcard(_string);
var _nodeType = require('./node-type');
var nodeType = _interopRequireWildcard(_nodeType);
var _config = require('./config');
var config = _interopRequireWildcard(_config);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* The parser module provides helper methods to parse html-chunks
* manipulations and helpers for common tasks.
* Provides DOM lookup helpers
*
* @module core
* @submodule parser
*/
/**
* Get the editableJS host block of a node.
*
* @method getHost
* @param {DOM Node}
* @return {DOM Node}
*/
function getHost(node) {
var editableSelector = '.' + config.editableClass;
var hostNode = (0, _jquery2.default)(node).closest(editableSelector);
return hostNode[0];
}
/**
* Get the index of a node so that
* parent.childNodes[ getNodeIndex(node) ] would return the node again.
*
* @method getNodeIndex
* @param {HTMLElement}
*/
function getNodeIndex(node) {
var index = 0;
while ((node = node.previousSibling) !== null) {
index++;
}return index;
}
/**
* Check if node contains text or element nodes
* whitespace counts too!
*
* @method isVoid
* @param {HTMLElement}
*/
function isVoid(node) {
var _this = this;
return (0, _from2.default)(node.childNodes).every(function (child) {
if (child.nodeType === nodeType.textNode && !_this.isVoidTextNode(child)) {
return false;
}
if (child.nodeType === nodeType.elementNode) {
return false;
}
return true;
});
}
/**
* Check if node is a text node and completely empty without any whitespace
*
* @method isVoidTextNode
* @param {HTMLElement}
*/
function isVoidTextNode(node) {
return node.nodeType === nodeType.textNode && !node.nodeValue;
}
/**
* Check if node is a text node and contains nothing but whitespace
*
* @method isWhitespaceOnly
* @param {HTMLElement}
*/
function isWhitespaceOnly(node) {
return node.nodeType === nodeType.textNode && this.lastOffsetWithContent(node) === 0;
}
function isLinebreak(node) {
return node.nodeType === nodeType.elementNode && node.tagName === 'BR';
}
/**
* Returns the last offset where the cursor can be positioned to
* be at the visible end of its container.
* Currently works only for empty text nodes (not empty tags)
*
* @method isWhitespaceOnly
* @param {HTMLElement}
*/
function lastOffsetWithContent(node) {
var _this2 = this;
if (node.nodeType === nodeType.textNode) return string.trimRight(node.nodeValue).length;
var lastOffset = 0;
(0, _from2.default)(node.childNodes).reverse().every(function (node, index, nodes) {
if (_this2.isWhitespaceOnly(node) || _this2.isLinebreak(node)) return true;
lastOffset = nodes.length - index;
return false;
});
return lastOffset;
}
function isBeginningOfHost(host, container, offset) {
if (container === host) return this.isStartOffset(container, offset);
if (this.isStartOffset(container, offset)) {
// The index of the element simulates a range offset
// right before the element.
var offsetInParent = this.getNodeIndex(container);
return this.isBeginningOfHost(host, container.parentNode, offsetInParent);
}
return false;
}
function isEndOfHost(host, container, offset) {
if (container === host) return this.isEndOffset(container, offset);
if (this.isEndOffset(container, offset)) {
// The index of the element plus one simulates a range offset
// right after the element.
var offsetInParent = this.getNodeIndex(container) + 1;
return this.isEndOfHost(host, container.parentNode, offsetInParent);
}
return false;
}
function isStartOffset(container, offset) {
if (container.nodeType === nodeType.textNode) return offset === 0;
if (container.childNodes.length === 0) return true;
return container.childNodes[offset] === container.firstChild;
}
function isEndOffset(container, offset) {
if (container.nodeType === nodeType.textNode) return offset === container.length;
if (container.childNodes.length === 0) return true;
if (offset > 0) return container.childNodes[offset - 1] === container.lastChild;
return false;
}
function isTextEndOfHost(host, container, offset) {
if (container === host) return this.isTextEndOffset(container, offset);
if (this.isTextEndOffset(container, offset)) {
// The index of the element plus one simulates a range offset
// right after the element.
var offsetInParent = this.getNodeIndex(container) + 1;
return this.isTextEndOfHost(host, container.parentNode, offsetInParent);
}
return false;
}
function isTextEndOffset(container, offset) {
if (container.nodeType === nodeType.textNode) {
var text = string.trimRight(container.nodeValue);
return offset >= text.length;
}
if (container.childNodes.length === 0) return true;
return offset >= this.lastOffsetWithContent(container);
}
function isSameNode(target, source) {
var i, len, attr;
if (target.nodeType !== source.nodeType) return false;
if (target.nodeName !== source.nodeName) return false;
for (i = 0, len = target.attributes.length; i < len; i++) {
attr = target.attributes[i];
if (source.getAttribute(attr.name) !== attr.value) return false;
}
return true;
}
/**
* Return the deepest last child of a node.
*
* @method latestChild
* @param {HTMLElement} container The container to iterate on.
* @return {HTMLElement} THe deepest last child in the container.
*/
function latestChild(container) {
return container.lastChild ? this.latestChild(container.lastChild) : container;
}
/**
* Checks if a documentFragment has no children.
* Fragments without children can cause errors if inserted into ranges.
*
* @method isDocumentFragmentWithoutChildren
* @param {HTMLElement} DOM node.
* @return {Boolean}
*/
function isDocumentFragmentWithoutChildren(fragment) {
return fragment && fragment.nodeType === nodeType.documentFragmentNode && fragment.childNodes.length === 0;
}
/**
* Determine if an element behaves like an inline element.
*/
function isInlineElement(window, element) {
var styles = element.currentStyle || window.getComputedStyle(element, '');
var display = styles.display;
switch (display) {
case 'inline':
case 'inline-block':
return true;
default:
return false;
}
}