UNPKG

slate-dom

Version:

Tools for building completely customizable richtext editors with React.

1,151 lines (1,109 loc) 104 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('slate')) : typeof define === 'function' && define.amd ? define(['exports', 'slate'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SlateDom = {}, global.Slate)); })(this, (function (exports, slate) { 'use strict'; function unwrapExports (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var arrayLikeToArray = createCommonjsModule(function (module) { function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } module.exports = _arrayLikeToArray, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(arrayLikeToArray); var arrayWithoutHoles = createCommonjsModule(function (module) { function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return arrayLikeToArray(arr); } module.exports = _arrayWithoutHoles, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(arrayWithoutHoles); var iterableToArray = createCommonjsModule(function (module) { function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } module.exports = _iterableToArray, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(iterableToArray); var unsupportedIterableToArray = createCommonjsModule(function (module) { function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen); } module.exports = _unsupportedIterableToArray, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(unsupportedIterableToArray); var nonIterableSpread = createCommonjsModule(function (module) { function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } module.exports = _nonIterableSpread, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(nonIterableSpread); var toConsumableArray = createCommonjsModule(function (module) { function _toConsumableArray(arr) { return arrayWithoutHoles(arr) || iterableToArray(arr) || unsupportedIterableToArray(arr) || nonIterableSpread(); } module.exports = _toConsumableArray, module.exports.__esModule = true, module.exports["default"] = module.exports; }); var _toConsumableArray = unwrapExports(toConsumableArray); var arrayWithHoles = createCommonjsModule(function (module) { function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } module.exports = _arrayWithHoles, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(arrayWithHoles); var iterableToArrayLimit = createCommonjsModule(function (module) { function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = true, o = false; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = true, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } module.exports = _iterableToArrayLimit, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(iterableToArrayLimit); var nonIterableRest = createCommonjsModule(function (module) { function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } module.exports = _nonIterableRest, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(nonIterableRest); var slicedToArray = createCommonjsModule(function (module) { function _slicedToArray(arr, i) { return arrayWithHoles(arr) || iterableToArrayLimit(arr, i) || unsupportedIterableToArray(arr, i) || nonIterableRest(); } module.exports = _slicedToArray, module.exports.__esModule = true, module.exports["default"] = module.exports; }); var _slicedToArray = unwrapExports(slicedToArray); function _createForOfIteratorHelper$2(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray$2(o)) || allowArrayLike) { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray$2(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray$2(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$2(o, minLen); } function _arrayLikeToArray$2(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } /** * Types. */ // COMPAT: This is required to prevent TypeScript aliases from doing some very // weird things for Slate's types with the same name as globals. (2019/11/27) // https://github.com/microsoft/TypeScript/issues/35002 var DOMNode = globalThis.Node; var DOMElement = globalThis.Element; var DOMText = globalThis.Text; var DOMRange = globalThis.Range; var DOMSelection = globalThis.Selection; var DOMStaticRange = globalThis.StaticRange; /** * Returns the host window of a DOM node */ var getDefaultView = function getDefaultView(value) { return value && value.ownerDocument && value.ownerDocument.defaultView || null; }; /** * Check if a DOM node is a comment node. */ var isDOMComment = function isDOMComment(value) { return isDOMNode(value) && value.nodeType === 8; }; /** * Check if a DOM node is an element node. */ var isDOMElement = function isDOMElement(value) { return isDOMNode(value) && value.nodeType === 1; }; /** * Check if a value is a DOM node. */ var isDOMNode = function isDOMNode(value) { var window = getDefaultView(value); return !!window && value instanceof window.Node; }; /** * Check if a value is a DOM selection. */ var isDOMSelection = function isDOMSelection(value) { var window = value && value.anchorNode && getDefaultView(value.anchorNode); return !!window && value instanceof window.Selection; }; /** * Check if a DOM node is an element node. */ var isDOMText = function isDOMText(value) { return isDOMNode(value) && value.nodeType === 3; }; /** * Checks whether a paste event is a plaintext-only event. */ var isPlainTextOnlyPaste = function isPlainTextOnlyPaste(event) { return event.clipboardData && event.clipboardData.getData('text/plain') !== '' && event.clipboardData.types.length === 1; }; /** * Normalize a DOM point so that it always refers to a text node. */ var normalizeDOMPoint = function normalizeDOMPoint(domPoint) { var _domPoint = _slicedToArray(domPoint, 2), node = _domPoint[0], offset = _domPoint[1]; // If it's an element node, its offset refers to the index of its children // including comment nodes, so try to find the right text child node. if (isDOMElement(node) && node.childNodes.length) { var isLast = offset === node.childNodes.length; var index = isLast ? offset - 1 : offset; // If the editable child found is in front of input offset, we instead seek to its end var _getEditableChildAndI = getEditableChildAndIndex(node, index, isLast ? 'backward' : 'forward'); var _getEditableChildAndI2 = _slicedToArray(_getEditableChildAndI, 2); node = _getEditableChildAndI2[0]; index = _getEditableChildAndI2[1]; isLast = index < offset; // If the node has children, traverse until we have a leaf node. Leaf nodes // can be either text nodes, or other void DOM nodes. while (isDOMElement(node) && node.childNodes.length) { var i = isLast ? node.childNodes.length - 1 : 0; node = getEditableChild(node, i, isLast ? 'backward' : 'forward'); } // Determine the new offset inside the text node. offset = isLast && node.textContent != null ? node.textContent.length : 0; } // Return the node and offset. return [node, offset]; }; /** * Determines whether the active element is nested within a shadowRoot */ var hasShadowRoot = function hasShadowRoot(node) { var parent = node && node.parentNode; while (parent) { if (parent.toString() === '[object ShadowRoot]') { return true; } parent = parent.parentNode; } return false; }; /** * Get the nearest editable child and index at `index` in a `parent`, preferring * `direction`. */ var getEditableChildAndIndex = function getEditableChildAndIndex(parent, index, direction) { if (typeof index !== 'number') { throw new Error('Expected index to be a number'); } var childNodes = parent.childNodes; var child = childNodes[index]; var i = index; var triedForward = false; var triedBackward = false; // While the child is a comment node, or an element node with no children, // keep iterating to find a sibling non-void, non-comment node. while (isDOMComment(child) || isDOMElement(child) && child.childNodes.length === 0 || isDOMElement(child) && child.getAttribute('contenteditable') === 'false') { if (triedForward && triedBackward) { break; } if (i >= childNodes.length) { triedForward = true; i = index - 1; direction = 'backward'; continue; } if (i < 0) { triedBackward = true; i = index + 1; direction = 'forward'; continue; } child = childNodes[i]; index = i; i += direction === 'forward' ? 1 : -1; } return [child, index]; }; /** * Get the nearest editable child at `index` in a `parent`, preferring * `direction`. */ var getEditableChild = function getEditableChild(parent, index, direction) { var _getEditableChildAndI3 = getEditableChildAndIndex(parent, index, direction), _getEditableChildAndI4 = _slicedToArray(_getEditableChildAndI3, 1), child = _getEditableChildAndI4[0]; return child; }; /** * Get a plaintext representation of the content of a node, accounting for block * elements which get a newline appended. * * The domNode must be attached to the DOM. */ var getPlainText = function getPlainText(domNode) { var text = ''; if (isDOMText(domNode) && domNode.nodeValue) { return domNode.nodeValue; } if (isDOMElement(domNode)) { for (var _i = 0, _Array$from = Array.from(domNode.childNodes); _i < _Array$from.length; _i++) { var childNode = _Array$from[_i]; text += getPlainText(childNode); } var display = getComputedStyle(domNode).getPropertyValue('display'); if (display === 'block' || display === 'list' || domNode.tagName === 'BR') { text += '\n'; } } return text; }; /** * Get x-slate-fragment attribute from data-slate-fragment */ var catchSlateFragment = /data-slate-fragment="(.+?)"/m; var getSlateFragmentAttribute = function getSlateFragmentAttribute(dataTransfer) { var htmlData = dataTransfer.getData('text/html'); var _ref = htmlData.match(catchSlateFragment) || [], _ref2 = _slicedToArray(_ref, 2), fragment = _ref2[1]; return fragment; }; /** * Get the dom selection from Shadow Root if possible, otherwise from the document */ var getSelection = function getSelection(root) { if (root.getSelection != null) { return root.getSelection(); } return document.getSelection(); }; /** * Check whether a mutation originates from a editable element inside the editor. */ var isTrackedMutation = function isTrackedMutation(editor, mutation, batch) { var target = mutation.target; if (isDOMElement(target) && target.matches('[contentEditable="false"]')) { return false; } var _DOMEditor$getWindow = DOMEditor.getWindow(editor), document = _DOMEditor$getWindow.document; if (containsShadowAware(document, target)) { return DOMEditor.hasDOMNode(editor, target, { editable: true }); } var parentMutation = batch.find(function (_ref3) { var addedNodes = _ref3.addedNodes, removedNodes = _ref3.removedNodes; var _iterator = _createForOfIteratorHelper$2(addedNodes), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var node = _step.value; if (node === target || containsShadowAware(node, target)) { return true; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } var _iterator2 = _createForOfIteratorHelper$2(removedNodes), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _node = _step2.value; if (_node === target || containsShadowAware(_node, target)) { return true; } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } }); if (!parentMutation || parentMutation === mutation) { return false; } // Target add/remove is tracked. Track the mutation if we track the parent mutation. return isTrackedMutation(editor, parentMutation, batch); }; /** * Retrieves the deepest active element in the DOM, considering nested shadow DOMs. */ var getActiveElement = function getActiveElement() { var activeElement = document.activeElement; while ((_activeElement = activeElement) !== null && _activeElement !== void 0 && _activeElement.shadowRoot && (_activeElement$shadow = activeElement.shadowRoot) !== null && _activeElement$shadow !== void 0 && _activeElement$shadow.activeElement) { var _activeElement, _activeElement$shadow, _activeElement2; activeElement = (_activeElement2 = activeElement) === null || _activeElement2 === void 0 || (_activeElement2 = _activeElement2.shadowRoot) === null || _activeElement2 === void 0 ? void 0 : _activeElement2.activeElement; } return activeElement; }; /** * @returns `true` if `otherNode` is before `node` in the document; otherwise, `false`. */ var isBefore = function isBefore(node, otherNode) { return Boolean(node.compareDocumentPosition(otherNode) & DOMNode.DOCUMENT_POSITION_PRECEDING); }; /** * @returns `true` if `otherNode` is after `node` in the document; otherwise, `false`. */ var isAfter = function isAfter(node, otherNode) { return Boolean(node.compareDocumentPosition(otherNode) & DOMNode.DOCUMENT_POSITION_FOLLOWING); }; /** * Shadow DOM-aware version of Element.closest() * Traverses up the DOM tree, crossing shadow DOM boundaries */ var closestShadowAware = function closestShadowAware(element, selector) { if (!element) { return null; } var current = element; while (current) { if (current.matches && current.matches(selector)) { return current; } if (current.parentElement) { current = current.parentElement; } else if (current.parentNode && 'host' in current.parentNode) { current = current.parentNode.host; } else { return null; } } return null; }; /** * Shadow DOM-aware version of Node.contains() * Checks if a node contains another node, crossing shadow DOM boundaries */ var containsShadowAware = function containsShadowAware(parent, child) { if (!parent || !child) { return false; } if (parent.contains(child)) { return true; } var current = child; while (current) { if (current === parent) { return true; } if (current.parentNode) { if ('host' in current.parentNode) { current = current.parentNode.host; } else { current = current.parentNode; } } else { return false; } } return false; }; var _navigator$userAgent$, _navigator$userAgent$2; var IS_IOS = typeof navigator !== 'undefined' && typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; var IS_APPLE = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent); var IS_ANDROID = typeof navigator !== 'undefined' && /Android/.test(navigator.userAgent); var IS_FIREFOX = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent); var IS_WEBKIT = typeof navigator !== 'undefined' && /AppleWebKit(?!.*Chrome)/i.test(navigator.userAgent); // "modern" Edge was released at 79.x var IS_EDGE_LEGACY = typeof navigator !== 'undefined' && /Edge?\/(?:[0-6][0-9]|[0-7][0-8])(?:\.)/i.test(navigator.userAgent); var IS_CHROME = typeof navigator !== 'undefined' && /Chrome/i.test(navigator.userAgent); // Native `beforeInput` events don't work well with react on Chrome 75 // and older, Chrome 76+ can use `beforeInput` though. var IS_CHROME_LEGACY = typeof navigator !== 'undefined' && /Chrome?\/(?:[0-7][0-5]|[0-6][0-9])(?:\.)/i.test(navigator.userAgent); var IS_ANDROID_CHROME_LEGACY = IS_ANDROID && typeof navigator !== 'undefined' && /Chrome?\/(?:[0-5]?\d)(?:\.)/i.test(navigator.userAgent); // Firefox did not support `beforeInput` until `v87`. var IS_FIREFOX_LEGACY = typeof navigator !== 'undefined' && /^(?!.*Seamonkey)(?=.*Firefox\/(?:[0-7][0-9]|[0-8][0-6])(?:\.)).*/i.test(navigator.userAgent); // UC mobile browser var IS_UC_MOBILE = typeof navigator !== 'undefined' && /.*UCBrowser/.test(navigator.userAgent); // Wechat browser (not including mac wechat) var IS_WECHATBROWSER = typeof navigator !== 'undefined' && /.*Wechat/.test(navigator.userAgent) && !/.*MacWechat/.test(navigator.userAgent) && ( // avoid lookbehind (buggy in safari < 16.4) !IS_CHROME || IS_CHROME_LEGACY); // wechat and low chrome is real wechat // Check if DOM is available as React does internally. // https://github.com/facebook/react/blob/master/packages/shared/ExecutionEnvironment.js var CAN_USE_DOM = !!(typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined'); // Check if the browser is Safari and older than 17 typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && /Version\/(\d+)/.test(navigator.userAgent) && ((_navigator$userAgent$ = navigator.userAgent.match(/Version\/(\d+)/)) !== null && _navigator$userAgent$ !== void 0 && _navigator$userAgent$[1] ? parseInt((_navigator$userAgent$2 = navigator.userAgent.match(/Version\/(\d+)/)) === null || _navigator$userAgent$2 === void 0 ? void 0 : _navigator$userAgent$2[1], 10) < 17 : false); // COMPAT: Firefox/Edge Legacy don't support the `beforeinput` event // Chrome Legacy doesn't support `beforeinput` correctly var HAS_BEFORE_INPUT_SUPPORT = (!IS_CHROME_LEGACY || !IS_ANDROID_CHROME_LEGACY) && !IS_EDGE_LEGACY && // globalThis is undefined in older browsers typeof globalThis !== 'undefined' && globalThis.InputEvent && // @ts-ignore The `getTargetRanges` property isn't recognized. typeof globalThis.InputEvent.prototype.getTargetRanges === 'function'; var _typeof_1 = createCommonjsModule(function (module) { function _typeof(o) { "@babel/helpers - typeof"; return (module.exports = _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(o); } module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(_typeof_1); var toPrimitive = createCommonjsModule(function (module) { var _typeof = _typeof_1["default"]; function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } module.exports = _toPrimitive, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(toPrimitive); var toPropertyKey = createCommonjsModule(function (module) { var _typeof = _typeof_1["default"]; function _toPropertyKey(arg) { var key = toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } module.exports = _toPropertyKey, module.exports.__esModule = true, module.exports["default"] = module.exports; }); unwrapExports(toPropertyKey); var createClass = createCommonjsModule(function (module) { function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } module.exports = _createClass, module.exports.__esModule = true, module.exports["default"] = module.exports; }); var _createClass = unwrapExports(createClass); var classCallCheck = createCommonjsModule(function (module) { function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } module.exports = _classCallCheck, module.exports.__esModule = true, module.exports["default"] = module.exports; }); var _classCallCheck = unwrapExports(classCallCheck); var defineProperty = createCommonjsModule(function (module) { function _defineProperty(obj, key, value) { key = toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports; }); var _defineProperty = unwrapExports(defineProperty); /** * An auto-incrementing identifier for keys. */ var n = 0; /** * A class that keeps track of a key string. We use a full class here because we * want to be able to use them as keys in `WeakMap` objects. */ var Key = /*#__PURE__*/_createClass(function Key() { _classCallCheck(this, Key); _defineProperty(this, "id", void 0); this.id = "".concat(n++); }); /** * Two weak maps that allow us rebuild a path given a node. They are populated * at render time such that after a render occurs we can always backtrack. */ var IS_NODE_MAP_DIRTY = new WeakMap(); var NODE_TO_INDEX = new WeakMap(); var NODE_TO_PARENT = new WeakMap(); /** * Weak maps that allow us to go between Slate nodes and DOM nodes. These * are used to resolve DOM event-related logic into Slate actions. */ var EDITOR_TO_WINDOW = new WeakMap(); var EDITOR_TO_ELEMENT = new WeakMap(); var EDITOR_TO_PLACEHOLDER_ELEMENT = new WeakMap(); var ELEMENT_TO_NODE = new WeakMap(); var NODE_TO_ELEMENT = new WeakMap(); var NODE_TO_KEY = new WeakMap(); var EDITOR_TO_KEY_TO_ELEMENT = new WeakMap(); /** * Weak maps for storing editor-related state. */ var IS_READ_ONLY = new WeakMap(); var IS_FOCUSED = new WeakMap(); var IS_COMPOSING = new WeakMap(); var EDITOR_TO_USER_SELECTION = new WeakMap(); /** * Weak map for associating the context `onChange` context with the plugin. */ var EDITOR_TO_ON_CHANGE = new WeakMap(); /** * Weak maps for saving pending state on composition stage. */ var EDITOR_TO_SCHEDULE_FLUSH = new WeakMap(); var EDITOR_TO_PENDING_INSERTION_MARKS = new WeakMap(); var EDITOR_TO_USER_MARKS = new WeakMap(); /** * Android input handling specific weak-maps */ var EDITOR_TO_PENDING_DIFFS = new WeakMap(); var EDITOR_TO_PENDING_ACTION = new WeakMap(); var EDITOR_TO_PENDING_SELECTION = new WeakMap(); var EDITOR_TO_FORCE_RENDER = new WeakMap(); /** * Symbols. */ var PLACEHOLDER_SYMBOL = Symbol('placeholder'); var MARK_PLACEHOLDER_SYMBOL = Symbol('mark-placeholder'); // eslint-disable-next-line no-redeclare var DOMEditor = { androidPendingDiffs: function androidPendingDiffs(editor) { return EDITOR_TO_PENDING_DIFFS.get(editor); }, androidScheduleFlush: function androidScheduleFlush(editor) { var _EDITOR_TO_SCHEDULE_F; (_EDITOR_TO_SCHEDULE_F = EDITOR_TO_SCHEDULE_FLUSH.get(editor)) === null || _EDITOR_TO_SCHEDULE_F === void 0 || _EDITOR_TO_SCHEDULE_F(); }, blur: function blur(editor) { var el = DOMEditor.toDOMNode(editor, editor); var root = DOMEditor.findDocumentOrShadowRoot(editor); IS_FOCUSED.set(editor, false); if (root.activeElement === el) { el.blur(); } }, deselect: function deselect(editor) { var selection = editor.selection; var root = DOMEditor.findDocumentOrShadowRoot(editor); var domSelection = getSelection(root); if (domSelection && domSelection.rangeCount > 0) { domSelection.removeAllRanges(); } if (selection) { slate.Transforms.deselect(editor); } }, findDocumentOrShadowRoot: function findDocumentOrShadowRoot(editor) { var el = DOMEditor.toDOMNode(editor, editor); var root = el.getRootNode(); if (root instanceof Document || root instanceof ShadowRoot) { return root; } return el.ownerDocument; }, findEventRange: function findEventRange(editor, event) { if ('nativeEvent' in event) { event = event.nativeEvent; } var _event = event, x = _event.clientX, y = _event.clientY, target = _event.target; if (x == null || y == null) { throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event)); } var node = DOMEditor.toSlateNode(editor, event.target); var path = DOMEditor.findPath(editor, node); // If the drop target is inside a void node, move it into either the // next or previous node, depending on which side the `x` and `y` // coordinates are closest to. if (slate.Node.isElement(node) && slate.Editor.isVoid(editor, node)) { var rect = target.getBoundingClientRect(); var isPrev = editor.isInline(node) ? x - rect.left < rect.left + rect.width - x : y - rect.top < rect.top + rect.height - y; var edge = slate.Editor.point(editor, path, { edge: isPrev ? 'start' : 'end' }); var point = isPrev ? slate.Editor.before(editor, edge) : slate.Editor.after(editor, edge); if (point) { var _range = slate.Editor.range(editor, point); return _range; } } // Else resolve a range from the caret position where the drop occured. var domRange; var _DOMEditor$getWindow = DOMEditor.getWindow(editor), document = _DOMEditor$getWindow.document; // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25) if (document.caretRangeFromPoint) { domRange = document.caretRangeFromPoint(x, y); } else { var position = document.caretPositionFromPoint(x, y); if (position) { domRange = document.createRange(); domRange.setStart(position.offsetNode, position.offset); domRange.setEnd(position.offsetNode, position.offset); } } if (!domRange) { throw new Error("Cannot resolve a Slate range from a DOM event: ".concat(event)); } // Resolve a Slate range from the DOM range. var range = DOMEditor.toSlateRange(editor, domRange, { exactMatch: false, suppressThrow: false }); return range; }, findKey: function findKey(editor, node) { var key = NODE_TO_KEY.get(node); if (!key) { key = new Key(); NODE_TO_KEY.set(node, key); } return key; }, findPath: function findPath(editor, node) { var path = []; var child = node; while (true) { var parent = NODE_TO_PARENT.get(child); if (parent == null) { if (child === editor) { return path; } else { break; } } var i = NODE_TO_INDEX.get(child); if (i == null) { break; } path.unshift(i); child = parent; } throw new Error("Unable to find the path for Slate node: ".concat(slate.Scrubber.stringify(node))); }, focus: function focus(editor) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { retries: 5 }; // Return if already focused if (IS_FOCUSED.get(editor)) { return; } // Return if no dom node is associated with the editor, which means the editor is not yet mounted // or has been unmounted. This can happen especially, while retrying to focus the editor. if (!EDITOR_TO_ELEMENT.get(editor)) { return; } // Retry setting focus if the editor has pending operations. // The DOM (selection) is unstable while changes are applied. // Retry until retries are exhausted or editor is focused. if (options.retries <= 0) { throw new Error('Could not set focus, editor seems stuck with pending operations'); } if (editor.operations.length > 0) { setTimeout(function () { DOMEditor.focus(editor, { retries: options.retries - 1 }); }, 10); return; } var el = DOMEditor.toDOMNode(editor, editor); var root = DOMEditor.findDocumentOrShadowRoot(editor); if (root.activeElement !== el) { // Ensure that the DOM selection state is set to the editor's selection if (editor.selection && root instanceof Document) { var domSelection = getSelection(root); var domRange = DOMEditor.toDOMRange(editor, editor.selection); domSelection === null || domSelection === void 0 || domSelection.removeAllRanges(); domSelection === null || domSelection === void 0 || domSelection.addRange(domRange); } // Create a new selection in the top of the document if missing if (!editor.selection) { slate.Transforms.select(editor, slate.Editor.start(editor, [])); } // IS_FOCUSED should be set before calling el.focus() to ensure that // FocusedContext is updated to the correct value IS_FOCUSED.set(editor, true); el.focus({ preventScroll: true }); } }, getWindow: function getWindow(editor) { var window = EDITOR_TO_WINDOW.get(editor); if (!window) { throw new Error('Unable to find a host window element for this editor'); } return window; }, hasDOMNode: function hasDOMNode(editor, target) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var _options$editable = options.editable, editable = _options$editable === void 0 ? false : _options$editable; var editorEl = DOMEditor.toDOMNode(editor, editor); var targetEl; // COMPAT: In Firefox, reading `target.nodeType` will throw an error if // target is originating from an internal "restricted" element (e.g. a // stepper arrow on a number input). (2018/05/04) // https://github.com/ianstormtaylor/slate/issues/1819 try { targetEl = isDOMElement(target) ? target : target.parentElement; } catch (err) { if (err instanceof Error && !err.message.includes('Permission denied to access property "nodeType"')) { throw err; } } if (!targetEl) { return false; } return closestShadowAware(targetEl, "[data-slate-editor]") === editorEl && (!editable || targetEl.isContentEditable ? true : typeof targetEl.isContentEditable === 'boolean' && // isContentEditable exists only on HTMLElement, and on other nodes it will be undefined // this is the core logic that lets you know you got the right editor.selection instead of null when editor is contenteditable="false"(readOnly) closestShadowAware(targetEl, '[contenteditable="false"]') === editorEl || !!targetEl.getAttribute('data-slate-zero-width')); }, hasEditableTarget: function hasEditableTarget(editor, target) { return isDOMNode(target) && DOMEditor.hasDOMNode(editor, target, { editable: true }); }, hasRange: function hasRange(editor, range) { var anchor = range.anchor, focus = range.focus; return slate.Editor.hasPath(editor, anchor.path) && slate.Editor.hasPath(editor, focus.path); }, hasSelectableTarget: function hasSelectableTarget(editor, target) { return DOMEditor.hasEditableTarget(editor, target) || DOMEditor.isTargetInsideNonReadonlyVoid(editor, target); }, hasTarget: function hasTarget(editor, target) { return isDOMNode(target) && DOMEditor.hasDOMNode(editor, target); }, insertData: function insertData(editor, data) { editor.insertData(data); }, insertFragmentData: function insertFragmentData(editor, data) { return editor.insertFragmentData(data); }, insertTextData: function insertTextData(editor, data) { return editor.insertTextData(data); }, isComposing: function isComposing(editor) { return !!IS_COMPOSING.get(editor); }, isFocused: function isFocused(editor) { return !!IS_FOCUSED.get(editor); }, isReadOnly: function isReadOnly(editor) { return !!IS_READ_ONLY.get(editor); }, isTargetInsideNonReadonlyVoid: function isTargetInsideNonReadonlyVoid(editor, target) { if (IS_READ_ONLY.get(editor)) return false; if (!DOMEditor.hasTarget(editor, target)) return false; var slateNode = DOMEditor.toSlateNode(editor, target); return slate.Node.isElement(slateNode) && slate.Editor.isVoid(editor, slateNode); }, setFragmentData: function setFragmentData(editor, data, originEvent) { return editor.setFragmentData(data, originEvent); }, toDOMNode: function toDOMNode(editor, node) { var _EDITOR_TO_KEY_TO_ELE; var domNode = node === editor ? EDITOR_TO_ELEMENT.get(editor) : (_EDITOR_TO_KEY_TO_ELE = EDITOR_TO_KEY_TO_ELEMENT.get(editor)) === null || _EDITOR_TO_KEY_TO_ELE === void 0 ? void 0 : _EDITOR_TO_KEY_TO_ELE.get(DOMEditor.findKey(editor, node)); if (!domNode) { throw new Error("Cannot resolve a DOM node from Slate node: ".concat(slate.Scrubber.stringify(node))); } return domNode; }, toDOMPoint: function toDOMPoint(editor, point) { var _Editor$node = slate.Editor.node(editor, point.path), _Editor$node2 = _slicedToArray(_Editor$node, 1), node = _Editor$node2[0]; var el = DOMEditor.toDOMNode(editor, node); var domPoint; // If we're inside a void node, force the offset to 0, otherwise the zero // width spacing character will result in an incorrect offset of 1 if (slate.Editor["void"](editor, { at: point })) { point = { path: point.path, offset: 0 }; } // For each leaf, we need to isolate its content, which means filtering // to its direct text and zero-width spans. (We have to filter out any // other siblings that may have been rendered alongside them.) var selector = "[data-slate-string], [data-slate-zero-width]"; var texts = Array.from(el.querySelectorAll(selector)); var start = 0; for (var i = 0; i < texts.length; i++) { var text = texts[i]; var domNode = text.childNodes[0]; if (domNode == null || domNode.textContent == null) { continue; } var length = domNode.textContent.length; var attr = text.getAttribute('data-slate-length'); var trueLength = attr == null ? length : parseInt(attr, 10); var end = start + trueLength; // Prefer putting the selection inside the mark placeholder to ensure // composed text is displayed with the correct marks. var nextText = texts[i + 1]; if (point.offset === end && nextText !== null && nextText !== void 0 && nextText.hasAttribute('data-slate-mark-placeholder')) { var _nextText$textContent; var domText = nextText.childNodes[0]; domPoint = [ // COMPAT: If we don't explicity set the dom point to be on the actual // dom text element, chrome will put the selection behind the actual dom // text element, causing domRange.getBoundingClientRect() calls on a collapsed // selection to return incorrect zero values (https://bugs.chromium.org/p/chromium/issues/detail?id=435438) // which will cause issues when scrolling to it. domText instanceof DOMText ? domText : nextText, (_nextText$textContent = nextText.textContent) !== null && _nextText$textContent !== void 0 && _nextText$textContent.startsWith("\uFEFF") ? 1 : 0]; break; } if (point.offset <= end) { var offset = Math.min(length, Math.max(0, point.offset - start)); domPoint = [domNode, offset]; break; } start = end; } if (!domPoint) { throw new Error("Cannot resolve a DOM point from Slate point: ".concat(slate.Scrubber.stringify(point))); } return domPoint; }, toDOMRange: function toDOMRange(editor, range) { var anchor = range.anchor, focus = range.focus; var isBackward = slate.Range.isBackward(range); var domAnchor = DOMEditor.toDOMPoint(editor, anchor); var domFocus = slate.Range.isCollapsed(range) ? domAnchor : DOMEditor.toDOMPoint(editor, focus); var window = DOMEditor.getWindow(editor); var domRange = window.document.createRange(); var _ref = isBackward ? domFocus : domAnchor, _ref2 = _slicedToArray(_ref, 2), startNode = _ref2[0], startOffset = _ref2[1]; var _ref3 = isBackward ? domAnchor : domFocus, _ref4 = _slicedToArray(_ref3, 2), endNode = _ref4[0], endOffset = _ref4[1]; // A slate Point at zero-width Leaf always has an offset of 0 but a native DOM selection at // zero-width node has an offset of 1 so we have to check if we are in a zero-width node and // adjust the offset accordingly. var startEl = isDOMElement(startNode) ? startNode : startNode.parentElement; var isStartAtZeroWidth = !!startEl.getAttribute('data-slate-zero-width'); var endEl = isDOMElement(endNode) ? endNode : endNode.parentElement; var isEndAtZeroWidth = !!endEl.getAttribute('data-slate-zero-width'); domRange.setStart(startNode, isStartAtZeroWidth ? 1 : startOffset); domRange.setEnd(endNode, isEndAtZeroWidth ? 1 : endOffset); return domRange; }, toSlateNode: function toSlateNode(editor, domNode) { var domEl = isDOMElement(domNode) ? domNode : domNode.parentElement; if (domEl && !domEl.hasAttribute('data-slate-node')) { domEl = domEl.closest("[data-slate-node]"); } var node = domEl ? ELEMENT_TO_NODE.get(domEl) : null; if (!node) { throw new Error("Cannot resolve a Slate node from DOM node: ".concat(domEl)); } return node; }, toSlatePoint: function toSlatePoint(editor, domPoint, options) { var exactMatch = options.exactMatch, suppressThrow = options.suppressThrow; var _ref5 = exactMatch ? domPoint : normalizeDOMPoint(domPoint), _ref6 = _slicedToArray(_ref5, 2), nearestNode = _ref6[0], nearestOffset = _ref6[1]; var parentNode = nearestNode.parentNode; var searchDirection = options.searchDirection; var textNode = null; var offset = 0; if (parentNode) { var _domNode$textContent, _domNode$textContent2; var editorEl = DOMEditor.toDOMNode(editor, editor); var potentialVoidNode = parentNode.closest('[data-slate-void="true"]'); // Need to ensure that the closest void node is actually a void node // within this editor, and not a void node within some parent editor. This can happen // if this editor is within a void node of another editor ("nested editors", like in // the "Editable Voids" example on the docs site). var voidNode = potentialVoidNode && containsShadowAware(editorEl, potentialVoidNode) ? potentialVoidNode : null; var potentialNonEditableNode = parentNode.closest('[contenteditable="false"]'); var nonEditableNode = potentialNonEditableNode && containsShadowAware(editorEl, potentialNonEditableNode) ? potentialNonEditableNode : null; var leafNode = parentNode.closest('[data-slate-leaf]'); var domNode = null; // Calculate how far into the text node the `nearestNode` is, so that we // can determine what the offset relative to the text node is. if (leafNode) { textNode = leafNode.closest('[data-slate-node="text"]'); if (textNode) { var window = DOMEditor.getWindow(editor); var range = window.document.createRange(); range.setStart(textNode, 0); range.setEnd(nearestNode, nearestOffset); var contents = range.cloneContents(); var removals = [].concat(_toConsumableArray(Array.prototype.slice.call(contents.querySelectorAll('[data-slate-zero-width]'))), _toConsumableArray(Array.prototype.slice.call(contents.querySelectorAll('[contenteditable=false]')))); removals.forEach(function (el) { // COMPAT: While composing at the start of a text node, some keyboards put // the text content inside the zero width space. if (IS_ANDROID && !exactMatch && el.hasAttribute('data-slate-zero-width') && el.textContent.length > 0 && el.textContext !== "\uFEFF") { if (el.textContent.startsWith("\uFEFF")) { el.textContent = el.textContent.slice(1); } return; } el.parentNode.removeChild(el); }); // COMPAT: Edge has a bug where Range.prototype.toString() will // convert \n into \r\n. The bug causes a loop when slate-dom // attempts to reposition its cursor to match the native position. Use // textContent.length instead. // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10291116/ offset = contents.textContent.length; domNode = textNode; } } else if (voidNode) { // For void nodes, the element with the offset key will be a cousin, not an // ancestor, so find it by going down from the nearest void parent and taking the // first one that isn't inside a nested editor. var leafNodes = voidNode.querySelectorAll('[data-slate-leaf]'); for (var index = 0; index < leafNodes.length; index++) { var current = leafNodes[index]; if (DOMEditor.hasDOMNode(editor, current)) { leafNode = current; break; } } // COMPAT: In read-only editors the leaf is not rendered. if (!leafNode) { offset = 1; } else { textNode = leafNode.closest('[data-slate-node="text"]'); domNode = leafNode; offset = domNode.textContent.length; domNode.querySelectorAll('[data-slate-zero-width]').forEach(function (el) { offset -= el.textContent.length; }); } } else if (nonEditableNode) { // Find the edge of the nearest leaf in `searchDirection` var getLeafNodes = function getLeafNodes(node) { return node ? node.querySelectorAll( // Exclude leaf nodes in nested editors '[data-slate-leaf]:not(:scope [data-slate-editor] [data-slate-leaf])') : []; }; var elementNode = nonEditableNode.closest('[data-slate-node="element"]'); if (searchDirection === 'backward' || !searchDirection) { var _leafNodes$findLast; var _leafNodes = [].concat(_toConsumableArray(getLeafNodes(elementNode === null || elementNode === void 0 ? void 0 : elementNode.previousElementSibling)), _toConsumableArray(getLeafNodes(elementNode))); leafNode = (_leafNodes$findLast = _leafNodes.findLast(function (leaf) { return isBefore(nonEditableNode, leaf); })) !== null && _leafNodes$findLast !== void 0 ? _leafNodes$findLast : null; if (leafNode) { searchDirection = 'backward'; } } if (searchDirection === 'forward' || !searchDirection) { var _leafNodes2$find; var _leafNodes2 = [].concat(_toConsumableArray(getLeafNodes(elementNode)), _toConsumableArray(getLeafNodes(elementNode === null || elementNode === void 0 ? void 0 : elementNode.nextElementSibling))); leafNode = (_leafNodes2$find = _leafNodes2.find(function (leaf) { return isAfter(nonEditableNode, leaf); })) !== null && _leafNodes2$find !== void 0 ? _leafNodes2$find : null; if (leafNode) { searchDirection = 'forward'; } } if (leafNode) { textNode = leafNode.closest('[data-slate-node="text"]'); domNode = leafNode; if (searchDirection === 'forward') { offset = 0; } else { offset = domNode.textContent.length; domNode.querySelectorAll('[data-slate-zero-width]').forEach(function (el) { offset -= el.textContent.length; }); } } } if (domNode && offset === domNode.textContent.length && // COMPAT: Android IMEs might remove the zero width space while composing, // and we don't add it for line-breaks. IS_ANDROID && domNode.getAttribute('data-slate-zero-width') === 'z' && (_domNode$textContent = domNode.textContent) !== null && _domNode$textContent !== void 0 && _domNode$textContent.startsWith("\uFEFF") && ( // COMPAT: If the parent node is a Slate zero-width space, editor is // because the text node should have no characters. However, during IME // composition the ASCII characters will be prepended to the zero-width // space, so subtract 1 from the offset to account for the zero-width // space character. parentNode.hasAttribute('data-slate-zero-width') || // COMPAT: In Firefox, `range.cloneContents()` returns an extra trailing '\n' // when the document ends with a new-line character. This results in the offset // length being off by one, so we need to subtract one to account for this. IS_FIREFOX && (_domNode$textContent2 = domNode.textContent) !== null && _domNode$textContent2 !== void 0 && _domNode$textContent2.endsWith('\n\n'))) { offset--; } } if (IS_ANDROID && !textNode && !exactMatch) { var node = parentNode.hasAttribute('data-slate-node') ? parentNode : parentNode.closest('[data-slate-node]'); if (node && DOMEditor.hasDOMNode(editor, node, { editable: true })) { var _slateNode = DOMEditor.toSlateNode(editor, node); var nodePath; try { nodePath = DOMEditor.findPath(editor, _slateNode); } catch (e) { if (suppressThrow) { return null; } throw e; } var _Editor$start = slate.Editor.start(editor, nodePath), _path = _Editor$start.path, _offset = _Editor$start.offset; if (!node.querySelector('[data-slate-leaf]')) {