upfront-editable
Version:
Friendly contenteditable API
264 lines (214 loc) • 8.23 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
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 = _interopRequireDefault(require("jquery"));
var string = _interopRequireWildcard(require("./util/string"));
var nodeType = _interopRequireWildcard(require("./node-type"));
var _config = _interopRequireDefault(require("./config"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* 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 = ".".concat(_config["default"].editableClass);
var hostNode = (0, _jquery["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) {
return Array.from(node.childNodes).every(function (child) {
if (child.nodeType === nodeType.textNode && !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 && 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(elem) {
if (elem.nodeType === nodeType.textNode) return string.trimRight(elem.nodeValue).length;
var lastOffset = 0;
Array.from(elem.childNodes).reverse().every(function (node, index, nodes) {
if (isWhitespaceOnly(node) || isLinebreak(node)) return true;
lastOffset = nodes.length - index;
return false;
});
return lastOffset;
}
function isBeginningOfHost(host, container, offset) {
if (container === host) return isStartOffset(container, offset);
if (isStartOffset(container, offset)) {
// The index of the element simulates a range offset
// right before the element.
var offsetInParent = getNodeIndex(container);
return isBeginningOfHost(host, container.parentNode, offsetInParent);
}
return false;
}
function isEndOfHost(host, container, offset) {
if (container === host) return isEndOffset(container, offset);
if (isEndOffset(container, offset)) {
// The index of the element plus one simulates a range offset
// right after the element.
var offsetInParent = getNodeIndex(container) + 1;
return 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 isTextEndOffset(container, offset);
if (isTextEndOffset(container, offset)) {
// The index of the element plus one simulates a range offset
// right after the element.
var offsetInParent = getNodeIndex(container) + 1;
return 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 >= 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 ? 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;
}
}