UNPKG

upfront-editable

Version:
445 lines (392 loc) 16 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _rangy = _interopRequireDefault(require("rangy")); var viewport = _interopRequireWildcard(require("./util/viewport")); var content = _interopRequireWildcard(require("./content")); var parser = _interopRequireWildcard(require("./parser")); var string = _interopRequireWildcard(require("./util/string")); var _nodeType = require("./node-type"); var _error = _interopRequireDefault(require("./util/error")); var rangeSaveRestore = _interopRequireWildcard(require("./range-save-restore")); var _nodeIterator = _interopRequireDefault(require("./node-iterator")); 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; } // import printRange from './util/print_range' /** * The Cursor module provides a cross-browser abstraction layer for cursor. * * @module core * @submodule cursor */ var Cursor = /*#__PURE__*/function () { /** * Class for the Cursor module. * * @class Cursor * @constructor */ function Cursor(editableHost, rangyRange) { (0, _classCallCheck2["default"])(this, Cursor); this.setHost(editableHost); this.range = rangyRange; this.isCursor = true; } // Get all tags that affect the current selection. Optionally pass a // method to filter the returned elements. // // @param {Function filter(node)} [Optional] Method to filter the returned // DOM Nodes. // @return {Array of DOM Nodes} (0, _createClass2["default"])(Cursor, [{ key: "getTags", value: function getTags(filterFunc) { return content.getTags(this.host, this.range, filterFunc); } // Get the names of all tags that affect the current selection. Optionally // pass a method to filter the returned elements. // // @param {Function filter(node)} [Optional] Method to filter the DOM // Nodes whose names are returned. // @return {Array<String> of tag names} }, { key: "getTagNames", value: function getTagNames(filterFunc) { return content.getTagNames(this.getTags(filterFunc)); } // Get all tags of the specified type that affect the current selection. // // @method getTagsByName // @param {String} tagName. E.g. 'a' to get all links. // @return {Array of DOM Nodes} }, { key: "getTagsByName", value: function getTagsByName(tagName) { return content.getTagsByName(this.host, this.range, tagName); } }, { key: "isAtEnd", value: function isAtEnd() { return parser.isEndOfHost(this.host, this.range.endContainer, this.range.endOffset); } }, { key: "isAtTextEnd", value: function isAtTextEnd() { return parser.isTextEndOfHost(this.host, this.range.endContainer, this.range.endOffset); } }, { key: "isAtLastLine", value: function isAtLastLine() { var hostRange = this.win.document.createRange(); hostRange.selectNodeContents(this.host); var hostCoords = hostRange.getBoundingClientRect(); var cursorCoords; if (this.range.nativeRange.startContainer.nodeType === _nodeType.elementNode) { var container = this.range.nativeRange.startContainer; if (container.children.length - 1 >= this.range.nativeRange.startOffset) { var elem = container.children[this.range.nativeRange.startOffset]; var iterator = new _nodeIterator["default"](elem); var textNode = iterator.getNextTextNode(); if (textNode) { var cursorRange = this.win.document.createRange(); cursorRange.setStart(textNode, 0); cursorRange.collapse(true); cursorCoords = cursorRange.getBoundingClientRect(); } else { cursorCoords = hostCoords; } } else { cursorCoords = hostCoords; } } else { cursorCoords = this.getBoundingClientRect(); } return hostCoords.bottom === cursorCoords.bottom; } }, { key: "isAtFirstLine", value: function isAtFirstLine() { var hostRange = this.win.document.createRange(); hostRange.selectNodeContents(this.host); var hostCoords = hostRange.getBoundingClientRect(); var cursorCoords; if (this.range.nativeRange.startContainer.nodeType === _nodeType.elementNode) { var container = this.range.nativeRange.startContainer; if (container.children.length - 1 >= this.range.nativeRange.startOffset) { var elem = container.children[this.range.nativeRange.startOffset]; var iterator = new _nodeIterator["default"](elem); var textNode = iterator.getPreviousTextNode(); if (textNode) { var cursorRange = this.win.document.createRange(); cursorRange.setStart(textNode, 0); cursorRange.collapse(true); cursorCoords = cursorRange.getBoundingClientRect(); } else { cursorCoords = hostCoords; } } else { cursorCoords = hostCoords; } } else { cursorCoords = this.range.nativeRange.getBoundingClientRect(); } return hostCoords.top === cursorCoords.top; } }, { key: "isAtBeginning", value: function isAtBeginning() { return parser.isBeginningOfHost(this.host, this.range.startContainer, this.range.startOffset); } // Insert content before the cursor // // @param {String, DOM node or document fragment} }, { key: "insertBefore", value: function insertBefore(element) { if (string.isString(element)) element = content.createFragmentFromString(element); if (parser.isDocumentFragmentWithoutChildren(element)) return; element = this.adoptElement(element); var preceedingElement = element; if (element.nodeType === _nodeType.documentFragmentNode) { var lastIndex = element.childNodes.length - 1; preceedingElement = element.childNodes[lastIndex]; } this.range.insertNode(element); this.range.setStartAfter(preceedingElement); this.range.setEndAfter(preceedingElement); } // Insert content after the cursor // // @param {String, DOM node or document fragment} }, { key: "insertAfter", value: function insertAfter(element) { if (string.isString(element)) element = content.createFragmentFromString(element); if (parser.isDocumentFragmentWithoutChildren(element)) return; element = this.adoptElement(element); this.range.insertNode(element); } // Alias for #setVisibleSelection() }, { key: "setSelection", value: function setSelection() { this.setVisibleSelection(); } }, { key: "setVisibleSelection", value: function setVisibleSelection() { if (this.win.document.activeElement !== this.host) { var _viewport$getScrollPo = viewport.getScrollPosition(this.win), x = _viewport$getScrollPo.x, y = _viewport$getScrollPo.y; this.win.scrollTo(x, y); } _rangy["default"].getSelection(this.win).setSingleRange(this.range); } // Take the following example: // (The character '|' represents the cursor position) // // <div contenteditable="true">fo|o</div> // before() will return a document fragment containing a text node 'fo'. // // @returns {Document Fragment} content before the cursor or selection. }, { key: "before", value: function before() { var range = this.range.cloneRange(); range.collapse(true); range.setStartBefore(this.host); return content.cloneRangeContents(range); } }, { key: "textBefore", value: function textBefore() { var range = this.range.cloneRange(); range.collapse(true); range.setStartBefore(this.host); return range.toString(); } // Same as before() but returns a string. }, { key: "beforeHtml", value: function beforeHtml() { return content.getInnerHtmlOfFragment(this.before()); } // Take the following example: // (The character '|' represents the cursor position) // // <div contenteditable="true">fo|o</div> // after() will return a document fragment containing a text node 'o'. // // @returns {Document Fragment} content after the cursor or selection. }, { key: "after", value: function after() { var range = this.range.cloneRange(); range.collapse(false); range.setEndAfter(this.host); return content.cloneRangeContents(range); } }, { key: "textAfter", value: function textAfter() { var range = this.range.cloneRange(); range.collapse(false); range.setEndAfter(this.host); return range.toString(); } // Same as after() but returns a string. }, { key: "afterHtml", value: function afterHtml() { return content.getInnerHtmlOfFragment(this.after()); } }, { key: "getBoundingClientRect", value: function getBoundingClientRect() { return this.range.nativeRange.getBoundingClientRect(); } // Get the BoundingClientRect of the cursor. // The returned values are transformed to be absolute // (relative to the document). }, { key: "getCoordinates", value: function getCoordinates() { var positioning = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'absolute'; var coords = this.range.nativeRange.getBoundingClientRect(); if (positioning === 'fixed') return coords; // translate into absolute positions var _viewport$getScrollPo2 = viewport.getScrollPosition(this.win), x = _viewport$getScrollPo2.x, y = _viewport$getScrollPo2.y; return { top: coords.top + y, bottom: coords.bottom + y, left: coords.left + x, right: coords.right + x, height: coords.height, width: coords.width }; } }, { key: "moveBefore", value: function moveBefore(element) { this.updateHost(element); this.range.setStartBefore(element); this.range.setEndBefore(element); if (this.isSelection) return new Cursor(this.host, this.range); } }, { key: "moveAfter", value: function moveAfter(element) { this.updateHost(element); this.range.setStartAfter(element); this.range.setEndAfter(element); if (this.isSelection) return new Cursor(this.host, this.range); } // Move the cursor to the beginning of the host. }, { key: "moveAtBeginning", value: function moveAtBeginning() { var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.host; this.updateHost(element); this.range.selectNodeContents(element); this.range.collapse(true); if (this.isSelection) return new Cursor(this.host, this.range); } // Move the cursor to the end of the host. }, { key: "moveAtEnd", value: function moveAtEnd() { var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.host; this.updateHost(element); this.range.selectNodeContents(element); this.range.collapse(false); if (this.isSelection) return new Cursor(this.host, this.range); } // Move the cursor after the last visible character of the host. }, { key: "moveAtTextEnd", value: function moveAtTextEnd(element) { return this.moveAtEnd(parser.latestChild(element)); } }, { key: "setHost", value: function setHost(element) { if (element.jquery) element = element[0]; this.host = element; this.win = element === undefined || element === null ? window : element.ownerDocument.defaultView; } }, { key: "updateHost", value: function updateHost(element) { var host = parser.getHost(element); if (!host) (0, _error["default"])('Can not set cursor outside of an editable block'); this.setHost(host); } }, { key: "retainVisibleSelection", value: function retainVisibleSelection(callback) { this.save(); callback(); this.restore(); this.setVisibleSelection(); } }, { key: "save", value: function save() { this.savedRangeInfo = rangeSaveRestore.save(this.range); this.savedRangeInfo.host = this.host; } }, { key: "restore", value: function restore() { if (!this.savedRangeInfo) (0, _error["default"])('Could not restore selection'); this.host = this.savedRangeInfo.host; this.range = rangeSaveRestore.restore(this.host, this.savedRangeInfo); this.savedRangeInfo = undefined; } }, { key: "equals", value: function equals(cursor) { if (!cursor) return false; if (!cursor.host) return false; if (!cursor.host.isEqualNode(this.host)) return false; if (!cursor.range) return false; if (!cursor.range.equals(this.range)) return false; return true; } // Create an element with the correct ownerWindow // (see: http://www.w3.org/DOM/faq.html#ownerdoc) }, { key: "createElement", value: function createElement(tagName) { var attributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var element = this.win.document.createElement(tagName); for (var attributeName in attributes) { var attributeValue = attributes[attributeName]; element.setAttribute(attributeName, attributeValue); } return element; } }, { key: "createTextNode", value: function createTextNode(text) { return this.win.document.createTextNode(text); } // Make sure a node has the correct ownerWindow // (see: https://developer.mozilla.org/en-US/docs/Web/API/Document/importNode) }, { key: "adoptElement", value: function adoptElement(node) { return content.adoptElement(node, this.win.document); } // Currently we call triggerChange manually after format changes. // This is to prevent excessive triggering of the change event during // merge or split operations or other manipulations by scripts. }, { key: "triggerChange", value: function triggerChange() { var event = document.createEvent('HTMLEvents'); event.initEvent('formatEditable', true, false); this.host.dispatchEvent(event); } }], [{ key: "findHost", value: function findHost(elem, selector) { if (!elem.closest) elem = elem.parentNode; return elem.closest(selector); } }]); return Cursor; }(); exports["default"] = Cursor; module.exports = exports.default;