UNPKG

upfront-editable

Version:
409 lines (331 loc) 12 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _jquery = require('jquery'); var _jquery2 = _interopRequireDefault(_jquery); var _rangy = require('rangy'); var _rangy2 = _interopRequireDefault(_rangy); var _viewport = require('./util/viewport'); var viewport = _interopRequireWildcard(_viewport); var _content = require('./content'); var content = _interopRequireWildcard(_content); var _parser = require('./parser'); var parser = _interopRequireWildcard(_parser); var _string = require('./util/string'); var string = _interopRequireWildcard(_string); var _nodeType = require('./node-type'); var nodeType = _interopRequireWildcard(_nodeType); var _error = require('./util/error'); var _error2 = _interopRequireDefault(_error); var _rangeSaveRestore = require('./range-save-restore'); var rangeSaveRestore = _interopRequireWildcard(_rangeSaveRestore); 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 Cursor module provides a cross-browser abstraction layer for cursor. * * @module core * @submodule cursor */ var Cursor = function () { /** * Class for the Cursor module. * * @class Cursor * @constructor */ function Cursor(editableHost, rangyRange) { (0, _classCallCheck3.default)(this, Cursor); this.setHost(editableHost); this.range = rangyRange; this.isCursor = true; } (0, _createClass3.default)(Cursor, [{ 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 range = _rangy2.default.createRange(); range.selectNodeContents(this.host); var hostCoords = range.nativeRange.getBoundingClientRect(); var cursorCoords = this.range.nativeRange.getBoundingClientRect(); return hostCoords.bottom === cursorCoords.bottom; } }, { key: 'isAtFirstLine', value: function isAtFirstLine() { var range = _rangy2.default.createRange(); range.selectNodeContents(this.host); var hostCoords = range.nativeRange.getBoundingClientRect(); var 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); } _rangy2.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()); } // 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, _error2.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, _error2.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() { (0, _jquery2.default)(this.host).trigger('formatEditable'); } }]); return Cursor; }(); exports.default = Cursor;