slate-react
Version:
Tools for building completely customizable richtext editors with React.
4,730 lines • 199 kB
JavaScript
'use strict';
var getDirection = require('direction');
var debounce = require('lodash/debounce');
var throttle = require('lodash/throttle');
var React = require('react');
var scrollIntoView = require('scroll-into-view-if-needed');
var slate = require('slate');
var slateDom = require('slate-dom');
var ReactDOM = require('react-dom');
var resizeObserver = require('@juggle/resize-observer');
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 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 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 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 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);
var objectWithoutPropertiesLoose = createCommonjsModule(function (module) {
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
module.exports = _objectWithoutPropertiesLoose, module.exports.__esModule = true, module.exports["default"] = module.exports;
});
unwrapExports(objectWithoutPropertiesLoose);
var objectWithoutProperties = createCommonjsModule(function (module) {
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
module.exports = _objectWithoutProperties, module.exports.__esModule = true, module.exports["default"] = module.exports;
});
var _objectWithoutProperties = unwrapExports(objectWithoutProperties);
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 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);
/**
* A React context for sharing the editor object.
*/
var EditorContext = /*#__PURE__*/React.createContext(null);
/**
* Get the current editor object from the React context.
*/
var useSlateStatic = function useSlateStatic() {
var editor = React.useContext(EditorContext);
if (!editor) {
throw new Error("The `useSlateStatic` hook must be used inside the <Slate> component's context.");
}
return editor;
};
// eslint-disable-next-line no-redeclare
var ReactEditor = slateDom.DOMEditor;
function ownKeys$8(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$8(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$8(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$8(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
// https://github.com/facebook/draft-js/blob/main/src/component/handlers/composition/DraftEditorCompositionHandler.js#L41
// When using keyboard English association function, conpositionEnd triggered too fast, resulting in after `insertText` still maintain association state.
var RESOLVE_DELAY = 25;
// Time with no user interaction before the current user action is considered as done.
var FLUSH_DELAY = 200;
// Time with no composition input after which a composition that never ended is
// treated as stale. Long enough that pausing mid-word does not cut a live
// composition short.
var COMPOSITION_IDLE_TIMEOUT = 5000;
// How often a deferred flush re-checks whether the composition is still live.
// Only bounds how quickly the value catches up with a composition that ended
// without firing `compositionend`; one that ends normally flushes immediately.
var COMPOSITION_RECHECK_DELAY = 50;
// Replace with `const debug = console.log` to debug
var debug = function debug() {};
// Type guard to check if a value is a DataTransfer
var isDataTransfer = function isDataTransfer(value) {
return (value === null || value === void 0 ? void 0 : value.constructor.name) === 'DataTransfer';
};
function createAndroidInputManager(_ref) {
var editor = _ref.editor,
scheduleOnDOMSelectionChange = _ref.scheduleOnDOMSelectionChange,
onDOMSelectionChange = _ref.onDOMSelectionChange;
var flushing = false;
var compositionEndTimeoutId = null;
var flushTimeoutId = null;
var actionTimeoutId = null;
var lastCompositionActivity = 0;
var compositionCleared = false;
var idCounter = 0;
var insertPositionHint = false;
var applyPendingSelection = function applyPendingSelection() {
var pendingSelection = slateDom.EDITOR_TO_PENDING_SELECTION.get(editor);
slateDom.EDITOR_TO_PENDING_SELECTION["delete"](editor);
if (pendingSelection) {
var selection = editor.selection;
var normalized = slateDom.normalizeRange(editor, pendingSelection);
if (normalized && (!selection || !slate.Range.equals(normalized, selection))) {
slate.Transforms.select(editor, normalized);
}
}
};
var performAction = function performAction() {
var action = slateDom.EDITOR_TO_PENDING_ACTION.get(editor);
slateDom.EDITOR_TO_PENDING_ACTION["delete"](editor);
if (!action) {
return;
}
if (action.at) {
var target = slate.Location.isPoint(action.at) ? slateDom.normalizePoint(editor, action.at) : slateDom.normalizeRange(editor, action.at);
if (!target) {
return;
}
var _targetRange = slate.Editor.range(editor, target);
if (!editor.selection || !slate.Range.equals(editor.selection, _targetRange)) {
slate.Transforms.select(editor, target);
}
}
action.run();
};
// `compositionend` is not fired reliably in every browser, which Slate
// already works around on keydown. Deferring flushes on a composition that
// never ends would strand the typed text outside the value, so a composition
// that has gone quiet, or that has lost focus, no longer holds flushes back.
var isCompositionLive = function isCompositionLive() {
if (!slateDom.IS_COMPOSING.get(editor) || compositionCleared) {
return false;
}
try {
var editable = ReactEditor.toDOMNode(editor, editor);
var activeElement = ReactEditor.getWindow(editor).document.activeElement;
if (activeElement !== editable && !editable.contains(activeElement)) {
return false;
}
} catch (_unused) {
// Fall back to the idle check below if the editable can't be resolved.
}
return Date.now() - lastCompositionActivity < COMPOSITION_IDLE_TIMEOUT;
};
// A leaf that Slate models as empty renders as a zero-width string, which on
// Android is a `<br>` rather than a text node. When the IME composes the
// first character into such a leaf, the browser creates a text node for the
// composition. Applying the pending diffs re-renders that leaf as a text
// leaf, which unmounts the zero-width span and takes the text node the IME is
// composing in with it, silently cancelling the composition.
var hasPendingDiffsInEmptyLeaf = function hasPendingDiffsInEmptyLeaf() {
var _EDITOR_TO_PENDING_DI;
return !!((_EDITOR_TO_PENDING_DI = slateDom.EDITOR_TO_PENDING_DIFFS.get(editor)) !== null && _EDITOR_TO_PENDING_DI !== void 0 && _EDITOR_TO_PENDING_DI.some(function (_ref2) {
var path = _ref2.path;
try {
return slate.Node.leaf(editor, path).text.length === 0;
} catch (_unused2) {
// The path may no longer resolve if the editor changed underneath us.
return false;
}
}));
};
var flush = function flush() {
if (flushTimeoutId) {
clearTimeout(flushTimeoutId);
flushTimeoutId = null;
}
if (actionTimeoutId) {
clearTimeout(actionTimeoutId);
actionTimeoutId = null;
}
// Defer flushing until the composition ends, so that the re-render that
// would replace the composing text node cannot happen mid-composition.
// Composing into a leaf that already has text is unaffected: applying the
// diff there only updates `textContent`, so the value still updates on
// every `compositionupdate`.
if (isCompositionLive() && hasPendingDiffsInEmptyLeaf()) {
flushTimeoutId = setTimeout(flush, COMPOSITION_RECHECK_DELAY);
return;
}
if (!hasPendingDiffs() && !hasPendingAction()) {
applyPendingSelection();
return;
}
if (!flushing) {
flushing = true;
setTimeout(function () {
return flushing = false;
});
}
if (hasPendingAction()) {
flushing = 'action';
}
var selectionRef = editor.selection && slate.Editor.rangeRef(editor, editor.selection, {
affinity: 'forward'
});
slateDom.EDITOR_TO_USER_MARKS.set(editor, editor.marks);
debug('flush', slateDom.EDITOR_TO_PENDING_ACTION.get(editor), slateDom.EDITOR_TO_PENDING_DIFFS.get(editor));
var scheduleSelectionChange = hasPendingDiffs();
var diff;
while (diff = (_EDITOR_TO_PENDING_DI2 = slateDom.EDITOR_TO_PENDING_DIFFS.get(editor)) === null || _EDITOR_TO_PENDING_DI2 === void 0 ? void 0 : _EDITOR_TO_PENDING_DI2[0]) {
var _EDITOR_TO_PENDING_DI2, _EDITOR_TO_PENDING_DI3;
var pendingMarks = slateDom.EDITOR_TO_PENDING_INSERTION_MARKS.get(editor);
if (pendingMarks !== undefined) {
slateDom.EDITOR_TO_PENDING_INSERTION_MARKS["delete"](editor);
editor.marks = pendingMarks;
}
if (pendingMarks && insertPositionHint === false) {
insertPositionHint = null;
}
var range = slateDom.targetRange(diff);
if (!editor.selection || !slate.Range.equals(editor.selection, range)) {
slate.Transforms.select(editor, range);
}
if (diff.diff.text) {
slate.Editor.insertText(editor, diff.diff.text);
} else {
slate.Editor.deleteFragment(editor);
}
// Remove diff only after we have applied it to account for it when transforming
// pending ranges.
slateDom.EDITOR_TO_PENDING_DIFFS.set(editor, (_EDITOR_TO_PENDING_DI3 = slateDom.EDITOR_TO_PENDING_DIFFS.get(editor)) === null || _EDITOR_TO_PENDING_DI3 === void 0 ? void 0 : _EDITOR_TO_PENDING_DI3.filter(function (_ref3) {
var id = _ref3.id;
return id !== diff.id;
}));
if (!slateDom.verifyDiffState(editor, diff)) {
scheduleSelectionChange = false;
slateDom.EDITOR_TO_PENDING_ACTION["delete"](editor);
slateDom.EDITOR_TO_USER_MARKS["delete"](editor);
flushing = 'action';
// Ensure we don't restore the pending user (dom) selection
// since the document and dom state do not match.
slateDom.EDITOR_TO_PENDING_SELECTION["delete"](editor);
scheduleOnDOMSelectionChange.cancel();
onDOMSelectionChange.cancel();
selectionRef === null || selectionRef === void 0 || selectionRef.unref();
}
}
var selection = selectionRef === null || selectionRef === void 0 ? void 0 : selectionRef.unref();
if (selection && !slateDom.EDITOR_TO_PENDING_SELECTION.get(editor) && (!editor.selection || !slate.Range.equals(selection, editor.selection))) {
slate.Transforms.select(editor, selection);
}
if (hasPendingAction()) {
performAction();
return;
}
// COMPAT: The selectionChange event is fired after the action is performed,
// so we have to manually schedule it to ensure we don't 'throw away' the selection
// while rendering if we have pending changes.
if (scheduleSelectionChange) {
scheduleOnDOMSelectionChange();
}
scheduleOnDOMSelectionChange.flush();
onDOMSelectionChange.flush();
applyPendingSelection();
var userMarks = slateDom.EDITOR_TO_USER_MARKS.get(editor);
slateDom.EDITOR_TO_USER_MARKS["delete"](editor);
if (userMarks !== undefined) {
editor.marks = userMarks;
editor.onChange();
}
};
var handleCompositionEnd = function handleCompositionEnd(_event) {
if (compositionEndTimeoutId) {
clearTimeout(compositionEndTimeoutId);
}
// Diffs deferred by `flush` have to be applied synchronously here. IMEs
// that compose one syllable at a time (Hangul, kana) start the next
// composition in the same tick as this event, so an asynchronous render
// would replace the text node that composition has already started in.
if (hasPendingDiffsInEmptyLeaf() && !flushing) {
ReactDOM.flushSync(function () {
slateDom.IS_COMPOSING.set(editor, false);
flush();
});
updatePlaceholderVisibility();
return;
}
compositionEndTimeoutId = setTimeout(function () {
slateDom.IS_COMPOSING.set(editor, false);
flush();
if (compositionCleared) {
var _EDITOR_TO_FORCE_REND;
// A cancelled composition can take the empty leaf's text node with
// it. A forced render lets RestoreDOM put the leaf's DOM back, so the
// next composition starts from a clean state instead of a bare <br>.
(_EDITOR_TO_FORCE_REND = slateDom.EDITOR_TO_FORCE_RENDER.get(editor)) === null || _EDITOR_TO_FORCE_REND === void 0 || _EDITOR_TO_FORCE_REND();
}
updatePlaceholderVisibility();
}, RESOLVE_DELAY);
};
var handleCompositionStart = function handleCompositionStart(_event) {
slateDom.IS_COMPOSING.set(editor, true);
lastCompositionActivity = Date.now();
compositionCleared = false;
if (compositionEndTimeoutId) {
clearTimeout(compositionEndTimeoutId);
compositionEndTimeoutId = null;
}
};
var updatePlaceholderVisibility = function updatePlaceholderVisibility() {
var forceHide = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var placeholderElement = slateDom.EDITOR_TO_PLACEHOLDER_ELEMENT.get(editor);
if (!placeholderElement) {
return;
}
if (hasPendingDiffs() || forceHide) {
placeholderElement.style.display = 'none';
return;
}
placeholderElement.style.removeProperty('display');
};
var storeDiff = function storeDiff(path, diff) {
var _EDITOR_TO_PENDING_DI4;
var pendingDiffs = (_EDITOR_TO_PENDING_DI4 = slateDom.EDITOR_TO_PENDING_DIFFS.get(editor)) !== null && _EDITOR_TO_PENDING_DI4 !== void 0 ? _EDITOR_TO_PENDING_DI4 : [];
slateDom.EDITOR_TO_PENDING_DIFFS.set(editor, pendingDiffs);
var target = slate.Node.leaf(editor, path);
var idx = pendingDiffs.findIndex(function (change) {
return slate.Path.equals(change.path, path);
});
if (idx < 0) {
var normalized = slateDom.normalizeStringDiff(target.text, diff);
if (normalized) {
pendingDiffs.push({
path: path,
diff: diff,
id: idCounter++
});
}
updatePlaceholderVisibility();
return;
}
var merged = slateDom.mergeStringDiffs(target.text, pendingDiffs[idx].diff, diff);
if (!merged) {
pendingDiffs.splice(idx, 1);
updatePlaceholderVisibility();
return;
}
pendingDiffs[idx] = _objectSpread$8(_objectSpread$8({}, pendingDiffs[idx]), {}, {
diff: merged
});
};
var scheduleAction = function scheduleAction(run) {
var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
at = _ref4.at;
insertPositionHint = false;
slateDom.EDITOR_TO_PENDING_SELECTION["delete"](editor);
scheduleOnDOMSelectionChange.cancel();
onDOMSelectionChange.cancel();
if (hasPendingAction()) {
flush();
}
slateDom.EDITOR_TO_PENDING_ACTION.set(editor, {
at: at,
run: run
});
// COMPAT: When deleting before a non-contenteditable element chrome only fires a beforeinput,
// (no input) and doesn't perform any dom mutations. Without a flush timeout we would never flush
// in this case and thus never actually perform the action.
actionTimeoutId = setTimeout(flush);
};
var handleDOMBeforeInput = function handleDOMBeforeInput(event) {
var _targetRange2;
if (flushTimeoutId) {
clearTimeout(flushTimeoutId);
flushTimeoutId = null;
}
if (slateDom.IS_NODE_MAP_DIRTY.get(editor)) {
return;
}
var type = event.inputType;
if (type === 'insertCompositionText' || type === 'deleteCompositionText') {
lastCompositionActivity = Date.now();
if (event.data) {
compositionCleared = false;
} else {
// The IME threw away what it was composing. The deferred text never
// reached the value, so the value is already in the desired state -
// drop the deferral instead of applying and re-deleting it, which
// would churn the DOM mid-composition and can make Android close the
// keyboard.
compositionCleared = true;
var deferredDiffs = slateDom.EDITOR_TO_PENDING_DIFFS.get(editor);
if (deferredDiffs !== null && deferredDiffs !== void 0 && deferredDiffs.length) {
var allInEmptyLeaves = deferredDiffs.every(function (_ref5) {
var path = _ref5.path;
try {
return slate.Node.leaf(editor, path).text.length === 0;
} catch (_unused3) {
return false;
}
});
if (allInEmptyLeaves) {
slateDom.EDITOR_TO_PENDING_DIFFS.set(editor, []);
slateDom.EDITOR_TO_PENDING_SELECTION["delete"](editor);
}
}
}
}
var targetRange = null;
var data = event.dataTransfer || event.data || undefined;
if (insertPositionHint !== false && type !== 'insertText' && type !== 'insertCompositionText') {
insertPositionHint = false;
}
var _event$getTargetRange = event.getTargetRanges(),
_event$getTargetRange2 = _slicedToArray(_event$getTargetRange, 1),
nativeTargetRange = _event$getTargetRange2[0];
if (nativeTargetRange) {
targetRange = ReactEditor.toSlateRange(editor, nativeTargetRange, {
exactMatch: false,
suppressThrow: true
});
}
// COMPAT: SelectionChange event is fired after the action is performed, so we
// have to manually get the selection here to ensure it's up-to-date.
var window = ReactEditor.getWindow(editor);
var domSelection = window.getSelection();
if (!targetRange && domSelection) {
nativeTargetRange = domSelection;
targetRange = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false,
suppressThrow: true
});
}
targetRange = (_targetRange2 = targetRange) !== null && _targetRange2 !== void 0 ? _targetRange2 : editor.selection;
if (!targetRange) {
return;
}
// By default, the input manager tries to store text diffs so that we can
// defer flushing them at a later point in time. We don't want to flush
// for every input event as this can be expensive. However, there are some
// scenarios where we cannot safely store the text diff and must instead
// schedule an action to let Slate normalize the editor state.
var canStoreDiff = true;
if (type.startsWith('delete')) {
var direction = type.endsWith('Backward') ? 'backward' : 'forward';
var _Range$edges = slate.Range.edges(targetRange),
_Range$edges2 = _slicedToArray(_Range$edges, 2),
start = _Range$edges2[0],
end = _Range$edges2[1];
var _Editor$leaf = slate.Editor.leaf(editor, start.path),
_Editor$leaf2 = _slicedToArray(_Editor$leaf, 2),
leaf = _Editor$leaf2[0],
path = _Editor$leaf2[1];
if (slate.Range.isExpanded(targetRange)) {
if (leaf.text.length === start.offset && end.offset === 0) {
var next = slate.Editor.next(editor, {
at: start.path,
match: slate.Node.isText
});
if (next && slate.Path.equals(next[1], end.path)) {
// when deleting a linebreak, targetRange will span across the break (ie start in the node before and end in the node after)
// if the node before is empty, this will look like a hanging range and get unhung later--which will take the break we want to remove out of the range
// so to avoid this we collapse the target range to default to single character deletion
if (direction === 'backward') {
targetRange = {
anchor: end,
focus: end
};
start = end;
var _next = _slicedToArray(next, 2);
leaf = _next[0];
path = _next[1];
} else {
targetRange = {
anchor: start,
focus: start
};
end = start;
}
}
}
}
var diff = {
text: '',
start: start.offset,
end: end.offset
};
var pendingDiffs = slateDom.EDITOR_TO_PENDING_DIFFS.get(editor);
var relevantPendingDiffs = pendingDiffs === null || pendingDiffs === void 0 ? void 0 : pendingDiffs.find(function (change) {
return slate.Path.equals(change.path, path);
});
var diffs = relevantPendingDiffs ? [relevantPendingDiffs.diff, diff] : [diff];
var text = slateDom.applyStringDiff.apply(void 0, [leaf.text].concat(diffs));
if (text.length === 0) {
// Text leaf will be removed, so we need to schedule an
// action to remove it so that Slate can normalize instead
// of storing as a diff
canStoreDiff = false;
}
if (slate.Range.isExpanded(targetRange)) {
if (canStoreDiff && slate.Path.equals(targetRange.anchor.path, targetRange.focus.path)) {
var point = {
path: targetRange.anchor.path,
offset: start.offset
};
var range = slate.Editor.range(editor, point, point);
handleUserSelect(range);
return storeDiff(targetRange.anchor.path, {
text: '',
end: end.offset,
start: start.offset
});
}
return scheduleAction(function () {
return slate.Editor.deleteFragment(editor, {
direction: direction
});
}, {
at: targetRange
});
}
}
switch (type) {
case 'deleteByComposition':
case 'deleteByCut':
case 'deleteByDrag':
{
return scheduleAction(function () {
return slate.Editor.deleteFragment(editor);
}, {
at: targetRange
});
}
case 'deleteContent':
case 'deleteContentForward':
{
var _targetRange3 = targetRange,
anchor = _targetRange3.anchor;
if (canStoreDiff && slate.Range.isCollapsed(targetRange)) {
var targetNode = slate.Node.leaf(editor, anchor.path);
if (anchor.offset < targetNode.text.length) {
return storeDiff(anchor.path, {
text: '',
start: anchor.offset,
end: anchor.offset + 1
});
}
}
return scheduleAction(function () {
return slate.Editor.deleteForward(editor);
}, {
at: targetRange
});
}
case 'deleteContentBackward':
{
var _nativeTargetRange;
var _targetRange4 = targetRange,
_anchor = _targetRange4.anchor;
// If we have a mismatch between the native and slate selection being collapsed
// we are most likely deleting a zero-width placeholder and thus should perform it
// as an action to ensure correct behavior (mostly happens with mark placeholders)
var nativeCollapsed = slateDom.isDOMSelection(nativeTargetRange) ? nativeTargetRange.isCollapsed : !!((_nativeTargetRange = nativeTargetRange) !== null && _nativeTargetRange !== void 0 && _nativeTargetRange.collapsed);
if (canStoreDiff && nativeCollapsed && slate.Range.isCollapsed(targetRange) && _anchor.offset > 0) {
return storeDiff(_anchor.path, {
text: '',
start: _anchor.offset - 1,
end: _anchor.offset
});
}
return scheduleAction(function () {
return slate.Editor.deleteBackward(editor);
}, {
at: targetRange
});
}
case 'deleteEntireSoftLine':
{
return scheduleAction(function () {
slate.Editor.deleteBackward(editor, {
unit: 'line'
});
slate.Editor.deleteForward(editor, {
unit: 'line'
});
}, {
at: targetRange
});
}
case 'deleteHardLineBackward':
{
return scheduleAction(function () {
return slate.Editor.deleteBackward(editor, {
unit: 'block'
});
}, {
at: targetRange
});
}
case 'deleteSoftLineBackward':
{
return scheduleAction(function () {
return slate.Editor.deleteBackward(editor, {
unit: 'line'
});
}, {
at: targetRange
});
}
case 'deleteHardLineForward':
{
return scheduleAction(function () {
return slate.Editor.deleteForward(editor, {
unit: 'block'
});
}, {
at: targetRange
});
}
case 'deleteSoftLineForward':
{
return scheduleAction(function () {
return slate.Editor.deleteForward(editor, {
unit: 'line'
});
}, {
at: targetRange
});
}
case 'deleteWordBackward':
{
return scheduleAction(function () {
return slate.Editor.deleteBackward(editor, {
unit: 'word'
});
}, {
at: targetRange
});
}
case 'deleteWordForward':
{
return scheduleAction(function () {
return slate.Editor.deleteForward(editor, {
unit: 'word'
});
}, {
at: targetRange
});
}
case 'insertLineBreak':
{
return scheduleAction(function () {
return slate.Editor.insertSoftBreak(editor);
}, {
at: targetRange
});
}
case 'insertParagraph':
{
return scheduleAction(function () {
return slate.Editor.insertBreak(editor);
}, {
at: targetRange
});
}
case 'insertCompositionText':
case 'deleteCompositionText':
case 'insertFromComposition':
case 'insertFromDrop':
case 'insertFromPaste':
case 'insertFromYank':
case 'insertReplacementText':
case 'insertText':
{
if (isDataTransfer(data)) {
return scheduleAction(function () {
return ReactEditor.insertData(editor, data);
}, {
at: targetRange
});
}
var _text = data !== null && data !== void 0 ? data : '';
// COMPAT: If we are writing inside a placeholder, the ime inserts the text inside
// the placeholder itself and thus includes the zero-width space inside edit events.
if (slateDom.EDITOR_TO_PENDING_INSERTION_MARKS.get(editor)) {
_text = _text.replace("\uFEFF", '');
}
// Pastes from the Android clipboard will generate `insertText` events.
// If the copied text contains any newlines, Android will append an
// extra newline to the end of the copied text.
if (type === 'insertText' && /.*\n.*\n$/.test(_text)) {
_text = _text.slice(0, -1);
}
// If the text includes a newline, split it at newlines and paste each component
// string, with soft breaks in between each.
if (_text.includes('\n')) {
return scheduleAction(function () {
var parts = _text.split('\n');
parts.forEach(function (line, i) {
if (line) {
slate.Editor.insertText(editor, line);
}
if (i !== parts.length - 1) {
slate.Editor.insertSoftBreak(editor);
}
});
}, {
at: targetRange
});
}
if (slate.Path.equals(targetRange.anchor.path, targetRange.focus.path)) {
var _Range$edges3 = slate.Range.edges(targetRange),
_Range$edges4 = _slicedToArray(_Range$edges3, 2),
_start = _Range$edges4[0],
_end = _Range$edges4[1];
var _diff = {
start: _start.offset,
end: _end.offset,
text: _text
};
// COMPAT: Swiftkey has a weird bug where the target range of the 2nd word
// inserted after a mark placeholder is inserted with an anchor offset off by 1.
// So writing 'some text' will result in 'some ttext'. Luckily all 'normal' insert
// text events are fired with the correct target ranges, only the final 'insertComposition'
// isn't, so we can adjust the target range start offset if we are confident this is the
// swiftkey insert causing the issue.
if (_text && insertPositionHint && type === 'insertCompositionText') {
var hintPosition = insertPositionHint.start + insertPositionHint.text.search(/\S|$/);
var diffPosition = _diff.start + _diff.text.search(/\S|$/);
if (diffPosition === hintPosition + 1 && _diff.end === insertPositionHint.start + insertPositionHint.text.length) {
_diff.start -= 1;
insertPositionHint = null;
scheduleFlush();
} else {
insertPositionHint = false;
}
} else if (type === 'insertText') {
if (insertPositionHint === null) {
insertPositionHint = _diff;
} else if (insertPositionHint && slate.Range.isCollapsed(targetRange) && insertPositionHint.end + insertPositionHint.text.length === _start.offset) {
insertPositionHint = _objectSpread$8(_objectSpread$8({}, insertPositionHint), {}, {
text: insertPositionHint.text + _text
});
} else {
insertPositionHint = false;
}
} else {
insertPositionHint = false;
}
if (canStoreDiff) {
var currentSelection = editor.selection;
storeDiff(_start.path, _diff);
if (currentSelection) {
var newPoint = {
path: _start.path,
offset: _start.offset + _text.length
};
scheduleAction(function () {
slate.Transforms.select(editor, {
anchor: newPoint,
focus: newPoint
});
}, {
at: newPoint
});
}
return;
}
}
return scheduleAction(function () {
return slate.Editor.insertText(editor, _text);
}, {
at: targetRange
});
}
}
};
var hasPendingAction = function hasPendingAction() {
return !!slateDom.EDITOR_TO_PENDING_ACTION.get(editor);
};
var hasPendingDiffs = function hasPendingDiffs() {
var _EDITOR_TO_PENDING_DI5;
return !!((_EDITOR_TO_PENDING_DI5 = slateDom.EDITOR_TO_PENDING_DIFFS.get(editor)) !== null && _EDITOR_TO_PENDING_DI5 !== void 0 && _EDITOR_TO_PENDING_DI5.length);
};
var hasPendingChanges = function hasPendingChanges() {
return hasPendingAction() || hasPendingDiffs();
};
var isFlushing = function isFlushing() {
return flushing;
};
var handleUserSelect = function handleUserSelect(range) {
slateDom.EDITOR_TO_PENDING_SELECTION.set(editor, range);
if (flushTimeoutId) {
clearTimeout(flushTimeoutId);
flushTimeoutId = null;
}
var selection = editor.selection;
if (!range) {
return;
}
var pathChanged = !selection || !slate.Path.equals(selection.anchor.path, range.anchor.path);
var parentPathChanged = !selection || !slate.Path.equals(selection.anchor.path.slice(0, -1), range.anchor.path.slice(0, -1));
if (pathChanged && insertPositionHint || parentPathChanged) {
insertPositionHint = false;
}
if (pathChanged || hasPendingDiffs()) {
flushTimeoutId = setTimeout(flush, FLUSH_DELAY);
}
};
var handleInput = function handleInput() {
if (hasPendingAction() || !hasPendingDiffs()) {
flush();
}
};
var handleKeyDown = function handleKeyDown(_) {
// COMPAT: Swiftkey closes the keyboard when typing inside a empty node
// directly next to a non-contenteditable element (= the placeholder).
// The only event fired soon enough for us to allow hiding the placeholder
// without swiftkey picking it up is the keydown event, so we have to hide it
// here. See https://github.com/ianstormtaylor/slate/pull/4988#issuecomment-1201050535
if (!hasPendingDiffs()) {
updatePlaceholderVisibility(true);
setTimeout(updatePlaceholderVisibility);
}
};
var scheduleFlush = function scheduleFlush() {
if (!hasPendingAction()) {
actionTimeoutId = setTimeout(flush);
}
};
var handleDomMutations = function handleDomMutations(mutations) {
if (hasPendingDiffs() || hasPendingAction()) {
return;
}
if (mutations.some(function (mutation) {
return slateDom.isTrackedMutation(editor, mutation, mutations);
})) {
var _EDITOR_TO_FORCE_REND2;
// Cause a re-render to restore the dom state if we encounter tracked mutations without
// a corresponding pending action.
(_EDITOR_TO_FORCE_REND2 = slateDom.EDITOR_TO_FORCE_RENDER.get(editor)) === null || _EDITOR_TO_FORCE_REND2 === void 0 || _EDITOR_TO_FORCE_REND2();
}
};
return {
flush: flush,
scheduleFlush: scheduleFlush,
hasPendingDiffs: hasPendingDiffs,
hasPendingAction: hasPendingAction,
hasPendingChanges: hasPendingChanges,
isFlushing: isFlushing,
handleUserSelect: handleUserSelect,
handleCompositionEnd: handleCompositionEnd,
handleCompositionStart: handleCompositionStart,
handleDOMBeforeInput: handleDOMBeforeInput,
handleKeyDown: handleKeyDown,
handleDomMutations: handleDomMutations,
handleInput: handleInput
};
}
function useIsMounted() {
var isMountedRef = React.useRef(false);
React.useEffect(function () {
isMountedRef.current = true;
return function () {
isMountedRef.current = false;
};
}, []);
return isMountedRef.current;
}
/**
* Prevent warning on SSR by falling back to useEffect when DOM isn't available
*/
var useIsomorphicLayoutEffect = slateDom.CAN_USE_DOM ? React.useLayoutEffect : React.useEffect;
function useMutationObserver(node, callback, options) {
var _useState = React.useState(function () {
return new MutationObserver(callback);
}),
_useState2 = _slicedToArray(_useState, 1),
mutationObserver = _useState2[0];
useIsomorphicLayoutEffect(function () {
// Discard mutations caused during render phase. This works due to react calling
// useLayoutEffect synchronously after the render phase before the next tick.
mutationObserver.takeRecords();
});
React.useEffect(function () {
if (!node.current) {
throw new Error('Failed to attach MutationObserver, `node` is undefined');
}
mutationObserver.observe(node.current, options);
return function () {
return mutationObserver.disconnect();
};
}, [mutationObserver, node, options]);
}
var _excluded$2 = ["node"];
function ownKeys$7(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$7(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$7(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$7(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var MUTATION_OBSERVER_CONFIG$1 = {
subtree: true,
childList: true,
characterData: true
};
var useAndroidInputManager = !slateDom.IS_ANDROID ? function () {
return null;
} : function (_ref) {
var node = _ref.node,
options = _objectWithoutProperties(_ref, _excluded$2);
if (!slateDom.IS_ANDROID) {
return null;
}
var editor = useSlateStatic();
var isMounted = useIsMounted();
var _useState = React.useState(function () {
return createAndroidInputManager(_objectSpread$7({
editor: editor
}, options));
}),
_useState2 = _slicedToArray(_useState, 1),
inputManager = _useState2[0];
useMutationObserver(node, inputManager.handleDomMutations, MUTATION_OBSERVER_CONFIG$1);
slateDom.EDITOR_TO_SCHEDULE_FLUSH.set(editor, inputManager.scheduleFlush);
if (isMounted) {
inputManager.flush();
}
return inputManager;
};
function ownKeys$6(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$6(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$6(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$6(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
/**
* Leaf content strings.
*/
var String$1 = function String(props) {
var isLast = props.isLast,
leaf = props.leaf,
parent = props.parent,
text = props.text;
var editor = useSlateStatic();
var path = ReactEditor.findPath(editor, text);
var parentPath = slate.Path.parent(path);
var isMarkPlaceholder = Boolean(leaf[slateDom.MARK_PLACEHOLDER_SYMBOL]);
// COMPAT: Render text inside void nodes with a zero-width space.
// So the node can contain selection but the text is not visible.
if (editor.isVoid(parent)) {
return /*#__PURE__*/React.createElement(ZeroWidthString, {
length: slate.Node.string(parent).length
});
}
// COMPAT: If this is the last text node in an empty block, render a zero-
// width space that will convert into a line break when copying and pasting
// to support expected plain text.
if (leaf.text === '' && parent.children[parent.children.length - 1] === text && !editor.isInline(parent) && slate.Editor.string(editor, parentPath) === '') {
return /*#__PURE__*/React.createElement(ZeroWidthString, {
isLineBreak: true,
isMarkPlaceholder: isMarkPlaceholder
});
}
// COMPAT: If the text is empty, it's because it's on the edge of an inline
// node, so we render a zero-width space so that the selection can be
// inserted next to it still.
if (leaf.text === '') {
return /*#__PURE__*/React.createElement(ZeroWidthString, {
isMarkPlaceholder: isMarkPlaceholder
});
}
// COMPAT: Browsers will collapse trailing new lines at the end of blocks,
// so we need to add an extra trailing new lines to prevent that.
if (isLast && leaf.text.slice(-1) === '\n') {
return /*#__PURE__*/React.createElement(TextString, {
isTrailing: true,
text: leaf.text
});
}
return /*#__PURE__*/React.createElement(TextString, {
text: leaf.text
});
};
/**
* Leaf strings with text in them.
*/
var TextString = function TextString(props) {
var text = props.text,
_props$isTrailing = props.isTrailing,
isTrailing = _props$isTrailing === void 0 ? false : _props$isTrailing;
var editor = useSlateStatic();
var ref = React.useRef(null);
var getTextContent = function getTextContent() {
return "".concat(text !== null && text !== void 0 ? text : '').concat(isTrailing ? '\n' : '');
};
var _useState = React.useState(getTextContent),
_useState2 = _slicedToArray(_useState, 1),
initialText = _useState2[0];
// This is the actual text rendering boundary where we interface with the DOM
// The text is not rendered as part of the virtual DOM, as since we handle basic character insertions natively,
// updating the DOM is not a one way dataflow anymore. What we need here is not reconciliation and diffing
// with previous version of the virtual DOM, but rather diffing with the actual DOM element, and replace the DOM <span> content
// exactly if and only if its current content does not match our current virtual DOM.
// Otherwise the DOM TextNode would always be replaced by React as the user types, which interferes with native text features,
// eg makes native spellcheck opt out from checking the text node.
// useLayoutEffect: updating our span before browser paint
useIsomorphicLayoutEffect(function () {
// null coalescing text to make sure we're not outputing "null" as a string in the extreme case it is nullish at runtime
var textWithTrailing = getTextContent();
if (ref.current && ref.current.textContent !== textWithTrailing) {
// On Android the DOM intentionally runs ahead of the value while the IME
// composes, since input is buffered as pending diffs. Writing
// `textContent` here would replace the text node the composition lives in
// and cancel it; the pending diffs reconcile this span when the
// composition ends.
if (slateDom.IS_ANDROID && slateDom.IS_COMPOSING.get(editor)) {
var _ReactEditor$getWindo;
var composingNode = (_ReactEditor$getWindo = ReactEditor.getWindow(editor).getSelection()) === null || _ReactEditor$getWindo === void 0 ? void 0 : _ReactEditor$getWindo.anchorNode;
if (composingNode && ref.current.contains(composingNode)) {
return;
}
}
ref.current.textContent = textWithTrailing;
}
// intentionally not specifying dependencies, so that this effect runs on every render
// as this effectively replaces "specifying the text in the virtual DOM under the <span> below" on each render
});
// We intentionally render a memoized <span> that only receives the initial text content when the component is mounted.
// We defer to the layout effect above to update the `textContent` of the span element when needed.
return /*#__PURE__*/React.createElement(MemoizedText$1, {
ref: ref
}, initialText);
};
var MemoizedText$1 = /*#__PURE__*/React.memo( /*#__PURE__*/React.forwardRef(function (props, ref) {
return /*#__PURE__*/React.createElement("span", {
"data-slate-string": true,
ref: ref
}, props.children);
}));
/**
* Leaf strings without text, render as zero-width strings.
*/
var ZeroWidthString = function ZeroWidthString(props) {
var _props$length = props.length,
length = _props$length === void 0 ? 0 : _props$length,
_props$isLineBreak = props.isLineBreak,
isLineBreak = _props$isLineBreak === void 0 ? false : _props$isLineBreak,
_props$isMarkPlacehol = props.isMarkPlaceholder,
isMarkPlaceholder = _props$isMarkPlacehol === void 0 ? false : _props$isMarkPlacehol;
var attributes = {
'data-slate-zero-width': isLineBreak ? 'n' : 'z',
'data-slate-length': length
};
if (isMarkPlaceholder) {
attributes['data-slate-mark-placeholder'] = true;
}
// FIXME: Inserting the \uFEFF on iOS breaks capitalization at the start of an
// empty editor (https://github.com/ianstormtaylor/slate/issues/5199).
//
// However, not inserting the \uFEFF on iOS causes the editor to crash when
// inserting any text using an IME at the start of a block. This appears to
// be because accepting an IME suggestion when at the start of a block (no
// preceding \uFEFF) removes one or more DOM elements that `toSlateRange`
// depends on. (https://github.com/ianstormtaylor/slate/issues/5703)
// A leaf with no text node at all forces the IME to create one when the
// first character is composed there, and no re-render can then touch the
// leaf without cancelling the composition. Rendering the zero-width space on
// Android too gives the composition a React-owned text node to live in; the
// wrapper keeps its data-slate-zero-width attributes, so selection mapping
// is unchanged.
return /*#__PURE__*/React.createElement("span", _objectSpread$6({}, attributes), "\uFEFF", isLineBreak ? /*#__PURE__*/React.createElement("br", null) : null);
};
function ownKeys$5(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$5(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$5(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$5(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
// Delay the placeholder on Android to prevent the keyboard from closing.
// (https://github.com/ianstormtaylor/slate/pull/5368)
var PLACEHOLDER_DELAY = slateDom.IS_ANDROID ? 300 : 0;
function disconnectPlaceholderResizeObserver(placeholderResizeObserver, releaseObserver) {
if (placeholderResizeObserver.current) {
placeholderResizeObserver.current.disconnect();
if (releaseObserver) {
placeholderResizeObserver.current = null;
}
}
}
function clearTimeoutRef(timeoutRef) {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}
var defaultRenderLeaf = function defaultRenderLeaf(props) {
return /*#__PURE__*/React.createElement(DefaultLeaf, _objectSpread$5({}, props));
};
/**
* Individual leaves in a text node with unique formatting.
*/
var Leaf = function Leaf(props) {
var leaf = props.leaf,
isLast = props.isLast,
text = props.text,
parent = props.parent,
renderPlaceholder = props.renderPlaceholder,
_props$renderLeaf = props.renderLeaf,
renderLeaf = _props$renderLeaf === void 0 ? defaultRenderLeaf : _props$renderLeaf,
leafPosition = props.leafPosition;
var editor = useSlateStatic();
var placeholderResizeObserver = React.useRef(null);
var placeholderRef = React.useRef(null);
var _useState = React.useState(false),
_useState2 = _slicedToArray(_useState, 2),
showPlaceholder = _useState2[0],
setShowPlaceholder = _useState2[1];
var showPlaceholderTimeoutRef = React.useRef(null);
var callbackPlaceholderRef = React.useCallback(function (placeholderEl) {
disconnectPlaceholderResizeObserver(placeholderResizeObserver, placeholderEl == null);
if (placeholderEl == null) {
var _leaf$onPlaceholderRe;
slateDom.EDITOR_TO_PLACEHOLDER_ELEMENT["delete"](editor);
(_leaf$onPlaceholderRe = leaf.onPlaceholderResize) === null || _leaf$onPlaceholderRe === void 0 || _leaf$onPlaceholderRe.call(leaf, null);
} else {
slateDom.EDITOR_TO_PLACEHOLDER_ELEMENT.set(editor, placeholderEl);
if (!placeholderResizeObserver.current) {
// Create a new observer and observe the placeholder element.
var ResizeObserver = window.ResizeObserver || resizeObserver.ResizeObserver;
placeholderResizeObserver.current = new ResizeObserver(function () {
var _leaf$onPlaceholderRe2;
(_leaf$onPlaceholderRe2 = leaf.onPlaceholderResize) === null || _leaf$onPlaceholderRe2 === void 0 || _leaf$onPlaceholderRe2.call(leaf, placeholderEl);
});
}
placeholderResizeObserver.current.observe(placeholderEl);
placeholderRef.current = placeholderEl;
}
}, [placeholderRef, leaf, editor]);
var children = /*#__PURE__*/React.createElement(String$1, {
isLast: isLast,
leaf: leaf,
parent: parent,
text: text
});
var leafIsPlaceholder = Boolean(leaf[slateDom.PLACEHOLDER_SYMBOL]);
React.useEffect(function () {
if (leafIsPlaceholder) {
if (!showPlaceholderTimeoutRef.current) {
// Delay the placeholder, so it will not render in a selection
showPlaceholderTimeoutRef.current = setTimeout(function () {
setShowPlaceholder(true);
showPlaceholderTimeoutRef.current = null;
}, PLACEHOLDER_DELAY);
}
} else {
clearTimeoutRef(showPlaceholderTimeoutRef);
setShowPlaceholder(false);
}
return function () {
return clearTimeoutRef(showPlaceholderTimeoutRef);
};
}, [leafIsPlaceholder, setShowPlaceholder]);
if (leafIsPlaceholder && showPlaceholder) {
var placeholderProps = {
children: leaf.placeholder,
attributes: {
'data-slate-placeholder': true,
style: {
position: 'absolute',
top: 0,
pointerEvents: 'none',
width: '100%',
maxWidth: '100%',
display: 'block',
opacity: '0.333',
userSelect: 'none',
textDecoration: 'none',
// Fixes https://github.com/udecode/plate/issues/2315
WebkitUserModify: slateDom.IS_WEBKIT ? 'inherit' : undefined
},
contentEditable: false,
ref: callbackPlaceholderRef
}
};
children = /*#__PURE__*/React.createElement(React.Fragment, null, children, renderPlaceholder(placeholderProps));
}
// COMPAT: Having the `data-` attributes on these leaf elements ensures that
// in certain misbehaving browsers they aren't weirdly cloned/destroyed by
// contenteditable behaviors. (2019/05/08)
var attributes = {
'data-slate-leaf': true
};
return renderLeaf({
attributes: attributes,
children: children,
leaf: leaf,
text: text,
leafPosition: leafPosition
});
};
var MemoizedLeaf = /*#__PURE__*/React.memo(Leaf, function (prev, next) {
return next.parent === prev.parent && next.isLast === prev.isLast && next.renderLeaf === prev.renderLeaf && next.renderPlaceholder === prev.renderPlaceholder && next.text === prev.text && slate.Text.equals(next.leaf, prev.leaf) && next.leaf[slateDom.PLACEHOLDER_SYMBOL] === prev.leaf[slateDom.PLACEHOLDER_SYMBOL];
});
var DefaultLeaf = function DefaultLeaf(props) {
var attributes = props.attributes,
children = props.children;
return /*#__PURE__*/React.createElement("span", _objectSpread$5({}, attributes), children);
};
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 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);
/**
* Create a selector that updates when an `update` function is called, and
* which only causes the component to render when the result of `selector`
* differs from the previous result according to `equalityFn`.
*
* If `selector` is memoized using `useCallback`, then it will only be called
* when it changes or when `update` is called. Otherwise, `selector` will be
* called every time the component renders.
*
* @example
* const [state, update] = useGenericSelector(selector, equalityFn)
*
* useIsomorphicLayoutEffect(() => {
* return addEventListener(update)
* }, [addEventListener, update])
*
* return state
*/
function useGenericSelector(selector, equalityFn) {
var _useReducer = React.useReducer(function (s) {
return s + 1;
}, 0),
_useReducer2 = _slicedToArray(_useReducer, 2),
forceRender = _useReducer2[1];
var latestSubscriptionCallbackError = React.useRef();
var latestSelector = React.useRef(function () {
return null;
});
var latestSelectedState = React.useRef(null);
var selectedState;
try {
if (selector !== latestSelector.current || latestSubscriptionCallbackError.current) {
var selectorResult = selector();
if (equalityFn(latestSelectedState.current, selectorResult)) {
selectedState = latestSelectedState.current;
} else {
selectedState = selectorResult;
}
} else {
selectedState = latestSelectedState.current;
}
} catch (err) {
if (latestSubscriptionCallbackError.current && isError(err)) {
err.message += "\nThe error may be correlated with this previous error:\n".concat(latestSubscriptionCallbackError.current.stack, "\n\n");
}
throw err;
}
latestSelector.current = selector;
latestSelectedState.current = selectedState;
latestSubscriptionCallbackError.current = undefined;
var update = React.useCallback(function () {
try {
var newSelectedState = latestSelector.current();
if (equalityFn(latestSelectedState.current, newSelectedState)) {
return;
}
latestSelectedState.current = newSelectedState;
} catch (err) {
// we ignore all errors here, since when the component
// is re-rendered, the selectors are called again, and
// will throw again, if neither props nor store state
// changed
if (err instanceof Error) {
latestSubscriptionCallbackError.current = err;
} else {
latestSubscriptionCallbackError.current = new Error(String(err));
}
}
forceRender();
// don't rerender on equalityFn change since we want to be able to define it inline
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return [selectedState, update];
}
function isError(error) {
return error instanceof Error;
}
/**
* A React context for sharing the `decorate` prop of the editable and
* subscribing to changes on this prop.
*/
var DecorateContext = /*#__PURE__*/React.createContext({});
var useDecorations = function useDecorations(node, parentDecorations) {
var editor = useSlateStatic();
var _useContext = React.useContext(DecorateContext),
decorate = _useContext.decorate,
addEventListener = _useContext.addEventListener;
// Not memoized since we want nodes to be decorated on each render
var selector = function selector() {
var path = ReactEditor.findPath(editor, node);
return decorate([node, path]);
};
var equalityFn = slate.Node.isText(node) ? slateDom.isTextDecorationsEqual : slateDom.isElementDecorationsEqual;
var _useGenericSelector = useGenericSelector(selector, equalityFn),
_useGenericSelector2 = _slicedToArray(_useGenericSelector, 2),
decorations = _useGenericSelector2[0],
update = _useGenericSelector2[1];
useIsomorphicLayoutEffect(function () {
var unsubscribe = addEventListener(update);
update();
return unsubscribe;
}, [addEventListener, update]);
return React.useMemo(function () {
return [].concat(_toConsumableArray(decorations), _toConsumableArray(parentDecorations));
}, [decorations, parentDecorations]);
};
var useDecorateContext = function useDecorateContext(decorateProp) {
var _useReducer = React.useReducer(function (s) {
return s + 1;
}, 0),
_useReducer2 = _slicedToArray(_useReducer, 2),
forceUpdate = _useReducer2[1];
var eventListeners = React.useRef(new Set());
var hasMounted = React.useRef(false);
var latestDecorate = React.useRef(decorateProp);
useIsomorphicLayoutEffect(function () {
latestDecorate.current = decorateProp;
eventListeners.current.forEach(function (listener) {
return listener();
});
// Skip the forced re-render on the initial mount: there is no committed
// DOM to restore the selection against yet, and forcing an extra render
// here breaks contenteditable input in Firefox. Only later decoration
// changes need to re-render Editable in the same batch as the text
// components notified above, so its selection-restoration layout effect
// runs after the decoration-induced DOM changes are committed. Without
// that, the text components restructure the DOM in a separate pass where
// Editable's layout effect never fires, potentially leaving the caret at
// a wrong position.
if (!hasMounted.current) {
hasMounted.current = true;
return;
}
forceUpdate();
}, [decorateProp]);
var decorate = React.useCallback(function (entry) {
return latestDecorate.current(entry);
}, []);
var addEventListener = React.useCallback(function (callback) {
eventListeners.current.add(callback);
return function () {
eventListeners.current["delete"](callback);
};
}, []);
return React.useMemo(function () {
return {
decorate: decorate,
addEventListener: addEventListener
};
}, [decorate, addEventListener]);
};
function ownKeys$4(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$4(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$4(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$4(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var defaultRenderText = function defaultRenderText(props) {
return /*#__PURE__*/React.createElement(DefaultText, _objectSpread$4({}, props));
};
/**
* Text.
*/
var Text = function Text(props) {
var parentDecorations = props.decorations,
isLast = props.isLast,
parent = props.parent,
renderPlaceholder = props.renderPlaceholder,
renderLeaf = props.renderLeaf,
_props$renderText = props.renderText,
renderText = _props$renderText === void 0 ? defaultRenderText : _props$renderText,
text = props.text;
var editor = useSlateStatic();
var ref = React.useRef(null);
var decorations = useDecorations(text, parentDecorations);
var decoratedLeaves = slate.Text.decorations(text, decorations);
var key = ReactEditor.findKey(editor, text);
var children = [];
for (var i = 0; i < decoratedLeaves.length; i++) {
var _decoratedLeaves$i = decoratedLeaves[i],
leaf = _decoratedLeaves$i.leaf,
position = _decoratedLeaves$i.position;
children.push( /*#__PURE__*/React.createElement(MemoizedLeaf, {
isLast: isLast && i === decoratedLeaves.length - 1,
key: "".concat(key.id, "-").concat(i),
renderPlaceholder: renderPlaceholder,
leaf: leaf,
leafPosition: position,
text: text,
parent: parent,
renderLeaf: renderLeaf
}));
}
// Update element-related weak maps with the DOM element ref.
var callbackRef = React.useCallback(function (span) {
var KEY_TO_ELEMENT = slateDom.EDITOR_TO_KEY_TO_ELEMENT.get(editor);
if (span) {
KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 || KEY_TO_ELEMENT.set(key, span);
slateDom.NODE_TO_ELEMENT.set(text, span);
slateDom.ELEMENT_TO_NODE.set(span, text);
} else {
KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 || KEY_TO_ELEMENT["delete"](key);
slateDom.NODE_TO_ELEMENT["delete"](text);
if (ref.current) {
slateDom.ELEMENT_TO_NODE["delete"](ref.current);
}
}
ref.current = span;
}, [ref, editor, key, text]);
var attributes = {
'data-slate-node': 'text',
ref: callbackRef
};
return renderText({
text: text,
children: children,
attributes: attributes
});
};
var MemoizedText = /*#__PURE__*/React.memo(Text, function (prev, next) {
return next.parent === prev.parent && next.isLast === prev.isLast && next.renderText === prev.renderText && next.renderLeaf === prev.renderLeaf && next.renderPlaceholder === prev.renderPlaceholder && next.text === prev.text && slateDom.isTextDecorationsEqual(next.decorations, prev.decorations);
});
var DefaultText = function DefaultText(props) {
var attributes = props.attributes,
children = props.children;
return /*#__PURE__*/React.createElement("span", _objectSpread$4({}, attributes), children);
};
function ownKeys$3(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$3(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$3(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$3(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var defaultRenderElement = function defaultRenderElement(props) {
return /*#__PURE__*/React.createElement(DefaultElement, _objectSpread$3({}, props));
};
/**
* Element.
*/
var Element = function Element(props) {
var parentDecorations = props.decorations,
element = props.element,
_props$renderElement = props.renderElement,
renderElement = _props$renderElement === void 0 ? defaultRenderElement : _props$renderElement,
renderChunk = props.renderChunk,
renderPlaceholder = props.renderPlaceholder,
renderLeaf = props.renderLeaf,
renderText = props.renderText;
var editor = useSlateStatic();
var readOnly = useReadOnly();
var isInline = editor.isInline(element);
var decorations = useDecorations(element, parentDecorations);
var key = ReactEditor.findKey(editor, element);
var ref = React.useCallback(function (ref) {
// Update element-related weak maps with the DOM element ref.
var KEY_TO_ELEMENT = slateDom.EDITOR_TO_KEY_TO_ELEMENT.get(editor);
if (ref) {
KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 || KEY_TO_ELEMENT.set(key, ref);
slateDom.NODE_TO_ELEMENT.set(element, ref);
slateDom.ELEMENT_TO_NODE.set(ref, element);
} else {
KEY_TO_ELEMENT === null || KEY_TO_ELEMENT === void 0 || KEY_TO_ELEMENT["delete"](key);
slateDom.NODE_TO_ELEMENT["delete"](element);
}
}, [editor, key, element]);
var children = useChildren({
decorations: decorations,
node: element,
renderElement: renderElement,
renderChunk: renderChunk,
renderPlaceholder: renderPlaceholder,
renderLeaf: renderLeaf,
renderText: renderText
});
// Attributes that the developer must mix into the element in their
// custom node renderer component.
var attributes = {
'data-slate-node': 'element',
ref: ref
};
if (isInline) {
attributes['data-slate-inline'] = true;
}
// If it's a block node with inline children, add the proper `dir` attribute
// for text direction.
if (!isInline && slate.Editor.hasInlines(editor, element)) {
var text = slate.Node.string(element);
var dir = getDirection(text);
if (dir === 'rtl') {
attributes.dir = dir;
}
}
// If it's a void node, wrap the children in extra void-specific elements.
if (slate.Editor.isVoid(editor, element)) {
attributes['data-slate-void'] = true;
if (!readOnly && isInline) {
attributes.contentEditable = false;
}
var Tag = isInline ? 'span' : 'div';
var _Node$texts = slate.Node.texts(element),
_Node$texts2 = _slicedToArray(_Node$texts, 1),
_Node$texts2$ = _slicedToArray(_Node$texts2[0], 1),
_text = _Node$texts2$[0];
children = /*#__PURE__*/React.createElement(Tag, {
"data-slate-spacer": true,
style: {
height: '0',
color: 'transparent',
outline: 'none',
position: 'absolute'
}
}, /*#__PURE__*/React.createElement(MemoizedText, {
renderPlaceholder: renderPlaceholder,
decorations: [],
isLast: false,
parent: element,
text: _text
}));
slateDom.NODE_TO_INDEX.set(_text, 0);
slateDom.NODE_TO_PARENT.set(_text, element);
}
return renderElement({
attributes: attributes,
children: children,
element: element
});
};
var MemoizedElement = /*#__PURE__*/React.memo(Element, function (prev, next) {
return prev.element === next.element && prev.renderElement === next.renderElement && prev.renderChunk === next.renderChunk && prev.renderText === next.renderText && prev.renderLeaf === next.renderLeaf && prev.renderPlaceholder === next.renderPlaceholder && slateDom.isElementDecorationsEqual(prev.decorations, next.decorations);
});
/**
* The default element renderer.
*/
var DefaultElement = function DefaultElement(props) {
var attributes = props.attributes,
children = props.children,
element = props.element;
var editor = useSlateStatic();
var Tag = editor.isInline(element) ? 'span' : 'div';
return /*#__PURE__*/React.createElement(Tag, _objectSpread$3(_objectSpread$3({}, attributes), {}, {
style: {
position: 'relative'
}
}), children);
};
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 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);
/**
* Traverse and modify a chunk tree
*/
var ChunkTreeHelper = /*#__PURE__*/function () {
function ChunkTreeHelper(chunkTree, _ref) {
var chunkSize = _ref.chunkSize,
debug = _ref.debug;
_classCallCheck(this, ChunkTreeHelper);
/**
* The root of the chunk tree
*/
_defineProperty(this, "root", void 0);
/**
* The ideal size of a chunk
*/
_defineProperty(this, "chunkSize", void 0);
/**
* Whether debug mode is enabled
*
* If enabled, the pointer state will be checked for internal consistency
* after each mutating operation.
*/
_defineProperty(this, "debug", void 0);
/**
* Whether the traversal has reached the end of the chunk tree
*
* When this is true, the pointerChunk and pointerIndex point to the last
* top-level node in the chunk tree, although pointerNode returns null.
*/
_defineProperty(this, "reachedEnd", void 0);
/**
* The chunk containing the current node
*/
_defineProperty(this, "pointerChunk", void 0);
/**
* The index of the current node within pointerChunk
*
* Can be -1 to indicate that the pointer is before the start of the tree.
*/
_defineProperty(this, "pointerIndex", void 0);
/**
* Similar to a Slate path; tracks the path of pointerChunk relative to the
* root.
*
* Used to move the pointer from the current chunk to the parent chunk more
* efficiently.
*/
_defineProperty(this, "pointerIndexStack", void 0);
/**
* Indexing the current chunk's children has a slight time cost, which adds up
* when traversing very large trees, so the current node is cached.
*
* A value of undefined means that the current node is not cached. This
* property must be set to undefined whenever the pointer is moved, unless
* the pointer is guaranteed to point to the same node that it did previously.
*/
_defineProperty(this, "cachedPointerNode", void 0);
this.root = chunkTree;
this.chunkSize = chunkSize;
// istanbul ignore next
this.debug = debug !== null && debug !== void 0 ? debug : false;
this.pointerChunk = chunkTree;
this.pointerIndex = -1;
this.pointerIndexStack = [];
this.reachedEnd = false;
this.validateState();
}
/**
* Move the pointer to the next leaf in the chunk tree
*/
_createClass(ChunkTreeHelper, [{
key: "readLeaf",
value: function readLeaf() {
// istanbul ignore next
if (this.reachedEnd) return null;
// Get the next sibling or aunt node
while (true) {
if (this.pointerIndex + 1 < this.pointerSiblings.length) {
this.pointerIndex++;
this.cachedPointerNode = undefined;
break;
} else if (this.pointerChunk.type === 'root') {
this.reachedEnd = true;
return null;
} else {
this.exitChunk();
}
}
this.validateState();
// If the next sibling or aunt is a chunk, descend into it
this.enterChunkUntilLeaf(false);
return this.pointerNode;
}
/**
* Move the pointer to the previous leaf in the chunk tree
*/
}, {
key: "returnToPreviousLeaf",
value: function returnToPreviousLeaf() {
// If we were at the end of the tree, descend into the end of the last
// chunk in the tree
if (this.reachedEnd) {
this.reachedEnd = false;
this.enterChunkUntilLeaf(true);
return;
}
// Get the previous sibling or aunt node
while (true) {
if (this.pointerIndex >= 1) {
this.pointerIndex--;
this.cachedPointerNode = undefined;
break;
} else if (this.pointerChunk.type === 'root') {
this.pointerIndex = -1;
return;
} else {
this.exitChunk();
}
}
this.validateState();
// If the previous sibling or aunt is a chunk, descend into it
this.enterChunkUntilLeaf(true);
}
/**
* Insert leaves before the current leaf, leaving the pointer unchanged
*/
}, {
key: "insertBefore",
value: function insertBefore(leaves) {
this.returnToPreviousLeaf();
this.insertAfter(leaves);
this.readLeaf();
}
/**
* Insert leaves after the current leaf, leaving the pointer on the last
* inserted leaf
*
* The insertion algorithm first checks for any chunk we're currently at the
* end of that can receive additional leaves. Next, it tries to insert leaves
* at the starts of any subsequent chunks.
*
* Any remaining leaves are passed to rawInsertAfter to be chunked and
* inserted at the highest possible level.
*/
}, {
key: "insertAfter",
value: function insertAfter(leaves) {
// istanbul ignore next
if (leaves.length === 0) return;
var beforeDepth = 0;
var afterDepth = 0;
// While at the end of a chunk, insert any leaves that will fit, and then
// exit the chunk
while (this.pointerChunk.type === 'chunk' && this.pointerIndex === this.pointerSiblings.length - 1) {
var remainingCapacity = this.chunkSize - this.pointerSiblings.length;
var toInsertCount = Math.min(remainingCapacity, leaves.length);
if (toInsertCount > 0) {
var leavesToInsert = leaves.splice(0, toInsertCount);
this.rawInsertAfter(leavesToInsert, beforeDepth);
}
this.exitChunk();
beforeDepth++;
}
if (leaves.length === 0) return;
// Save the pointer so that we can come back here after inserting leaves
// into the starts of subsequent blocks
var rawInsertPointer = this.savePointer();
// If leaves are inserted into the start of a subsequent block, then we
// eventually need to restore the pointer to the last such inserted leaf
var finalPointer = null;
// Move the pointer into the chunk containing the next leaf, if it exists
if (this.readLeaf()) {
// While at the start of a chunk, insert any leaves that will fit, and
// then exit the chunk
while (this.pointerChunk.type === 'chunk' && this.pointerIndex === 0) {
var _remainingCapacity = this.chunkSize - this.pointerSiblings.length;
var _toInsertCount = Math.min(_remainingCapacity, leaves.length);
if (_toInsertCount > 0) {
var _leavesToInsert = leaves.splice(-_toInsertCount, _toInsertCount);
// Insert the leaves at the start of the chunk
this.pointerIndex = -1;
this.cachedPointerNode = undefined;
this.rawInsertAfter(_leavesToInsert, afterDepth);
// If this is the first batch of insertions at the start of a
// subsequent chunk, set the final pointer to the last inserted leaf
if (!finalPointer) {
finalPointer = this.savePointer();
}
}
this.exitChunk();
afterDepth++;
}
}
this.restorePointer(rawInsertPointer);
// If there are leaves left to insert, insert them between the end of the
// previous chunk and the start of the first subsequent chunk, or wherever
// the pointer ended up after the first batch of insertions
var minDepth = Math.max(beforeDepth, afterDepth);
this.rawInsertAfter(leaves, minDepth);
if (finalPointer) {
this.restorePointer(finalPointer);
}
this.validateState();
}
/**
* Remove the current node and decrement the pointer, deleting any ancestor
* chunk that becomes empty as a result
*/
}, {
key: "remove",
value: function remove() {
this.pointerSiblings.splice(this.pointerIndex--, 1);
this.cachedPointerNode = undefined;
if (this.pointerSiblings.length === 0 && this.pointerChunk.type === 'chunk') {
this.exitChunk();
this.remove();
} else {
this.invalidateChunk();
}
this.validateState();
}
/**
* Add the current chunk and all ancestor chunks to the list of modified
* chunks
*/
}, {
key: "invalidateChunk",
value: function invalidateChunk() {
for (var c = this.pointerChunk; c.type === 'chunk'; c = c.parent) {
this.root.modifiedChunks.add(c);
}
}
/**
* Whether the pointer is at the start of the tree
*/
}, {
key: "atStart",
get: function get() {
return this.pointerChunk.type === 'root' && this.pointerIndex === -1;
}
/**
* The siblings of the current node
*/
}, {
key: "pointerSiblings",
get: function get() {
return this.pointerChunk.children;
}
/**
* Get the current node (uncached)
*
* If the pointer is at the start or end of the document, returns null.
*
* Usually, the current node is a chunk leaf, although it can be a chunk
* while insertions are in progress.
*/
}, {
key: "getPointerNode",
value: function getPointerNode() {
if (this.reachedEnd || this.pointerIndex === -1) {
return null;
}
return this.pointerSiblings[this.pointerIndex];
}
/**
* Cached getter for the current node
*/
}, {
key: "pointerNode",
get: function get() {
if (this.cachedPointerNode !== undefined) return this.cachedPointerNode;
var pointerNode = this.getPointerNode();
this.cachedPointerNode = pointerNode;
return pointerNode;
}
/**
* Get the path of a chunk relative to the root, returning null if the chunk
* is not connected to the root
*/
}, {
key: "getChunkPath",
value: function getChunkPath(chunk) {
var path = [];
for (var c = chunk; c.type === 'chunk'; c = c.parent) {
var index = c.parent.children.indexOf(c);
// istanbul ignore next
if (index === -1) {
return null;
}
path.unshift(index);
}
return path;
}
/**
* Save the current pointer to be restored later
*/
}, {
key: "savePointer",
value: function savePointer() {
if (this.atStart) return 'start';
// istanbul ignore next
if (!this.pointerNode) {
throw new Error('Cannot save pointer when pointerNode is null');
}
return {
chunk: this.pointerChunk,
node: this.pointerNode
};
}
/**
* Restore the pointer to a previous state
*/
}, {
key: "restorePointer",
value: function restorePointer(savedPointer) {
if (savedPointer === 'start') {
this.pointerChunk = this.root;
this.pointerIndex = -1;
this.pointerIndexStack = [];
this.reachedEnd = false;
this.cachedPointerNode = undefined;
return;
}
// Since nodes may have been inserted or removed prior to the saved
// pointer since it was saved, the index and index stack must be
// recomputed. This is slow, but this is fine since restoring a pointer is
// not a frequent operation.
var chunk = savedPointer.chunk,
node = savedPointer.node;
var index = chunk.children.indexOf(node);
// istanbul ignore next
if (index === -1) {
throw new Error('Cannot restore point because saved node is no longer in saved chunk');
}
var indexStack = this.getChunkPath(chunk);
// istanbul ignore next
if (!indexStack) {
throw new Error('Cannot restore point because saved chunk is no longer connected to root');
}
this.pointerChunk = chunk;
this.pointerIndex = index;
this.pointerIndexStack = indexStack;
this.reachedEnd = false;
this.cachedPointerNode = node;
this.validateState();
}
/**
* Assuming the current node is a chunk, move the pointer into that chunk
*
* @param end If true, place the pointer on the last node of the chunk.
* Otherwise, place the pointer on the first node.
*/
}, {
key: "enterChunk",
value: function enterChunk(end) {
var _this$pointerNode;
// istanbul ignore next
if (((_this$pointerNode = this.pointerNode) === null || _this$pointerNode === void 0 ? void 0 : _this$pointerNode.type) !== 'chunk') {
throw new Error('Cannot enter non-chunk');
}
this.pointerIndexStack.push(this.pointerIndex);
this.pointerChunk = this.pointerNode;
this.pointerIndex = end ? this.pointerSiblings.length - 1 : 0;
this.cachedPointerNode = undefined;
this.validateState();
// istanbul ignore next
if (this.pointerChunk.children.length === 0) {
throw new Error('Cannot enter empty chunk');
}
}
/**
* Assuming the current node is a chunk, move the pointer into that chunk
* repeatedly until the current node is a leaf
*
* @param end If true, place the pointer on the last node of the chunk.
* Otherwise, place the pointer on the first node.
*/
}, {
key: "enterChunkUntilLeaf",
value: function enterChunkUntilLeaf(end) {
while (((_this$pointerNode2 = this.pointerNode) === null || _this$pointerNode2 === void 0 ? void 0 : _this$pointerNode2.type) === 'chunk') {
var _this$pointerNode2;
this.enterChunk(end);
}
}
/**
* Move the pointer to the parent chunk
*/
}, {
key: "exitChunk",
value: function exitChunk() {
// istanbul ignore next
if (this.pointerChunk.type === 'root') {
throw new Error('Cannot exit root');
}
var previousPointerChunk = this.pointerChunk;
this.pointerChunk = previousPointerChunk.parent;
this.pointerIndex = this.pointerIndexStack.pop();
this.cachedPointerNode = undefined;
this.validateState();
}
/**
* Insert leaves immediately after the current node, leaving the pointer on
* the last inserted leaf
*
* Leaves are chunked according to the number of nodes already in the parent
* plus the number of nodes being inserted, or the minimum depth if larger
*/
}, {
key: "rawInsertAfter",
value: function rawInsertAfter(leaves, minDepth) {
var _this = this,
_this$pointerSiblings;
if (leaves.length === 0) return;
var groupIntoChunks = function groupIntoChunks(leaves, parent, perChunk) {
if (perChunk === 1) return leaves;
var chunks = [];
for (var i = 0; i < _this.chunkSize; i++) {
var chunkNodes = leaves.slice(i * perChunk, (i + 1) * perChunk);
if (chunkNodes.length === 0) break;
var chunk = {
type: 'chunk',
key: new slateDom.Key(),
parent: parent,
children: []
};
chunk.children = groupIntoChunks(chunkNodes, chunk, perChunk / _this.chunkSize);
chunks.push(chunk);
}
return chunks;
};
// Determine the chunking depth based on the number of existing nodes in
// the chunk and the number of nodes being inserted
var newTotal = this.pointerSiblings.length + leaves.length;
var depthForTotal = 0;
for (var i = this.chunkSize; i < newTotal; i *= this.chunkSize) {
depthForTotal++;
}
// A depth of 0 means no chunking
var depth = Math.max(depthForTotal, minDepth);
var perTopLevelChunk = Math.pow(this.chunkSize, depth);
var chunks = groupIntoChunks(leaves, this.pointerChunk, perTopLevelChunk);
(_this$pointerSiblings = this.pointerSiblings).splice.apply(_this$pointerSiblings, [this.pointerIndex + 1, 0].concat(_toConsumableArray(chunks)));
this.pointerIndex += chunks.length;
this.cachedPointerNode = undefined;
this.invalidateChunk();
this.validateState();
}
/**
* If debug mode is enabled, ensure that the state is internally consistent
*/
// istanbul ignore next
}, {
key: "validateState",
value: function validateState() {
if (!this.debug) return;
var validateDescendant = function validateDescendant(node) {
if (node.type === 'chunk') {
var parent = node.parent,
children = node.children;
if (!parent.children.includes(node)) {
throw new Error("Debug: Chunk ".concat(node.key.id, " has an incorrect parent property"));
}
children.forEach(validateDescendant);
}
};
this.root.children.forEach(validateDescendant);
if (this.cachedPointerNode !== undefined && this.cachedPointerNode !== this.getPointerNode()) {
throw new Error('Debug: The cached pointer is incorrect and has not been invalidated');
}
var actualIndexStack = this.getChunkPath(this.pointerChunk);
if (!actualIndexStack) {
throw new Error('Debug: The pointer chunk is not connected to the root');
}
if (!slate.Path.equals(this.pointerIndexStack, actualIndexStack)) {
throw new Error("Debug: The cached index stack [".concat(this.pointerIndexStack.join(', '), "] does not match the path of the pointer chunk [").concat(actualIndexStack.join(', '), "]"));
}
}
}]);
return ChunkTreeHelper;
}();
/**
* Traverse an array of children, providing helpers useful for reconciling the
* children array with a chunk tree
*/
var ChildrenHelper = /*#__PURE__*/function () {
function ChildrenHelper(editor, children) {
_classCallCheck(this, ChildrenHelper);
_defineProperty(this, "editor", void 0);
_defineProperty(this, "children", void 0);
/**
* Sparse array of Slate node keys, each index corresponding to an index in
* the children array
*
* Fetching the key for a Slate node is expensive, so we cache them here.
*/
_defineProperty(this, "cachedKeys", void 0);
/**
* The index of the next node to be read in the children array
*/
_defineProperty(this, "pointerIndex", void 0);
this.editor = editor;
this.children = children;
this.cachedKeys = new Array(children.length);
this.pointerIndex = 0;
}
/**
* Read a given number of nodes, advancing the pointer by that amount
*/
_createClass(ChildrenHelper, [{
key: "read",
value: function read(n) {
// PERF: If only one child was requested (the most common case), use array
// indexing instead of slice
if (n === 1) {
return [this.children[this.pointerIndex++]];
}
var slicedChildren = this.remaining(n);
this.pointerIndex += n;
return slicedChildren;
}
/**
* Get the remaining children without advancing the pointer
*
* @param [maxChildren] Limit the number of children returned.
*/
}, {
key: "remaining",
value: function remaining(maxChildren) {
if (maxChildren === undefined) {
return this.children.slice(this.pointerIndex);
}
return this.children.slice(this.pointerIndex, this.pointerIndex + maxChildren);
}
/**
* Whether all children have been read
*/
}, {
key: "reachedEnd",
get: function get() {
return this.pointerIndex >= this.children.length;
}
/**
* Determine whether a node with a given key appears in the unread part of the
* children array, and return its index relative to the current pointer if so
*
* Searching for the node object itself using indexOf is most efficient, but
* will fail to locate nodes that have been modified. In this case, nodes
* should be identified by their keys instead.
*
* Searching an array of keys using indexOf is very inefficient since fetching
* the keys for all children in advance is very slow. Insead, if the node
* search fails to return a value, fetch the keys of each remaining child one
* by one and compare it to the known key.
*/
}, {
key: "lookAhead",
value: function lookAhead(node, key) {
var elementResult = this.children.indexOf(node, this.pointerIndex);
if (elementResult > -1) return elementResult - this.pointerIndex;
for (var i = this.pointerIndex; i < this.children.length; i++) {
var candidateNode = this.children[i];
var candidateKey = this.findKey(candidateNode, i);
if (candidateKey === key) return i - this.pointerIndex;
}
return -1;
}
/**
* Convert an array of Slate nodes to an array of chunk leaves, each
* containing the node and its key
*/
}, {
key: "toChunkLeaves",
value: function toChunkLeaves(nodes, startIndex) {
var _this = this;
return nodes.map(function (node, i) {
return {
type: 'leaf',
node: node,
key: _this.findKey(node, startIndex + i),
index: startIndex + i
};
});
}
/**
* Get the key for a Slate node, cached using the node's index
*/
}, {
key: "findKey",
value: function findKey(node, index) {
var cachedKey = this.cachedKeys[index];
if (cachedKey) return cachedKey;
var key = ReactEditor.findKey(this.editor, node);
this.cachedKeys[index] = key;
return key;
}
}]);
return ChildrenHelper;
}();
/**
* Update the chunk tree to match the children array, inserting, removing and
* updating differing nodes
*/
var reconcileChildren = function reconcileChildren(editor, _ref) {
var chunkTree = _ref.chunkTree,
children = _ref.children,
chunkSize = _ref.chunkSize,
_ref$rerenderChildren = _ref.rerenderChildren,
rerenderChildren = _ref$rerenderChildren === void 0 ? [] : _ref$rerenderChildren,
onInsert = _ref.onInsert,
onUpdate = _ref.onUpdate,
onIndexChange = _ref.onIndexChange,
debug = _ref.debug;
var chunkTreeHelper = new ChunkTreeHelper(chunkTree, {
chunkSize: chunkSize,
debug: debug
});
var childrenHelper = new ChildrenHelper(editor, children);
var treeLeaf;
// Read leaves from the tree one by one, each one representing a single Slate
// node. Each leaf from the tree is compared to the current node in the
// children array to determine whether nodes have been inserted, removed or
// updated.
var _loop = function _loop() {
// Check where the tree node appears in the children array. In the most
// common case (where no insertions or removals have occurred), this will be
// 0. If the node has been removed, this will be -1. If new nodes have been
// inserted before the node, or if the node has been moved to a later
// position in the same children array, this will be a positive number.
var lookAhead = childrenHelper.lookAhead(treeLeaf.node, treeLeaf.key);
// If the node was moved, we want to remove it and insert it later, rather
// then re-inserting all intermediate nodes before it.
var wasMoved = lookAhead > 0 && chunkTree.movedNodeKeys.has(treeLeaf.key);
// If the tree leaf was moved or removed, remove it
if (lookAhead === -1 || wasMoved) {
chunkTreeHelper.remove();
return 1; // continue
}
// Get the matching Slate node and any nodes that may have been inserted
// prior to it. Insert these into the chunk tree.
var insertedChildrenStartIndex = childrenHelper.pointerIndex;
var insertedChildren = childrenHelper.read(lookAhead + 1);
var matchingChild = insertedChildren.pop();
if (insertedChildren.length) {
var _leavesToInsert = childrenHelper.toChunkLeaves(insertedChildren, insertedChildrenStartIndex);
chunkTreeHelper.insertBefore(_leavesToInsert);
insertedChildren.forEach(function (node, relativeIndex) {
onInsert === null || onInsert === void 0 || onInsert(node, insertedChildrenStartIndex + relativeIndex);
});
}
var matchingChildIndex = childrenHelper.pointerIndex - 1;
// Make sure the chunk tree contains the most recent version of the Slate
// node
if (treeLeaf.node !== matchingChild) {
treeLeaf.node = matchingChild;
chunkTreeHelper.invalidateChunk();
onUpdate === null || onUpdate === void 0 || onUpdate(matchingChild, matchingChildIndex);
}
// Update the index if it has changed
if (treeLeaf.index !== matchingChildIndex) {
treeLeaf.index = matchingChildIndex;
onIndexChange === null || onIndexChange === void 0 || onIndexChange(matchingChild, matchingChildIndex);
}
// Manually invalidate chunks containing specific children that we want to
// re-render
if (rerenderChildren.includes(matchingChildIndex)) {
chunkTreeHelper.invalidateChunk();
}
};
while (treeLeaf = chunkTreeHelper.readLeaf()) {
if (_loop()) continue;
}
// If there are still Slate nodes remaining from the children array that were
// not matched to nodes in the tree, insert them at the end of the tree
if (!childrenHelper.reachedEnd) {
var remainingChildren = childrenHelper.remaining();
var leavesToInsert = childrenHelper.toChunkLeaves(remainingChildren, childrenHelper.pointerIndex);
// Move the pointer back to the final leaf in the tree, or the start of the
// tree if the tree is currently empty
chunkTreeHelper.returnToPreviousLeaf();
chunkTreeHelper.insertAfter(leavesToInsert);
remainingChildren.forEach(function (node, relativeIndex) {
onInsert === null || onInsert === void 0 || onInsert(node, childrenHelper.pointerIndex + relativeIndex);
});
}
chunkTree.movedNodeKeys.clear();
};
function ownKeys$2(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$2(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$2(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var KEY_TO_CHUNK_TREE = new WeakMap();
/**
* Get or create the chunk tree for a Slate node
*
* If the reconcile option is provided, the chunk tree will be updated to
* match the current children of the node. The children are chunked
* automatically using the given chunk size.
*/
var getChunkTreeForNode = function getChunkTreeForNode(editor, node) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var key = ReactEditor.findKey(editor, node);
var chunkTree = KEY_TO_CHUNK_TREE.get(key);
if (!chunkTree) {
chunkTree = {
type: 'root',
movedNodeKeys: new Set(),
modifiedChunks: new Set(),
children: []
};
KEY_TO_CHUNK_TREE.set(key, chunkTree);
}
if (options.reconcile) {
reconcileChildren(editor, _objectSpread$2({
chunkTree: chunkTree,
children: node.children
}, options.reconcile));
}
return chunkTree;
};
function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var defaultRenderChunk = function defaultRenderChunk(_ref) {
var children = _ref.children;
return children;
};
var ChunkAncestor = function ChunkAncestor(props) {
var root = props.root,
ancestor = props.ancestor,
renderElement = props.renderElement,
_props$renderChunk = props.renderChunk,
renderChunk = _props$renderChunk === void 0 ? defaultRenderChunk : _props$renderChunk;
return ancestor.children.map(function (chunkNode) {
if (chunkNode.type === 'chunk') {
var key = chunkNode.key.id;
var renderedChunk = renderChunk({
highest: ancestor === root,
lowest: chunkNode.children.some(function (c) {
return c.type === 'leaf';
}),
attributes: {
'data-slate-chunk': true
},
children: /*#__PURE__*/React.createElement(MemoizedChunk, {
root: root,
ancestor: chunkNode,
renderElement: renderElement,
renderChunk: renderChunk
})
});
return /*#__PURE__*/React.createElement(React.Fragment, {
key: key
}, renderedChunk);
}
// Only blocks containing no inlines are chunked
var element = chunkNode.node;
return renderElement(element, chunkNode.index, chunkNode.key);
});
};
var ChunkTree = function ChunkTree(props) {
// Clear the set of modified chunks only when React finishes rendering. The
// timing of this is important in strict mode because if the chunks are
// cleared during rendering (such as in reconcileChildren), strict mode's
// second render won't include them.
React.useEffect(function () {
props.root.modifiedChunks.clear();
});
return /*#__PURE__*/React.createElement(ChunkAncestor, _objectSpread$1({}, props));
};
var MemoizedChunk = /*#__PURE__*/React.memo(ChunkAncestor, function (prev, next) {
return prev.root === next.root && prev.renderElement === next.renderElement && prev.renderChunk === next.renderChunk && !next.root.modifiedChunks.has(next.ancestor);
});
var ElementContext = /*#__PURE__*/React.createContext(null);
/**
* Get the current element.
*/
var useElement = function useElement() {
var context = React.useContext(ElementContext);
if (!context) {
throw new Error('The `useElement` hook must be used inside `renderElement`.');
}
return context;
};
/**
* Get the current element, or return null if not inside `renderElement`.
*/
var useElementIf = function useElementIf() {
return React.useContext(ElementContext);
};
/**
* Children.
*/
var useChildren = function useChildren(props) {
var decorations = props.decorations,
node = props.node,
renderElement = props.renderElement,
renderChunk = props.renderChunk,
renderPlaceholder = props.renderPlaceholder,
renderText = props.renderText,
renderLeaf = props.renderLeaf;
var editor = useSlateStatic();
slateDom.IS_NODE_MAP_DIRTY.set(editor, false);
var isBlock = slate.Node.isElement(node) && !editor.isInline(node);
var isLeafBlock = isBlock && slate.Editor.hasInlines(editor, node);
var chunkSize = isLeafBlock ? null : editor.getChunkSize(node);
var chunking = !!chunkSize;
var _useDecorationsByChil = useDecorationsByChild(editor, node, decorations),
decorationsByChild = _useDecorationsByChil.decorationsByChild,
childrenToRedecorate = _useDecorationsByChil.childrenToRedecorate;
// Update the index and parent of each child.
// PERF: If chunking is enabled, this is done while traversing the chunk tree
// instead to eliminate unnecessary weak map operations.
if (!chunking) {
node.children.forEach(function (n, i) {
slateDom.NODE_TO_INDEX.set(n, i);
slateDom.NODE_TO_PARENT.set(n, node);
});
}
var renderElementComponent = React.useCallback(function (n, i, cachedKey) {
var key = cachedKey !== null && cachedKey !== void 0 ? cachedKey : ReactEditor.findKey(editor, n);
return /*#__PURE__*/React.createElement(ElementContext.Provider, {
key: "provider-".concat(key.id),
value: n
}, /*#__PURE__*/React.createElement(MemoizedElement, {
decorations: decorationsByChild[i],
element: n,
key: key.id,
renderElement: renderElement,
renderChunk: renderChunk,
renderPlaceholder: renderPlaceholder,
renderLeaf: renderLeaf,
renderText: renderText
}));
}, [editor, decorationsByChild, renderElement, renderChunk, renderPlaceholder, renderLeaf, renderText]);
var renderTextComponent = function renderTextComponent(n, i) {
var key = ReactEditor.findKey(editor, n);
return /*#__PURE__*/React.createElement(MemoizedText, {
decorations: decorationsByChild[i],
key: key.id,
isLast: i === node.children.length - 1,
parent: node,
renderPlaceholder: renderPlaceholder,
renderLeaf: renderLeaf,
renderText: renderText,
text: n
});
};
if (!chunking) {
return node.children.map(function (n, i) {
return slate.Node.isText(n) ? renderTextComponent(n, i) : renderElementComponent(n, i);
});
}
var chunkTree = getChunkTreeForNode(editor, node, {
reconcile: {
chunkSize: chunkSize,
rerenderChildren: childrenToRedecorate,
onInsert: function onInsert(n, i) {
slateDom.NODE_TO_INDEX.set(n, i);
slateDom.NODE_TO_PARENT.set(n, node);
},
onUpdate: function onUpdate(n, i) {
slateDom.NODE_TO_INDEX.set(n, i);
slateDom.NODE_TO_PARENT.set(n, node);
},
onIndexChange: function onIndexChange(n, i) {
slateDom.NODE_TO_INDEX.set(n, i);
}
}
});
return /*#__PURE__*/React.createElement(ChunkTree, {
root: chunkTree,
ancestor: chunkTree,
renderElement: renderElementComponent,
renderChunk: renderChunk
});
};
var useDecorationsByChild = function useDecorationsByChild(editor, node, decorations) {
var decorationsByChild = slateDom.splitDecorationsByChild(editor, node, decorations);
// The value we return is a mutable array of `DecoratedRange[]` arrays. This
// lets us avoid passing an immutable array of decorations for each child into
// `ChunkTree` using props. Each `DecoratedRange[]` is only updated if the
// decorations at that index have changed, which speeds up the equality check
// for the `decorations` prop in the memoized `Element` and `Text` components.
var mutableDecorationsByChild = React.useRef(decorationsByChild).current;
// Track the list of child indices whose decorations have changed, so that we
// can tell the chunk tree to re-render these children.
var childrenToRedecorate = [];
// Resize the mutable array to match the latest result
mutableDecorationsByChild.length = decorationsByChild.length;
for (var i = 0; i < decorationsByChild.length; i++) {
var _mutableDecorationsBy;
var _decorations = decorationsByChild[i];
var previousDecorations = (_mutableDecorationsBy = mutableDecorationsByChild[i]) !== null && _mutableDecorationsBy !== void 0 ? _mutableDecorationsBy : null;
if (!slateDom.isElementDecorationsEqual(previousDecorations, _decorations)) {
mutableDecorationsByChild[i] = _decorations;
childrenToRedecorate.push(i);
}
}
return {
decorationsByChild: mutableDecorationsByChild,
childrenToRedecorate: childrenToRedecorate
};
};
/**
* A React context for sharing the `readOnly` state of the editor.
*/
var ReadOnlyContext = /*#__PURE__*/React.createContext(false);
/**
* Get the current `readOnly` state of the editor.
*/
var useReadOnly = function useReadOnly() {
return React.useContext(ReadOnlyContext);
};
/**
* A React context for sharing the editor selector context in a way to control
* re-renders.
*/
var SlateSelectorContext = /*#__PURE__*/React.createContext({});
var refEquality = function refEquality(a, b) {
return a === b;
};
/**
* Use redux style selectors to prevent re-rendering on every keystroke.
*
* Bear in mind re-rendering can only prevented if the returned value is a value
* type or for reference types (e.g. objects and arrays) add a custom equality
* function.
*
* If `selector` is memoized using `useCallback`, then it will only be called
* when it or the editor state changes. Otherwise, `selector` will be called
* every time the component renders.
*
* @example
* const isSelectionActive = useSlateSelector(editor => Boolean(editor.selection))
*/
function useSlateSelector(selector) {
var equalityFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : refEquality;
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
deferred = _ref.deferred;
var context = React.useContext(SlateSelectorContext);
if (!context) {
throw new Error("The `useSlateSelector` hook must be used inside the <Slate> component's context.");
}
var addEventListener = context.addEventListener;
var editor = useSlateStatic();
var genericSelector = React.useCallback(function () {
return selector(editor);
}, [editor, selector]);
var _useGenericSelector = useGenericSelector(genericSelector, equalityFn),
_useGenericSelector2 = _slicedToArray(_useGenericSelector, 2),
selectedState = _useGenericSelector2[0],
update = _useGenericSelector2[1];
useIsomorphicLayoutEffect(function () {
var unsubscribe = addEventListener(update, {
deferred: deferred
});
update();
return unsubscribe;
}, [addEventListener, update, deferred]);
return selectedState;
}
/**
* Create selector context with editor updating on every editor change
*/
function useSelectorContext() {
var eventListeners = React.useRef(new Set());
var deferredEventListeners = React.useRef(new Set());
var onChange = React.useCallback(function () {
eventListeners.current.forEach(function (listener) {
return listener();
});
}, []);
var flushDeferred = React.useCallback(function () {
deferredEventListeners.current.forEach(function (listener) {
return listener();
});
deferredEventListeners.current.clear();
}, []);
var addEventListener = React.useCallback(function (callbackProp) {
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref2$deferred = _ref2.deferred,
deferred = _ref2$deferred === void 0 ? false : _ref2$deferred;
var callback = deferred ? function () {
return deferredEventListeners.current.add(callbackProp);
} : callbackProp;
eventListeners.current.add(callback);
return function () {
eventListeners.current["delete"](callback);
};
}, []);
var selectorContext = React.useMemo(function () {
return {
addEventListener: addEventListener,
flushDeferred: flushDeferred
};
}, [addEventListener, flushDeferred]);
return {
selectorContext: selectorContext,
onChange: onChange
};
}
function useFlushDeferredSelectorsOnRender() {
var _useContext = React.useContext(SlateSelectorContext),
flushDeferred = _useContext.flushDeferred;
useIsomorphicLayoutEffect(flushDeferred);
}
/**
* Get the current editor object and re-render whenever it changes.
*/
var useSlate = function useSlate() {
var _useContext = React.useContext(SlateSelectorContext),
addEventListener = _useContext.addEventListener;
var _useReducer = React.useReducer(function (s) {
return s + 1;
}, 0),
_useReducer2 = _slicedToArray(_useReducer, 2),
forceRender = _useReducer2[1];
if (!addEventListener) {
throw new Error("The `useSlate` hook must be used inside the <Slate> component's context.");
}
useIsomorphicLayoutEffect(function () {
return addEventListener(forceRender);
}, [addEventListener]);
return useSlateStatic();
};
var EDITOR_TO_V = new WeakMap();
var getEditorVersionRef = function getEditorVersionRef(editor) {
var v = EDITOR_TO_V.get(editor);
if (v) {
return v;
}
v = {
current: 0
};
EDITOR_TO_V.set(editor, v);
// Register the `onChange` handler exactly once per editor
var onChange = editor.onChange;
editor.onChange = function (options) {
v.current++;
onChange(options);
};
return v;
};
/**
* Get the current editor object and its version, which increments on every
* change.
*
* @deprecated The `v` counter is no longer used except for this hook, and may
* be removed in a future version.
*/
var useSlateWithV = function useSlateWithV() {
var editor = useSlate();
var vRef = React.useMemo(function () {
return getEditorVersionRef(editor);
}, [editor]);
return {
editor: editor,
v: vRef.current
};
};
function useTrackUserInput() {
var editor = useSlateStatic();
var receivedUserInput = React.useRef(false);
var animationFrameIdRef = React.useRef(0);
var onUserInput = React.useCallback(function () {
if (receivedUserInput.current) {
return;
}
receivedUserInput.current = true;
var window = ReactEditor.getWindow(editor);
window.cancelAnimationFrame(animationFrameIdRef.current);
animationFrameIdRef.current = window.requestAnimationFrame(function () {
receivedUserInput.current = false;
});
}, [editor]);
React.useEffect(function () {
return function () {
return cancelAnimationFrame(animationFrameIdRef.current);
};
}, []);
return {
receivedUserInput: receivedUserInput,
onUserInput: onUserInput
};
}
var assertThisInitialized = createCommonjsModule(function (module) {
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
module.exports = _assertThisInitialized, module.exports.__esModule = true, module.exports["default"] = module.exports;
});
var _assertThisInitialized = unwrapExports(assertThisInitialized);
var setPrototypeOf = createCommonjsModule(function (module) {
function _setPrototypeOf(o, p) {
module.exports = _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
}, module.exports.__esModule = true, module.exports["default"] = module.exports;
return _setPrototypeOf(o, p);
}
module.exports = _setPrototypeOf, module.exports.__esModule = true, module.exports["default"] = module.exports;
});
unwrapExports(setPrototypeOf);
var inherits = createCommonjsModule(function (module) {
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
Object.defineProperty(subClass, "prototype", {
writable: false
});
if (superClass) setPrototypeOf(subClass, superClass);
}
module.exports = _inherits, module.exports.__esModule = true, module.exports["default"] = module.exports;
});
var _inherits = unwrapExports(inherits);
var possibleConstructorReturn = createCommonjsModule(function (module) {
var _typeof = _typeof_1["default"];
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
} else if (call !== void 0) {
throw new TypeError("Derived constructors may only return object or undefined");
}
return assertThisInitialized(self);
}
module.exports = _possibleConstructorReturn, module.exports.__esModule = true, module.exports["default"] = module.exports;
});
var _possibleConstructorReturn = unwrapExports(possibleConstructorReturn);
var getPrototypeOf = createCommonjsModule(function (module) {
function _getPrototypeOf(o) {
module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
}, module.exports.__esModule = true, module.exports["default"] = module.exports;
return _getPrototypeOf(o);
}
module.exports = _getPrototypeOf, module.exports.__esModule = true, module.exports["default"] = module.exports;
});
var _getPrototypeOf = unwrapExports(getPrototypeOf);
var createRestoreDomManager = function createRestoreDomManager(editor, receivedUserInput) {
var bufferedMutations = [];
var clear = function clear() {
bufferedMutations = [];
};
var registerMutations = function registerMutations(mutations) {
var _bufferedMutations;
if (!receivedUserInput.current) {
return;
}
var trackedMutations = mutations.filter(function (mutation) {
return slateDom.isTrackedMutation(editor, mutation, mutations);
});
(_bufferedMutations = bufferedMutations).push.apply(_bufferedMutations, _toConsumableArray(trackedMutations));
};
function restoreDOM() {
if (bufferedMutations.length > 0) {
bufferedMutations.reverse().forEach(function (mutation) {
if (mutation.type === 'characterData') {
// We don't want to restore the DOM for characterData mutations
// because this interrupts the composition.
return;
}
mutation.removedNodes.forEach(function (node) {
mutation.target.insertBefore(node, mutation.nextSibling);
});
mutation.addedNodes.forEach(function (node) {
mutation.target.removeChild(node);
});
});
// Clear buffered mutations to ensure we don't undo them twice
clear();
}
}
return {
registerMutations: registerMutations,
restoreDOM: restoreDOM,
clear: clear
};
};
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
var MUTATION_OBSERVER_CONFIG = {
subtree: true,
childList: true,
characterData: true,
characterDataOldValue: true
};
// We have to use a class component here since we rely on `getSnapshotBeforeUpdate` which has no FC equivalent
// to run code synchronously immediately before react commits the component update to the DOM.
var RestoreDOMComponent = /*#__PURE__*/function (_Component) {
_inherits(RestoreDOMComponent, _Component);
var _super = _createSuper(RestoreDOMComponent);
function RestoreDOMComponent() {
var _this;
_classCallCheck(this, RestoreDOMComponent);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _super.call.apply(_super, [this].concat(args));
_defineProperty(_assertThisInitialized(_this), "context", null);
_defineProperty(_assertThisInitialized(_this), "manager", null);
_defineProperty(_assertThisInitialized(_this), "mutationObserver", null);
return _this;
}
_createClass(RestoreDOMComponent, [{
key: "observe",
value: function observe() {
var _this$mutationObserve;
var node = this.props.node;
if (!node.current) {
throw new Error('Failed to attach MutationObserver, `node` is undefined');
}
(_this$mutationObserve = this.mutationObserver) === null || _this$mutationObserve === void 0 || _this$mutationObserve.observe(node.current, MUTATION_OBSERVER_CONFIG);
}
}, {
key: "componentDidMount",
value: function componentDidMount() {
var receivedUserInput = this.props.receivedUserInput;
var editor = this.context;
this.manager = createRestoreDomManager(editor, receivedUserInput);
this.mutationObserver = new MutationObserver(this.manager.registerMutations);
this.observe();
}
}, {
key: "getSnapshotBeforeUpdate",
value: function getSnapshotBeforeUpdate() {
var _this$mutationObserve2, _this$mutationObserve3, _this$manager2;
var pendingMutations = (_this$mutationObserve2 = this.mutationObserver) === null || _this$mutationObserve2 === void 0 ? void 0 : _this$mutationObserve2.takeRecords();
if (pendingMutations !== null && pendingMutations !== void 0 && pendingMutations.length) {
var _this$manager;
(_this$manager = this.manager) === null || _this$manager === void 0 || _this$manager.registerMutations(pendingMutations);
}
(_this$mutationObserve3 = this.mutationObserver) === null || _this$mutationObserve3 === void 0 || _this$mutationObserve3.disconnect();
(_this$manager2 = this.manager) === null || _this$manager2 === void 0 || _this$manager2.restoreDOM();
return null;
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate() {
var _this$manager3;
(_this$manager3 = this.manager) === null || _this$manager3 === void 0 || _this$manager3.clear();
this.observe();
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
var _this$mutationObserve4;
(_this$mutationObserve4 = this.mutationObserver) === null || _this$mutationObserve4 === void 0 || _this$mutationObserve4.disconnect();
}
}, {
key: "render",
value: function render() {
return this.props.children;
}
}]);
return RestoreDOMComponent;
}(React.Component);
_defineProperty(RestoreDOMComponent, "contextType", EditorContext);
var RestoreDOM = slateDom.IS_ANDROID ? RestoreDOMComponent : function (_ref) {
var children = _ref.children;
return /*#__PURE__*/React.createElement(React.Fragment, null, children);
};
/**
* A React context for sharing the `composing` state of the editor.
*/
var ComposingContext = /*#__PURE__*/React.createContext(false);
/**
* Get the current `composing` state of the editor.
*/
var useComposing = function useComposing() {
return React.useContext(ComposingContext);
};
var _excluded$1 = ["autoFocus", "decorate", "onDOMBeforeInput", "placeholder", "readOnly", "renderElement", "renderChunk", "renderLeaf", "renderText", "renderPlaceholder", "scrollSelectionIntoView", "style", "as", "disableDefaultStyles"],
_excluded2 = ["text"];
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(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(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); }
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; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var Children = function Children(props) {
return /*#__PURE__*/React.createElement(React.Fragment, null, useChildren(props));
};
/**
* Editable.
*/
var Editable = /*#__PURE__*/React.forwardRef(function (props, forwardedRef) {
var defaultRenderPlaceholder = React.useCallback(function (props) {
return /*#__PURE__*/React.createElement(DefaultPlaceholder, _objectSpread({}, props));
}, []);
var autoFocus = props.autoFocus,
_props$decorate = props.decorate,
decorate = _props$decorate === void 0 ? defaultDecorate : _props$decorate,
propsOnDOMBeforeInput = props.onDOMBeforeInput,
placeholder = props.placeholder,
_props$readOnly = props.readOnly,
readOnly = _props$readOnly === void 0 ? false : _props$readOnly,
renderElement = props.renderElement,
renderChunk = props.renderChunk,
renderLeaf = props.renderLeaf,
renderText = props.renderText,
_props$renderPlacehol = props.renderPlaceholder,
renderPlaceholder = _props$renderPlacehol === void 0 ? defaultRenderPlaceholder : _props$renderPlacehol,
_props$scrollSelectio = props.scrollSelectionIntoView,
scrollSelectionIntoView = _props$scrollSelectio === void 0 ? defaultScrollSelectionIntoView : _props$scrollSelectio,
_props$style = props.style,
userStyle = _props$style === void 0 ? {} : _props$style,
_props$as = props.as,
Component = _props$as === void 0 ? 'div' : _props$as,
_props$disableDefault = props.disableDefaultStyles,
disableDefaultStyles = _props$disableDefault === void 0 ? false : _props$disableDefault,
attributes = _objectWithoutProperties(props, _excluded$1);
var editor = useSlate();
// Rerender editor when composition status changed
var _useState = React.useState(false),
_useState2 = _slicedToArray(_useState, 2),
isComposing = _useState2[0],
setIsComposing = _useState2[1];
var ref = React.useRef(null);
var deferredOperations = React.useRef([]);
var _useState3 = React.useState(),
_useState4 = _slicedToArray(_useState3, 2),
placeholderHeight = _useState4[0],
setPlaceholderHeight = _useState4[1];
var processing = React.useRef(false);
var _useTrackUserInput = useTrackUserInput(),
onUserInput = _useTrackUserInput.onUserInput,
receivedUserInput = _useTrackUserInput.receivedUserInput;
var _useReducer = React.useReducer(function (s) {
return s + 1;
}, 0),
_useReducer2 = _slicedToArray(_useReducer, 2),
forceRender = _useReducer2[1];
slateDom.EDITOR_TO_FORCE_RENDER.set(editor, forceRender);
// Update internal state on each render.
slateDom.IS_READ_ONLY.set(editor, readOnly);
// Keep track of some state for the event handler logic.
var state = React.useMemo(function () {
return {
isDraggingInternally: false,
isUpdatingSelection: false,
latestElement: null,
hasMarkPlaceholder: false
};
}, []);
// The autoFocus TextareaHTMLAttribute doesn't do anything on a div, so it
// needs to be manually focused.
//
// If this stops working in Firefox, make sure nothing is causing this
// component to re-render during the initial mount. If the DOM selection is
// set by `useIsomorphicLayoutEffect` before `onDOMSelectionChange` updates
// `editor.selection`, the DOM selection can be removed accidentally.
React.useEffect(function () {
if (ref.current && autoFocus) {
ref.current.focus();
}
}, [autoFocus]);
/**
* The AndroidInputManager object has a cyclical dependency on onDOMSelectionChange
*
* It is defined as a reference to simplify hook dependencies and clarify that
* it needs to be initialized.
*/
var androidInputManagerRef = React.useRef();
// Listen on the native `selectionchange` event to be able to update any time
// the selection changes. This is required because React's `onSelect` is leaky
// and non-standard so it doesn't fire until after a selection has been
// released. This causes issues in situations where another change happens
// while a selection is being dragged.
var onDOMSelectionChange = React.useMemo(function () {
return throttle(function () {
if (slateDom.IS_NODE_MAP_DIRTY.get(editor)) {
onDOMSelectionChange();
return;
}
var el = ReactEditor.toDOMNode(editor, editor);
var root = el.getRootNode();
if (!processing.current && slateDom.IS_WEBKIT && root instanceof ShadowRoot) {
processing.current = true;
var active = slateDom.getActiveElement();
if (active) {
document.execCommand('indent');
} else {
slate.Transforms.deselect(editor);
}
processing.current = false;
return;
}
var androidInputManager = androidInputManagerRef.current;
if ((slateDom.IS_ANDROID || !ReactEditor.isComposing(editor)) && (!state.isUpdatingSelection || androidInputManager !== null && androidInputManager !== void 0 && androidInputManager.isFlushing()) && !state.isDraggingInternally) {
var _root = ReactEditor.findDocumentOrShadowRoot(editor);
var activeElement = _root.activeElement;
var _el = ReactEditor.toDOMNode(editor, editor);
var domSelection = slateDom.getSelection(_root);
if (activeElement === _el) {
state.latestElement = activeElement;
slateDom.IS_FOCUSED.set(editor, true);
} else {
slateDom.IS_FOCUSED["delete"](editor);
}
if (!domSelection) {
return slate.Transforms.deselect(editor);
}
var anchorNode = domSelection.anchorNode,
focusNode = domSelection.focusNode;
var anchorNodeSelectable = ReactEditor.hasEditableTarget(editor, anchorNode) || ReactEditor.isTargetInsideNonReadonlyVoid(editor, anchorNode);
var focusNodeInEditor = ReactEditor.hasTarget(editor, focusNode);
if (anchorNodeSelectable && focusNodeInEditor) {
var range = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false,
suppressThrow: true
});
if (range) {
if (!ReactEditor.isComposing(editor) && !(androidInputManager !== null && androidInputManager !== void 0 && androidInputManager.hasPendingChanges()) && !(androidInputManager !== null && androidInputManager !== void 0 && androidInputManager.isFlushing())) {
slate.Transforms.select(editor, range);
} else {
androidInputManager === null || androidInputManager === void 0 || androidInputManager.handleUserSelect(range);
}
}
}
// Deselect the editor if the dom selection is not selectable in readonly mode
if (readOnly && (!anchorNodeSelectable || !focusNodeInEditor)) {
slate.Transforms.deselect(editor);
}
}
}, 100);
}, [editor, readOnly, state]);
var scheduleOnDOMSelectionChange = React.useMemo(function () {
return debounce(onDOMSelectionChange, 0);
}, [onDOMSelectionChange]);
androidInputManagerRef.current = useAndroidInputManager({
node: ref,
onDOMSelectionChange: onDOMSelectionChange,
scheduleOnDOMSelectionChange: scheduleOnDOMSelectionChange
});
useIsomorphicLayoutEffect(function () {
var _androidInputManagerR, _androidInputManagerR2;
// Update element-related weak maps with the DOM element ref.
var window;
if (ref.current && (window = slateDom.getDefaultView(ref.current))) {
slateDom.EDITOR_TO_WINDOW.set(editor, window);
slateDom.EDITOR_TO_ELEMENT.set(editor, ref.current);
slateDom.NODE_TO_ELEMENT.set(editor, ref.current);
slateDom.ELEMENT_TO_NODE.set(ref.current, editor);
} else {
slateDom.NODE_TO_ELEMENT["delete"](editor);
}
// Make sure the DOM selection state is in sync.
var selection = editor.selection;
var root = ReactEditor.findDocumentOrShadowRoot(editor);
var domSelection = slateDom.getSelection(root);
if (!domSelection || !ReactEditor.isFocused(editor) || (_androidInputManagerR = androidInputManagerRef.current) !== null && _androidInputManagerR !== void 0 && _androidInputManagerR.hasPendingAction()) {
return;
}
var setDomSelection = function setDomSelection(forceChange) {
var hasDomSelection = domSelection.type !== 'None';
// If the DOM selection is properly unset, we're done.
if (!selection && !hasDomSelection) {
return;
}
// Get anchorNode and focusNode
var focusNode = domSelection.focusNode;
var anchorNode;
// COMPAT: In firefox the normal selection way does not work
// (https://github.com/ianstormtaylor/slate/pull/5486#issue-1820720223)
if (slateDom.IS_FIREFOX && domSelection.rangeCount > 1) {
var firstRange = domSelection.getRangeAt(0);
var lastRange = domSelection.getRangeAt(domSelection.rangeCount - 1);
// Right to left
if (firstRange.startContainer === focusNode) {
anchorNode = lastRange.endContainer;
} else {
// Left to right
anchorNode = firstRange.startContainer;
}
} else {
anchorNode = domSelection.anchorNode;
}
// verify that the dom selection is in the editor
var editorElement = slateDom.EDITOR_TO_ELEMENT.get(editor);
var hasDomSelectionInEditor = false;
if (slateDom.containsShadowAware(editorElement, anchorNode) && slateDom.containsShadowAware(editorElement, focusNode)) {
hasDomSelectionInEditor = true;
}
// If the DOM selection is in the editor and the editor selection is already correct, we're done.
if (hasDomSelection && hasDomSelectionInEditor && selection && !forceChange) {
var slateRange = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: true,
// domSelection is not necessarily a valid Slate range
// (e.g. when clicking on contentEditable:false element)
suppressThrow: true
});
if (slateRange && slate.Range.equals(slateRange, selection)) {
var _anchorNode;
if (!state.hasMarkPlaceholder) {
return;
}
// Ensure selection is inside the mark placeholder
if ((_anchorNode = anchorNode) !== null && _anchorNode !== void 0 && (_anchorNode = _anchorNode.parentElement) !== null && _anchorNode !== void 0 && _anchorNode.hasAttribute('data-slate-mark-placeholder')) {
return;
}
}
}
// when <Editable/> is being controlled through external value
// then its children might just change - DOM responds to it on its own
// but Slate's value is not being updated through any operation
// and thus it doesn't transform selection on its own
if (selection && !ReactEditor.hasRange(editor, selection)) {
editor.selection = ReactEditor.toSlateRange(editor, domSelection, {
exactMatch: false,
suppressThrow: true
});
return;
}
// Otherwise the DOM selection is out of sync, so update it.
state.isUpdatingSelection = true;
var newDomRange = null;
try {
newDomRange = selection && ReactEditor.toDOMRange(editor, selection);
} catch (e) {
// Ignore, dom and state might be out of sync
}
if (newDomRange) {
if (ReactEditor.isComposing(editor) && !slateDom.IS_ANDROID) {
if (domSelection.rangeCount > 0) {
domSelection.collapseToEnd();
}
} else if (slate.Range.isBackward(selection)) {
domSelection.setBaseAndExtent(newDomRange.endContainer, newDomRange.endOffset, newDomRange.startContainer, newDomRange.startOffset);
} else {
domSelection.setBaseAndExtent(newDomRange.startContainer, newDomRange.startOffset, newDomRange.endContainer, newDomRange.endOffset);
}
scrollSelectionIntoView(editor, newDomRange);
} else {
domSelection.removeAllRanges();
}
return newDomRange;
};
// In firefox if there is more then 1 range and we call setDomSelection we remove the ability to select more cells in a table
if (domSelection.rangeCount <= 1) {
setDomSelection();
}
var ensureSelection = ((_androidInputManagerR2 = androidInputManagerRef.current) === null || _androidInputManagerR2 === void 0 ? void 0 : _androidInputManagerR2.isFlushing()) === 'action';
if (!slateDom.IS_ANDROID || !ensureSelection) {
setTimeout(function () {
state.isUpdatingSelection = false;
});
return;
}
var timeoutId = null;
var animationFrameId = requestAnimationFrame(function () {
if (ensureSelection) {
var ensureDomSelection = function ensureDomSelection(forceChange) {
try {
var el = ReactEditor.toDOMNode(editor, editor);
el.focus();
setDomSelection(forceChange);
} catch (e) {
// Ignore, dom and state might be out of sync
}
};
// Compat: Android IMEs try to force their selection by manually re-applying it even after we set it.
// This essentially would make setting the slate selection during an update meaningless, so we force it
// again here. We can't only do it in the setTimeout after the animation frame since that would cause a
// visible flicker.
ensureDomSelection();
timeoutId = setTimeout(function () {
// COMPAT: While setting the selection in an animation frame visually correctly sets the selection,
// it doesn't update GBoards spellchecker state. We have to manually trigger a selection change after
// the animation frame to ensure it displays the correct state.
ensureDomSelection(true);
state.isUpdatingSelection = false;
});
}
});
return function () {
cancelAnimationFrame(animationFrameId);
if (timeoutId) {
clearTimeout(timeoutId);
}
};
});
// Listen on the native `beforeinput` event to get real "Level 2" events. This
// is required because React's `beforeinput` is fake and never really attaches
// to the real event sadly. (2019/11/01)
// https://github.com/facebook/react/issues/11211
var onDOMBeforeInput = React.useCallback(function (event) {
handleNativeHistoryEvents(editor, event);
var el = ReactEditor.toDOMNode(editor, editor);
var root = el.getRootNode();
if (processing !== null && processing !== void 0 && processing.current && slateDom.IS_WEBKIT && root instanceof ShadowRoot) {
var ranges = event.getTargetRanges();
var range = ranges[0];
var newRange = new window.Range();
newRange.setStart(range.startContainer, range.startOffset);
newRange.setEnd(range.endContainer, range.endOffset);
// Unresolvable ranges would throw out of the handler (#3556); suppress and skip the move.
var slateRange = ReactEditor.toSlateRange(editor, newRange, {
exactMatch: false,
suppressThrow: true
});
if (slateRange) {
slate.Transforms.select(editor, slateRange);
}
event.preventDefault();
event.stopImmediatePropagation();
return;
}
onUserInput();
if (!readOnly && ReactEditor.hasEditableTarget(editor, event.target) && !isDOMEventHandled(event, propsOnDOMBeforeInput)) {
var _EDITOR_TO_USER_SELEC;
// COMPAT: BeforeInput events aren't cancelable on android, so we have to handle them differently using the android input manager.
if (androidInputManagerRef.current) {
return androidInputManagerRef.current.handleDOMBeforeInput(event);
}
// Some IMEs/Chrome extensions like e.g. Grammarly set the selection immediately before
// triggering a `beforeinput` expecting the change to be applied to the immediately before
// set selection.
scheduleOnDOMSelectionChange.flush();
onDOMSelectionChange.flush();
var selection = editor.selection;
var type = event.inputType;
var data = event.dataTransfer || event.data || undefined;
var isCompositionChange = type === 'insertCompositionText' || type === 'deleteCompositionText';
// COMPAT: use composition change events as a hint to where we should insert
// composition text if we aren't composing to work around https://github.com/ianstormtaylor/slate/issues/5038
if (isCompositionChange && ReactEditor.isComposing(editor)) {
return;
}
var _native = false;
if (type === 'insertText' && selection && slate.Range.isCollapsed(selection) &&
// Only use native character insertion for single characters a-z or space for now.
// Long-press events (hold a + press 4 = ä) to choose a special character otherwise
// causes duplicate inserts.
event.data && event.data.length === 1 && /[a-z ]/i.test(event.data) &&
// Chrome has issues correctly editing the start of nodes: https://bugs.chromium.org/p/chromium/issues/detail?id=1249405
// When there is an inline element, e.g. a link, and you select
// right after it (the start of the next node).
selection.anchor.offset !== 0) {
_native = true;
// Skip native if there are marks, as
// `insertText` will insert a node, not just text.
if (editor.marks) {
_native = false;
}
// If the NODE_MAP is dirty, we can't trust the selection anchor (eg ReactEditor.toDOMPoint)
if (!slateDom.IS_NODE_MAP_DIRTY.get(editor)) {
var _node$parentElement, _window$getComputedSt;
// Chrome also has issues correctly editing the end of anchor elements: https://bugs.chromium.org/p/chromium/issues/detail?id=1259100
// Therefore we don't allow native events to insert text at the end of anchor nodes.
var anchor = selection.anchor;
var _ReactEditor$toDOMPoi = ReactEditor.toDOMPoint(editor, anchor),
_ReactEditor$toDOMPoi2 = _slicedToArray(_ReactEditor$toDOMPoi, 2),
node = _ReactEditor$toDOMPoi2[0],
offset = _ReactEditor$toDOMPoi2[1];
var anchorNode = (_node$parentElement = node.parentElement) === null || _node$parentElement === void 0 ? void 0 : _node$parentElement.closest('a');
var _window = ReactEditor.getWindow(editor);
if (_native && anchorNode && ReactEditor.hasDOMNode(editor, anchorNode)) {
var _lastText$textContent;
// Find the last text node inside the anchor.
var lastText = _window === null || _window === void 0 ? void 0 : _window.document.createTreeWalker(anchorNode, NodeFilter.SHOW_TEXT).lastChild();
if (lastText === node && ((_lastText$textContent = lastText.textContent) === null || _lastText$textContent === void 0 ? void 0 : _lastText$textContent.length) === offset) {
_native = false;
}
}
// Chrome has issues with the presence of tab characters inside elements with whiteSpace = 'pre'
// causing abnormal insert behavior: https://bugs.chromium.org/p/chromium/issues/detail?id=1219139
if (_native && node.parentElement && (_window === null || _window === void 0 || (_window$getComputedSt = _window.getComputedStyle(node.parentElement)) === null || _window$getComputedSt === void 0 ? void 0 : _window$getComputedSt.whiteSpace) === 'pre') {
var block = slate.Editor.above(editor, {
at: anchor.path,
match: function match(n) {
return slate.Node.isElement(n) && slate.Editor.isBlock(editor, n);
}
});
if (block && slate.Node.string(block[0]).includes('\t')) {
_native = false;
}
}
}
}
// COMPAT: For the deleting forward/backward input types we don't want
// to change the selection because it is the range that will be deleted,
// and those commands determine that for themselves.
// If the NODE_MAP is dirty, we can't trust the selection anchor (eg ReactEditor.toDOMPoint via ReactEditor.toSlateRange)
if ((!type.startsWith('delete') || type.startsWith('deleteBy')) && !slateDom.IS_NODE_MAP_DIRTY.get(editor)) {
var _event$getTargetRange = event.getTargetRanges(),
_event$getTargetRange2 = _slicedToArray(_event$getTargetRange, 1),
targetRange = _event$getTargetRange2[0];
if (targetRange) {
// Unresolvable ranges would throw out of the handler (#3556); suppress and fall back to synthetic handling.
var _range = ReactEditor.toSlateRange(editor, targetRange, {
exactMatch: false,
suppressThrow: true
});
if (!_range) {
_native = false;
} else if (!selection || !slate.Range.equals(selection, _range)) {
_native = false;
var selectionRef = !isCompositionChange && editor.selection && slate.Editor.rangeRef(editor, editor.selection);
slate.Transforms.select(editor, _range);
if (selectionRef) {
slateDom.EDITOR_TO_USER_SELECTION.set(editor, selectionRef);
}
}
}
}
// Composition change types occur while a user is composing text and can't be
// cancelled. Let them through and wait for the composition to end.
if (isCompositionChange) {
return;
}
if (!_native) {
event.preventDefault();
}
// COMPAT: If the selection is expanded, even if the command seems like
// a delete forward/backward command it should delete the selection.
if (selection && slate.Range.isExpanded(selection) && type.startsWith('delete')) {
var direction = type.endsWith('Backward') ? 'backward' : 'forward';
slate.Editor.deleteFragment(editor, {
direction: direction
});
return;
}
switch (type) {
case 'deleteByComposition':
case 'deleteByCut':
case 'deleteByDrag':
{
slate.Editor.deleteFragment(editor);
break;
}
case 'deleteContent':
case 'deleteContentForward':
{
slate.Editor.deleteForward(editor);
break;
}
case 'deleteContentBackward':
{
slate.Editor.deleteBackward(editor);
break;
}
case 'deleteEntireSoftLine':
{
slate.Editor.deleteBackward(editor, {
unit: 'line'
});
slate.Editor.deleteForward(editor, {
unit: 'line'
});
break;
}
case 'deleteHardLineBackward':
{
slate.Editor.deleteBackward(editor, {
unit: 'block'
});
break;
}
case 'deleteSoftLineBackward':
{
slate.Editor.deleteBackward(editor, {
unit: 'line'
});
break;
}
case 'deleteHardLineForward':
{
slate.Editor.deleteForward(editor, {
unit: 'block'
});
break;
}
case 'deleteSoftLineForward':
{
slate.Editor.deleteForward(editor, {
unit: 'line'
});
break;
}
case 'deleteWordBackward':
{
slate.Editor.deleteBackward(editor, {
unit: 'word'
});
break;
}
case 'deleteWordForward':
{
slate.Editor.deleteForward(editor, {
unit: 'word'
});
break;
}
case 'insertLineBreak':
slate.Editor.insertSoftBreak(editor);
break;
case 'insertParagraph':
{
slate.Editor.insertBreak(editor);
break;
}
case 'insertFromComposition':
case 'insertFromDrop':
case 'insertFromPaste':
case 'insertFromYank':
case 'insertReplacementText':
case 'insertText':
{
if (type === 'insertFromComposition') {
// COMPAT: in Safari, `compositionend` is dispatched after the
// `beforeinput` for "insertFromComposition". But if we wait for it
// then we will abort because we're still composing and the selection
// won't be updated properly.
// https://www.w3.org/TR/input-events-2/
if (ReactEditor.isComposing(editor)) {
setIsComposing(false);
slateDom.IS_COMPOSING.set(editor, false);
}
}
// use a weak comparison instead of 'instanceof' to allow
// programmatic access of paste events coming from external windows
// like cypress where cy.window does not work realibly
if ((data === null || data === void 0 ? void 0 : data.constructor.name) === 'DataTransfer') {
ReactEditor.insertData(editor, data);
} else if (typeof data === 'string') {
// Only insertText operations use the native functionality, for now.
// Potentially expand to single character deletes, as well.
if (_native) {
deferredOperations.current.push(function () {
return slate.Editor.insertText(editor, data);
});
} else {
slate.Editor.insertText(editor, data);
}
}
break;
}
}
// Restore the actual user section if nothing manually set it.
var toRestore = (_EDITOR_TO_USER_SELEC = slateDom.EDITOR_TO_USER_SELECTION.get(editor)) === null || _EDITOR_TO_USER_SELEC === void 0 ? void 0 : _EDITOR_TO_USER_SELEC.unref();
slateDom.EDITOR_TO_USER_SELECTION["delete"](editor);
if (toRestore && (!editor.selection || !slate.Range.equals(editor.selection, toRestore))) {
slate.Transforms.select(editor, toRestore);
}
}
}, [editor, onDOMSelectionChange, onUserInput, propsOnDOMBeforeInput, readOnly, scheduleOnDOMSelectionChange]);
var callbackRef = React.useCallback(function (node) {
if (node == null) {
onDOMSelectionChange.cancel();
scheduleOnDOMSelectionChange.cancel();
slateDom.EDITOR_TO_ELEMENT["delete"](editor);
slateDom.NODE_TO_ELEMENT["delete"](editor);
if (ref.current && slateDom.HAS_BEFORE_INPUT_SUPPORT) {
// @ts-ignore The `beforeinput` event isn't recognized.
ref.current.removeEventListener('beforeinput', onDOMBeforeInput);
}
} else {
// Attach a native DOM event handler for `beforeinput` events, because React's
// built-in `onBeforeInput` is actually a leaky polyfill that doesn't expose
// real `beforeinput` events sadly... (2019/11/04)
// https://github.com/facebook/react/issues/11211
if (slateDom.HAS_BEFORE_INPUT_SUPPORT) {
// @ts-ignore The `beforeinput` event isn't recognized.
node.addEventListener('beforeinput', onDOMBeforeInput);
}
}
ref.current = node;
if (typeof forwardedRef === 'function') {
forwardedRef(node);
} else if (forwardedRef) {
forwardedRef.current = node;
}
}, [onDOMSelectionChange, scheduleOnDOMSelectionChange, editor, onDOMBeforeInput, forwardedRef]);
useIsomorphicLayoutEffect(function () {
var window = ReactEditor.getWindow(editor);
// COMPAT: In Chrome, `selectionchange` events can fire when <input> and
// <textarea> elements are appended to the DOM, causing
// `editor.selection` to be overwritten in some circumstances.
// (2025/01/16) https://issues.chromium.org/issues/389368412
var onSelectionChange = function onSelectionChange(_ref) {
var target = _ref.target;
var targetElement = target instanceof HTMLElement ? target : null;
var targetTagName = targetElement === null || targetElement === void 0 ? void 0 : targetElement.tagName;
if (targetTagName === 'INPUT' || targetTagName === 'TEXTAREA') {
return;
}
scheduleOnDOMSelectionChange();
};
// Attach a native DOM event handler for `selectionchange`, because React's
// built-in `onSelect` handler doesn't fire for all selection changes. It's
// a leaky polyfill that only fires on keypresses or clicks. Instead, we
// want to fire for any change to the selection inside the editor.
// (2019/11/04) https://github.com/facebook/react/issues/5785
window.document.addEventListener('selectionchange', onSelectionChange);
// Listen for dragend and drop globally. In Firefox, if a drop handler
// initiates an operation that causes the originally dragged element to
// unmount, that element will not emit a dragend event. (2024/06/21)
var stoppedDragging = function stoppedDragging() {
state.isDraggingInternally = false;
};
window.document.addEventListener('dragend', stoppedDragging);
window.document.addEventListener('drop', stoppedDragging);
return function () {
window.document.removeEventListener('selectionchange', onSelectionChange);
window.document.removeEventListener('dragend', stoppedDragging);
window.document.removeEventListener('drop', stoppedDragging);
};
}, [scheduleOnDOMSelectionChange, state]);
var decorations = decorate([editor, []]);
var decorateContext = useDecorateContext(decorate);
var showPlaceholder = placeholder && editor.children.length === 1 && Array.from(slate.Node.texts(editor)).length === 1 && slate.Node.string(editor) === '' && !isComposing;
var placeHolderResizeHandler = React.useCallback(function (placeholderEl) {
if (placeholderEl && showPlaceholder) {
var _placeholderEl$getBou;
setPlaceholderHeight((_placeholderEl$getBou = placeholderEl.getBoundingClientRect()) === null || _placeholderEl$getBou === void 0 ? void 0 : _placeholderEl$getBou.height);
} else {
setPlaceholderHeight(undefined);
}
}, [showPlaceholder]);
if (showPlaceholder) {
var _decorations$push;
var start = slate.Editor.start(editor, []);
decorations.push((_decorations$push = {}, _defineProperty(_decorations$push, slateDom.PLACEHOLDER_SYMBOL, true), _defineProperty(_decorations$push, "placeholder", placeholder), _defineProperty(_decorations$push, "onPlaceholderResize", placeHolderResizeHandler), _defineProperty(_decorations$push, "anchor", start), _defineProperty(_decorations$push, "focus", start), _decorations$push));
}
var marks = editor.marks;
state.hasMarkPlaceholder = false;
if (editor.selection && slate.Range.isCollapsed(editor.selection) && marks) {
var anchor = editor.selection.anchor;
var leaf = slate.Node.leaf(editor, anchor.path);
leaf.text;
var rest = _objectWithoutProperties(leaf, _excluded2);
// While marks isn't a 'complete' text, we can still use loose Text.equals
// here which only compares marks anyway.
if (!slate.Text.equals(leaf, marks, {
loose: true
})) {
state.hasMarkPlaceholder = true;
var unset = Object.fromEntries(Object.keys(rest).map(function (mark) {
return [mark, null];
}));
decorations.push(_objectSpread(_objectSpread(_objectSpread(_defineProperty({}, slateDom.MARK_PLACEHOLDER_SYMBOL, true), unset), marks), {}, {
anchor: anchor,
focus: anchor
}));
}
}
// Update EDITOR_TO_MARK_PLACEHOLDER_MARKS in setTimeout useEffect to ensure we don't set it
// before we receive the composition end event.
React.useEffect(function () {
setTimeout(function () {
var selection = editor.selection;
if (selection) {
var _anchor = selection.anchor;
var _text = slate.Node.leaf(editor, _anchor.path);
// While marks isn't a 'complete' text, we can still use loose Text.equals
// here which only compares marks anyway.
if (marks && !slate.Text.equals(_text, marks, {
loose: true
})) {
slateDom.EDITOR_TO_PENDING_INSERTION_MARKS.set(editor, marks);
return;
}
}
slateDom.EDITOR_TO_PENDING_INSERTION_MARKS["delete"](editor);
});
});
useFlushDeferredSelectorsOnRender();
return /*#__PURE__*/React.createElement(ReadOnlyContext.Provider, {
value: readOnly
}, /*#__PURE__*/React.createElement(ComposingContext.Provider, {
value: isComposing
}, /*#__PURE__*/React.createElement(DecorateContext.Provider, {
value: decorateContext
}, /*#__PURE__*/React.createElement(RestoreDOM, {
node: ref,
receivedUserInput: receivedUserInput
}, /*#__PURE__*/React.createElement(Component, _objectSpread(_objectSpread({
role: readOnly ? undefined : 'textbox',
"aria-multiline": readOnly ? undefined : true,
translate: "no"
}, attributes), {}, {
// COMPAT: Certain browsers don't support the `beforeinput` event, so we'd
// have to use hacks to make these replacement-based features work.
// For SSR situations HAS_BEFORE_INPUT_SUPPORT is false and results in prop
// mismatch warning app moves to browser. Pass-through consumer props when
// not CAN_USE_DOM (SSR) and default to falsy value
spellCheck: slateDom.HAS_BEFORE_INPUT_SUPPORT || !slateDom.CAN_USE_DOM ? attributes.spellCheck : false,
autoCorrect: slateDom.HAS_BEFORE_INPUT_SUPPORT || !slateDom.CAN_USE_DOM ? attributes.autoCorrect : 'false',
autoCapitalize: slateDom.HAS_BEFORE_INPUT_SUPPORT || !slateDom.CAN_USE_DOM ? attributes.autoCapitalize : 'false',
"data-slate-editor": true,
"data-slate-node": "value",
// explicitly set this
contentEditable: !readOnly,
// in some cases, a decoration needs access to the range / selection to decorate a text node,
// then you will select the whole text node when you select part the of text
// this magic zIndex="-1" will fix it
zindex: -1,
suppressContentEditableWarning: true,
ref: callbackRef,
style: _objectSpread(_objectSpread({}, disableDefaultStyles ? {} : _objectSpread({
// Allow positioning relative to the editable element.
position: 'relative',
// Preserve adjacent whitespace and new lines.
whiteSpace: 'pre-wrap',
// Allow words to break if they are too long.
wordWrap: 'break-word'
}, placeholderHeight ? {
minHeight: placeholderHeight
} : {})), userStyle),
onBeforeInput: React.useCallback(function (event) {
// COMPAT: Certain browsers don't support the `beforeinput` event, so we
// fall back to React's leaky polyfill instead just for it. It
// only works for the `insertText` input type.
if (!slateDom.HAS_BEFORE_INPUT_SUPPORT && !readOnly && !isEventHandled(event, attributes.onBeforeInput) && ReactEditor.hasSelectableTarget(editor, event.target)) {
event.preventDefault();
if (!ReactEditor.isComposing(editor)) {
var _text2 = event.data;
slate.Editor.insertText(editor, _text2);
}
}
}, [attributes.onBeforeInput, editor, readOnly]),
onInput: React.useCallback(function (event) {
if (isEventHandled(event, attributes.onInput)) {
return;
}
if (androidInputManagerRef.current) {
androidInputManagerRef.current.handleInput();
return;
}
// Flush native operations, as native events will have propogated
// and we can correctly compare DOM text values in components
// to stop rendering, so that browser functions like autocorrect
// and spellcheck work as expected.
var _iterator = _createForOfIteratorHelper(deferredOperations.current),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var op = _step.value;
op();
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
deferredOperations.current = [];
// COMPAT: Since `beforeinput` doesn't fully `preventDefault`,
// there's a chance that content might be placed in the browser's undo stack.
// This means undo can be triggered even when the div is not focused,
// and it only triggers the input event for the node. (2024/10/09)
if (!ReactEditor.isFocused(editor)) {
handleNativeHistoryEvents(editor, event.nativeEvent);
}
}, [attributes.onInput, editor]),
onBlur: React.useCallback(function (event) {
if (readOnly || state.isUpdatingSelection || !ReactEditor.hasSelectableTarget(editor, event.target) || isEventHandled(event, attributes.onBlur)) {
return;
}
// COMPAT: If the current `activeElement` is still the previous
// one, this is due to the window being blurred when the tab
// itself becomes unfocused, so we want to abort early to allow to
// editor to stay focused when the tab becomes focused again.
var root = ReactEditor.findDocumentOrShadowRoot(editor);
if (state.latestElement === root.activeElement) {
return;
}
var relatedTarget = event.relatedTarget;
var el = ReactEditor.toDOMNode(editor, editor);
// COMPAT: The event should be ignored if the focus is returning
// to the editor from an embedded editable element (eg. an <input>
// element inside a void node).
if (relatedTarget === el) {
return;
}
// COMPAT: The event should be ignored if the focus is moving from
// the editor to inside a void node's spacer element.
if (slateDom.isDOMElement(relatedTarget) && relatedTarget.hasAttribute('data-slate-spacer')) {
return;
}
// COMPAT: The event should be ignored if the focus is moving to a
// non- editable section of an element that isn't a void node (eg.
// a list item of the check list example).
if (relatedTarget != null && slateDom.isDOMNode(relatedTarget) && ReactEditor.hasDOMNode(editor, relatedTarget)) {
var node = ReactEditor.toSlateNode(editor, relatedTarget);
if (slate.Node.isElement(node) && !editor.isVoid(node)) {
return;
}
}
// COMPAT: Safari doesn't always remove the selection even if the content-
// editable element no longer has focus. Refer to:
// https://stackoverflow.com/questions/12353247/force-contenteditable-div-to-stop-accepting-input-after-it-loses-focus-under-web
if (slateDom.IS_WEBKIT) {
var domSelection = slateDom.getSelection(root);
domSelection === null || domSelection === void 0 || domSelection.removeAllRanges();
}
slateDom.IS_FOCUSED["delete"](editor);
}, [readOnly, state.isUpdatingSelection, state.latestElement, editor, attributes.onBlur]),
onClick: React.useCallback(function (event) {
if (ReactEditor.hasTarget(editor, event.target) && !isEventHandled(event, attributes.onClick) && slateDom.isDOMNode(event.target)) {
var node = ReactEditor.toSlateNode(editor, event.target);
var path = ReactEditor.findPath(editor, node);
// At this time, the Slate document may be arbitrarily different,
// because onClick handlers can change the document before we get here.
// Therefore we must check that this path actually exists,
// and that it still refers to the same node.
if (!slate.Editor.hasPath(editor, path) || slate.Node.get(editor, path) !== node) {
return;
}
if (event.detail === slateDom.TRIPLE_CLICK && path.length >= 1) {
var blockPath = path;
if (!(slate.Node.isElement(node) && slate.Editor.isBlock(editor, node))) {
var _block$;
var block = slate.Editor.above(editor, {
match: function match(n) {
return slate.Node.isElement(n) && slate.Editor.isBlock(editor, n);
},
at: path
});
blockPath = (_block$ = block === null || block === void 0 ? void 0 : block[1]) !== null && _block$ !== void 0 ? _block$ : path.slice(0, 1);
}
var range = slate.Editor.range(editor, blockPath);
slate.Transforms.select(editor, range);
return;
}
if (readOnly) {
return;
}
var _start = slate.Editor.start(editor, path);
var end = slate.Editor.end(editor, path);
var startVoid = slate.Editor["void"](editor, {
at: _start
});
var endVoid = slate.Editor["void"](editor, {
at: end
});
if (startVoid && endVoid && slate.Path.equals(startVoid[1], endVoid[1])) {
var _range2 = slate.Editor.range(editor, _start);
slate.Transforms.select(editor, _range2);
}
}
}, [editor, attributes.onClick, readOnly]),
onCompositionEnd: React.useCallback(function (event) {
if (isDOMEventTargetInput(event)) {
return;
}
if (ReactEditor.hasSelectableTarget(editor, event.target)) {
var _androidInputManagerR3;
if (ReactEditor.isComposing(editor)) {
Promise.resolve().then(function () {
setIsComposing(false);
slateDom.IS_COMPOSING.set(editor, false);
});
}
(_androidInputManagerR3 = androidInputManagerRef.current) === null || _androidInputManagerR3 === void 0 || _androidInputManagerR3.handleCompositionEnd(event);
if (isEventHandled(event, attributes.onCompositionEnd) || slateDom.IS_ANDROID) {
return;
}
// COMPAT: In Chrome, `beforeinput` events for compositions
// aren't correct and never fire the "insertFromComposition"
// type that we need. So instead, insert whenever a composition
// ends since it will already have been committed to the DOM.
if (!slateDom.IS_WEBKIT && !slateDom.IS_FIREFOX_LEGACY && !slateDom.IS_IOS && !slateDom.IS_WECHATBROWSER && !slateDom.IS_UC_MOBILE && event.data) {
var placeholderMarks = slateDom.EDITOR_TO_PENDING_INSERTION_MARKS.get(editor);
slateDom.EDITOR_TO_PENDING_INSERTION_MARKS["delete"](editor);
// Ensure we insert text with the marks the user was actually seeing
if (placeholderMarks !== undefined) {
slateDom.EDITOR_TO_USER_MARKS.set(editor, editor.marks);
editor.marks = placeholderMarks;
}
slate.Editor.insertText(editor, event.data);
var userMarks = slateDom.EDITOR_TO_USER_MARKS.get(editor);
slateDom.EDITOR_TO_USER_MARKS["delete"](editor);
if (userMarks !== undefined) {
editor.marks = userMarks;
}
}
}
}, [attributes.onCompositionEnd, editor]),
onCompositionUpdate: React.useCallback(function (event) {
if (ReactEditor.hasSelectableTarget(editor, event.target) && !isEventHandled(event, attributes.onCompositionUpdate) && !isDOMEventTargetInput(event)) {
if (!ReactEditor.isComposing(editor)) {
setIsComposing(true);
slateDom.IS_COMPOSING.set(editor, true);
}
}
}, [attributes.onCompositionUpdate, editor]),
onCompositionStart: React.useCallback(function (event) {
if (isDOMEventTargetInput(event)) {
return;
}
if (ReactEditor.hasSelectableTarget(editor, event.target)) {
var _androidInputManagerR4;
(_androidInputManagerR4 = androidInputManagerRef.current) === null || _androidInputManagerR4 === void 0 || _androidInputManagerR4.handleCompositionStart(event);
if (isEventHandled(event, attributes.onCompositionStart) || slateDom.IS_ANDROID) {
return;
}
setIsComposing(true);
var selection = editor.selection;
if (selection && slate.Range.isExpanded(selection)) {
slate.Editor.deleteFragment(editor);
return;
}
}
}, [attributes.onCompositionStart, editor]),
onCopy: React.useCallback(function (event) {
if (ReactEditor.hasSelectableTarget(editor, event.target) && !isEventHandled(event, attributes.onCopy) && !isDOMEventTargetInput(event)) {
event.preventDefault();
ReactEditor.setFragmentData(editor, event.clipboardData, 'copy');
}
}, [attributes.onCopy, editor]),
onCut: React.useCallback(function (event) {
if (!readOnly && ReactEditor.hasSelectableTarget(editor, event.target) && !isEventHandled(event, attributes.onCut) && !isDOMEventTargetInput(event)) {
event.preventDefault();
ReactEditor.setFragmentData(editor, event.clipboardData, 'cut');
var selection = editor.selection;
if (selection) {
if (slate.Range.isExpanded(selection)) {
slate.Editor.deleteFragment(editor);
} else {
var node = slate.Node.parent(editor, selection.anchor.path);
if (slate.Editor.isVoid(editor, node)) {
slate.Transforms["delete"](editor);
}
}
}
}
}, [readOnly, editor, attributes.onCut]),
onDragOver: React.useCallback(function (event) {
if (ReactEditor.hasTarget(editor, event.target) && !isEventHandled(event, attributes.onDragOver)) {
// Only when the target is void, call `preventDefault` to signal
// that drops are allowed. Editable content is droppable by
// default, and calling `preventDefault` hides the cursor.
var node = ReactEditor.toSlateNode(editor, event.target);
if (slate.Node.isElement(node) && slate.Editor.isVoid(editor, node)) {
event.preventDefault();
}
}
}, [attributes.onDragOver, editor]),
onDragStart: React.useCallback(function (event) {
if (!readOnly && ReactEditor.hasTarget(editor, event.target) && !isEventHandled(event, attributes.onDragStart)) {
var node = ReactEditor.toSlateNode(editor, event.target);
var path = ReactEditor.findPath(editor, node);
var voidMatch = slate.Node.isElement(node) && slate.Editor.isVoid(editor, node) || slate.Editor["void"](editor, {
at: path,
voids: true
});
// If starting a drag on a void node, make sure it is selected
// so that it shows up in the selection's fragment.
if (voidMatch) {
var range = slate.Editor.range(editor, path);
slate.Transforms.select(editor, range);
}
state.isDraggingInternally = true;
ReactEditor.setFragmentData(editor, event.dataTransfer, 'drag');
}
}, [readOnly, editor, attributes.onDragStart, state]),
onDrop: React.useCallback(function (event) {
if (!readOnly && ReactEditor.hasTarget(editor, event.target) && !isEventHandled(event, attributes.onDrop)) {
event.preventDefault();
// Keep a reference to the dragged range before updating selection
var draggedRange = editor.selection;
// Find the range where the drop happened
var range = ReactEditor.findEventRange(editor, event);
var data = event.dataTransfer;
slate.Transforms.select(editor, range);
if (state.isDraggingInternally) {
if (draggedRange && !slate.Range.equals(draggedRange, range) && !slate.Editor["void"](editor, {
at: range,
voids: true
})) {
slate.Transforms["delete"](editor, {
at: draggedRange
});
}
}
ReactEditor.insertData(editor, data);
// When dragging from another source into the editor, it's possible
// that the current editor does not have focus.
if (!ReactEditor.isFocused(editor)) {
ReactEditor.focus(editor);
}
}
}, [readOnly, editor, attributes.onDrop, state]),
onDragEnd: React.useCallback(function (event) {
if (!readOnly && state.isDraggingInternally && attributes.onDragEnd && ReactEditor.hasTarget(editor, event.target)) {
attributes.onDragEnd(event);
}
}, [readOnly, state, attributes, editor]),
onFocus: React.useCallback(function (event) {
if (!readOnly && !state.isUpdatingSelection && ReactEditor.hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onFocus)) {
var el = ReactEditor.toDOMNode(editor, editor);
var root = ReactEditor.findDocumentOrShadowRoot(editor);
state.latestElement = root.activeElement;
// COMPAT: If the editor has nested editable elements, the focus
// can go to them. In Firefox, this must be prevented because it
// results in issues with keyboard navigation. (2017/03/30)
if (slateDom.IS_FIREFOX && event.target !== el) {
el.focus();
return;
}
slateDom.IS_FOCUSED.set(editor, true);
}
}, [readOnly, state, editor, attributes.onFocus]),
onKeyDown: React.useCallback(function (event) {
if (!readOnly && ReactEditor.hasEditableTarget(editor, event.target)) {
var _androidInputManagerR5;
(_androidInputManagerR5 = androidInputManagerRef.current) === null || _androidInputManagerR5 === void 0 || _androidInputManagerR5.handleKeyDown(event);
var nativeEvent = event.nativeEvent;
// COMPAT: The composition end event isn't fired reliably in all browsers,
// so we sometimes might end up stuck in a composition state even though we
// aren't composing any more.
if (ReactEditor.isComposing(editor) && nativeEvent.isComposing === false) {
slateDom.IS_COMPOSING.set(editor, false);
setIsComposing(false);
}
if (isEventHandled(event, attributes.onKeyDown) || ReactEditor.isComposing(editor)) {
return;
}
var selection = editor.selection;
var element = editor.children[selection !== null ? selection.focus.path[0] : 0];
var isRTL = getDirection(slate.Node.string(element)) === 'rtl';
// COMPAT: Since we prevent the default behavior on
// `beforeinput` events, the browser doesn't think there's ever
// any history stack to undo or redo, so we have to manage these
// hotkeys ourselves. (2019/11/06)
if (slateDom.Hotkeys.isRedo(nativeEvent)) {
event.preventDefault();
var maybeHistoryEditor = editor;
if (typeof maybeHistoryEditor.redo === 'function') {
maybeHistoryEditor.redo();
}
return;
}
if (slateDom.Hotkeys.isUndo(nativeEvent)) {
event.preventDefault();
var _maybeHistoryEditor = editor;
if (typeof _maybeHistoryEditor.undo === 'function') {
_maybeHistoryEditor.undo();
}
return;
}
// COMPAT: Certain browsers don't handle the selection updates
// properly. In Chrome, the selection isn't properly extended.
// And in Firefox, the selection isn't properly collapsed.
// (2017/10/17)
if (slateDom.Hotkeys.isMoveLineBackward(nativeEvent)) {
event.preventDefault();
slate.Transforms.move(editor, {
unit: 'line',
reverse: true
});
return;
}
if (slateDom.Hotkeys.isMoveLineForward(nativeEvent)) {
event.preventDefault();
slate.Transforms.move(editor, {
unit: 'line'
});
return;
}
if (slateDom.Hotkeys.isExtendLineBackward(nativeEvent)) {
event.preventDefault();
slate.Transforms.move(editor, {
unit: 'line',
edge: 'focus',
reverse: true
});
return;
}
if (slateDom.Hotkeys.isExtendLineForward(nativeEvent)) {
event.preventDefault();
slate.Transforms.move(editor, {
unit: 'line',
edge: 'focus'
});
return;
}
// COMPAT: If a void node is selected, or a zero-width text node
// adjacent to an inline is selected, we need to handle these
// hotkeys manually because browsers won't be able to skip over
// the void node with the zero-width space not being an empty
// string.
if (slateDom.Hotkeys.isMoveBackward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isCollapsed(selection)) {
slate.Transforms.move(editor, {
reverse: !isRTL
});
} else {
slate.Transforms.collapse(editor, {
edge: isRTL ? 'end' : 'start'
});
}
return;
}
if (slateDom.Hotkeys.isMoveForward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isCollapsed(selection)) {
slate.Transforms.move(editor, {
reverse: isRTL
});
} else {
slate.Transforms.collapse(editor, {
edge: isRTL ? 'start' : 'end'
});
}
return;
}
if (slateDom.Hotkeys.isMoveWordBackward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isExpanded(selection)) {
slate.Transforms.collapse(editor, {
edge: 'focus'
});
}
slate.Transforms.move(editor, {
unit: 'word',
reverse: !isRTL
});
return;
}
if (slateDom.Hotkeys.isMoveWordForward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isExpanded(selection)) {
slate.Transforms.collapse(editor, {
edge: 'focus'
});
}
slate.Transforms.move(editor, {
unit: 'word',
reverse: isRTL
});
return;
}
// COMPAT: Certain browsers don't support the `beforeinput` event, so we
// fall back to guessing at the input intention for hotkeys.
// COMPAT: In iOS, some of these hotkeys are handled in the
if (!slateDom.HAS_BEFORE_INPUT_SUPPORT) {
// We don't have a core behavior for these, but they change the
// DOM if we don't prevent them, so we have to.
if (slateDom.Hotkeys.isBold(nativeEvent) || slateDom.Hotkeys.isItalic(nativeEvent) || slateDom.Hotkeys.isTransposeCharacter(nativeEvent)) {
event.preventDefault();
return;
}
if (slateDom.Hotkeys.isSoftBreak(nativeEvent)) {
event.preventDefault();
slate.Editor.insertSoftBreak(editor);
return;
}
if (slateDom.Hotkeys.isSplitBlock(nativeEvent)) {
event.preventDefault();
slate.Editor.insertBreak(editor);
return;
}
if (slateDom.Hotkeys.isDeleteBackward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isExpanded(selection)) {
slate.Editor.deleteFragment(editor, {
direction: 'backward'
});
} else {
slate.Editor.deleteBackward(editor);
}
return;
}
if (slateDom.Hotkeys.isDeleteForward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isExpanded(selection)) {
slate.Editor.deleteFragment(editor, {
direction: 'forward'
});
} else {
slate.Editor.deleteForward(editor);
}
return;
}
if (slateDom.Hotkeys.isDeleteLineBackward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isExpanded(selection)) {
slate.Editor.deleteFragment(editor, {
direction: 'backward'
});
} else {
slate.Editor.deleteBackward(editor, {
unit: 'line'
});
}
return;
}
if (slateDom.Hotkeys.isDeleteLineForward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isExpanded(selection)) {
slate.Editor.deleteFragment(editor, {
direction: 'forward'
});
} else {
slate.Editor.deleteForward(editor, {
unit: 'line'
});
}
return;
}
if (slateDom.Hotkeys.isDeleteWordBackward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isExpanded(selection)) {
slate.Editor.deleteFragment(editor, {
direction: 'backward'
});
} else {
slate.Editor.deleteBackward(editor, {
unit: 'word'
});
}
return;
}
if (slateDom.Hotkeys.isDeleteWordForward(nativeEvent)) {
event.preventDefault();
if (selection && slate.Range.isExpanded(selection)) {
slate.Editor.deleteFragment(editor, {
direction: 'forward'
});
} else {
slate.Editor.deleteForward(editor, {
unit: 'word'
});
}
return;
}
} else {
if (slateDom.IS_CHROME || slateDom.IS_WEBKIT) {
// COMPAT: Chrome and Safari support `beforeinput` event but do not fire
// an event when deleting backwards in a selected void inline node
if (selection && (slateDom.Hotkeys.isDeleteBackward(nativeEvent) || slateDom.Hotkeys.isDeleteForward(nativeEvent)) && slate.Range.isCollapsed(selection)) {
var currentNode = slate.Node.parent(editor, selection.anchor.path);
if (slate.Node.isElement(currentNode) && slate.Editor.isVoid(editor, currentNode) && (slate.Editor.isInline(editor, currentNode) || slate.Editor.isBlock(editor, currentNode))) {
event.preventDefault();
slate.Editor.deleteBackward(editor, {
unit: 'block'
});
return;
}
}
}
}
}
}, [readOnly, editor, attributes.onKeyDown]),
onPaste: React.useCallback(function (event) {
if (!readOnly && ReactEditor.hasEditableTarget(editor, event.target) && !isEventHandled(event, attributes.onPaste)) {
// COMPAT: Certain browsers don't support the `beforeinput` event, so we
// fall back to React's `onPaste` here instead.
// COMPAT: Firefox, Chrome and Safari don't emit `beforeinput` events
// when "paste without formatting" is used, so fallback. (2020/02/20)
// COMPAT: Safari InputEvents generated by pasting won't include
// application/x-slate-fragment items, so use the
// ClipboardEvent here. (2023/03/15)
if (!slateDom.HAS_BEFORE_INPUT_SUPPORT || slateDom.isPlainTextOnlyPaste(event.nativeEvent) || slateDom.IS_WEBKIT) {
event.preventDefault();
ReactEditor.insertData(editor, event.clipboardData);
}
}
}, [readOnly, editor, attributes.onPaste])
}), /*#__PURE__*/React.createElement(Children, {
decorations: decorations,
node: editor,
renderElement: renderElement,
renderChunk: renderChunk,
renderPlaceholder: renderPlaceholder,
renderLeaf: renderLeaf,
renderText: renderText
}))))));
});
/**
* The default placeholder element
*/
var DefaultPlaceholder = function DefaultPlaceholder(_ref2) {
var attributes = _ref2.attributes,
children = _ref2.children;
return (
/*#__PURE__*/
// COMPAT: Artificially add a line-break to the end on the placeholder element
// to prevent Android IMEs to pick up its content in autocorrect and to auto-capitalize the first letter
React.createElement("span", _objectSpread({}, attributes), children, slateDom.IS_ANDROID && /*#__PURE__*/React.createElement("br", null))
);
};
/**
* A default memoized decorate function.
*/
var defaultDecorate = function defaultDecorate() {
return [];
};
/**
* A default implement to scroll dom range into view.
*/
var defaultScrollSelectionIntoView = function defaultScrollSelectionIntoView(editor, domRange) {
// Scroll to the focus point of the selection, in case the selection is expanded
var isBackward = !!editor.selection && slate.Range.isBackward(editor.selection);
var domFocusPoint = domRange.cloneRange();
domFocusPoint.collapse(isBackward);
if (domFocusPoint.getBoundingClientRect) {
var leafEl = domFocusPoint.startContainer.parentElement;
// COMPAT: In Chrome, domFocusPoint.getBoundingClientRect() can return zero dimensions for valid ranges (e.g. line breaks).
// When this happens, do not scroll like most editors do.
var domRect = domFocusPoint.getBoundingClientRect();
var isZeroDimensionRect = domRect.width === 0 && domRect.height === 0 && domRect.x === 0 && domRect.y === 0;
if (isZeroDimensionRect) {
var leafRect = leafEl.getBoundingClientRect();
var leafHasDimensions = leafRect.width > 0 || leafRect.height > 0;
if (leafHasDimensions) {
return;
}
}
// Default behavior: use domFocusPoint's getBoundingClientRect
leafEl.getBoundingClientRect = domFocusPoint.getBoundingClientRect.bind(domFocusPoint);
scrollIntoView(leafEl, {
scrollMode: 'if-needed'
});
// @ts-expect-error an unorthodox delete D:
delete leafEl.getBoundingClientRect;
}
};
/**
* Check if an event is overrided by a handler.
*/
var isEventHandled = function isEventHandled(event, handler) {
if (!handler) {
return false;
}
// The custom event handler may return a boolean to specify whether the event
// shall be treated as being handled or not.
var shouldTreatEventAsHandled = handler(event);
if (shouldTreatEventAsHandled != null) {
return shouldTreatEventAsHandled;
}
return event.isDefaultPrevented() || event.isPropagationStopped();
};
/**
* Check if the event's target is an input element
*/
var isDOMEventTargetInput = function isDOMEventTargetInput(event) {
return slateDom.isDOMNode(event.target) && (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement);
};
/**
* Check if a DOM event is overrided by a handler.
*/
var isDOMEventHandled = function isDOMEventHandled(event, handler) {
if (!handler) {
return false;
}
// The custom event handler may return a boolean to specify whether the event
// shall be treated as being handled or not.
var shouldTreatEventAsHandled = handler(event);
if (shouldTreatEventAsHandled != null) {
return shouldTreatEventAsHandled;
}
return event.defaultPrevented;
};
var handleNativeHistoryEvents = function handleNativeHistoryEvents(editor, event) {
var maybeHistoryEditor = editor;
if (event.inputType === 'historyUndo' && typeof maybeHistoryEditor.undo === 'function') {
maybeHistoryEditor.undo();
return;
}
if (event.inputType === 'historyRedo' && typeof maybeHistoryEditor.redo === 'function') {
maybeHistoryEditor.redo();
return;
}
};
/**
* A React context for sharing the `focused` state of the editor.
*/
var FocusedContext = /*#__PURE__*/React.createContext(false);
/**
* Get the current `focused` state of the editor.
*/
var useFocused = function useFocused() {
return React.useContext(FocusedContext);
};
var REACT_MAJOR_VERSION = parseInt(React.version.split('.')[0], 10);
var _excluded = ["editor", "children", "onChange", "onSelectionChange", "onValueChange", "initialValue"];
/**
* A wrapper around the provider to handle `onChange` events, because the editor
* is a mutable singleton so it won't ever register as "changed" otherwise.
*/
var Slate = function Slate(props) {
var editor = props.editor,
children = props.children,
onChange = props.onChange,
onSelectionChange = props.onSelectionChange,
onValueChange = props.onValueChange,
initialValue = props.initialValue,
rest = _objectWithoutProperties(props, _excluded);
// Run once on first mount, but before `useEffect` or render
React.useState(function () {
if (!slate.Node.isNodeList(initialValue)) {
throw new Error("[Slate] initialValue is invalid! Expected a list of elements but got: ".concat(slate.Scrubber.stringify(initialValue)));
}
if (!slate.Editor.isEditor(editor)) {
throw new Error("[Slate] editor is invalid! You passed: ".concat(slate.Scrubber.stringify(editor)));
}
editor.children = initialValue;
Object.assign(editor, rest);
});
var _useSelectorContext = useSelectorContext(),
selectorContext = _useSelectorContext.selectorContext,
handleSelectorChange = _useSelectorContext.onChange;
var onContextChange = React.useCallback(function () {
if (onChange) {
onChange(editor.children);
}
if (onSelectionChange && editor.operations.find(function (op) {
return op.type === 'set_selection';
})) {
onSelectionChange(editor.selection);
}
if (onValueChange && editor.operations.find(function (op) {
return op.type !== 'set_selection';
})) {
onValueChange(editor.children);
}
handleSelectorChange();
}, [editor, handleSelectorChange, onChange, onSelectionChange, onValueChange]);
React.useEffect(function () {
slateDom.EDITOR_TO_ON_CHANGE.set(editor, onContextChange);
return function () {
slateDom.EDITOR_TO_ON_CHANGE.set(editor, function () {});
};
}, [editor, onContextChange]);
var _useState = React.useState(ReactEditor.isFocused(editor)),
_useState2 = _slicedToArray(_useState, 2),
isFocused = _useState2[0],
setIsFocused = _useState2[1];
React.useEffect(function () {
setIsFocused(ReactEditor.isFocused(editor));
}, [editor]);
useIsomorphicLayoutEffect(function () {
var fn = function fn() {
return setIsFocused(ReactEditor.isFocused(editor));
};
if (REACT_MAJOR_VERSION >= 17) {
// In React >= 17 onFocus and onBlur listen to the focusin and focusout events during the bubbling phase.
// Therefore in order for <Editable />'s handlers to run first, which is necessary for ReactEditor.isFocused(editor)
// to return the correct value, we have to listen to the focusin and focusout events without useCapture here.
document.addEventListener('focusin', fn);
document.addEventListener('focusout', fn);
return function () {
document.removeEventListener('focusin', fn);
document.removeEventListener('focusout', fn);
};
} else {
document.addEventListener('focus', fn, true);
document.addEventListener('blur', fn, true);
return function () {
document.removeEventListener('focus', fn, true);
document.removeEventListener('blur', fn, true);
};
}
}, [editor]);
return /*#__PURE__*/React.createElement(SlateSelectorContext.Provider, {
value: selectorContext
}, /*#__PURE__*/React.createElement(EditorContext.Provider, {
value: editor
}, /*#__PURE__*/React.createElement(FocusedContext.Provider, {
value: isFocused
}, children)));
};
/**
* Get the current editor object from the React context.
* @deprecated Use useSlateStatic instead.
*/
var useEditor = function useEditor() {
var editor = React.useContext(EditorContext);
if (!editor) {
throw new Error("The `useEditor` hook must be used inside the <Slate> component's context.");
}
return editor;
};
/**
* Get the current `selected` state of an element.
*
* Set `suppressThrow` to return `false` instead of throwing when the element
* can no longer be found in the editor.
*/
var useSelected = function useSelected() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref$suppressThrow = _ref.suppressThrow,
suppressThrow = _ref$suppressThrow === void 0 ? false : _ref$suppressThrow;
var element = useElementIf();
// Breaking the rules of hooks is fine here since `!element` will remain true
// or false for the entire lifetime of the component this hook is called from.
// TODO: Decide if we want to throw an error instead when calling
// `useSelected` outside of an element (potentially a breaking change).
if (!element) return false;
// eslint-disable-next-line react-hooks/rules-of-hooks
var selector = React.useCallback(function (editor) {
if (!editor.selection) return false;
try {
var path = ReactEditor.findPath(editor, element);
var range = slate.Editor.range(editor, path);
return !!slate.Range.intersection(range, editor.selection);
} catch (e) {
if (suppressThrow) {
return false;
}
throw e;
}
}, [element, suppressThrow]);
// eslint-disable-next-line react-hooks/rules-of-hooks
return useSlateSelector(selector, undefined, {
// Defer the selector until after `Editable` has rendered so that the path
// will be accurate.
deferred: true
});
};
/**
* Get the current slate selection.
* Only triggers a rerender when the selection actually changes
*/
var useSlateSelection = function useSlateSelection() {
return useSlateSelector(function (editor) {
return editor.selection;
}, isSelectionEqual);
};
var isSelectionEqual = function isSelectionEqual(a, b) {
if (!a && !b) return true;
if (!a || !b) return false;
return slate.Range.equals(a, b);
};
/**
* `withReact` adds React and DOM specific behaviors to the editor.
*
* If you are using TypeScript, you must extend Slate's CustomTypes to use
* this plugin.
*
* See https://docs.slatejs.org/concepts/11-typescript to learn how.
*/
var withReact = function withReact(editor) {
var clipboardFormatKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'x-slate-fragment';
var e = editor;
e = slateDom.withDOM(e, clipboardFormatKey);
var _e = e,
onChange = _e.onChange,
apply = _e.apply,
insertText = _e.insertText;
e.getChunkSize = function () {
return null;
};
if (slateDom.IS_ANDROID) {
e.insertText = function (text, options) {
// COMPAT: Android devices, specifically Samsung devices, experience cursor jumping.
// This issue occurs when the insertText function is called immediately after typing.
// The problem arises because typing schedules a selection change.
// However, this selection change is only executed after the insertText function.
// As a result, the already obsolete selection is applied, leading to incorrect
// final cursor position.
slateDom.EDITOR_TO_PENDING_SELECTION["delete"](e);
return insertText(text, options);
};
}
e.onChange = function (options) {
// COMPAT: React < 18 doesn't batch `setState` hook calls, which means
// that the children and selection can get out of sync for one render
// pass. So we have to use this unstable API to ensure it batches them.
// (2019/12/03)
// https://github.com/facebook/react/issues/14259#issuecomment-439702367
var maybeBatchUpdates = REACT_MAJOR_VERSION < 18 ? ReactDOM.unstable_batchedUpdates : function (callback) {
return callback();
};
maybeBatchUpdates(function () {
onChange(options);
});
};
// On move_node, if the chunking optimization is enabled for the parent of the
// node being moved, add the moved node to the movedNodeKeys set of the
// parent's chunk tree.
e.apply = function (operation) {
if (operation.type === 'move_node') {
var parent = slate.Node.parent(e, operation.path);
var chunking = !!e.getChunkSize(parent);
if (chunking) {
var node = slate.Node.get(e, operation.path);
var chunkTree = getChunkTreeForNode(e, parent);
var key = ReactEditor.findKey(e, node);
chunkTree.movedNodeKeys.add(key);
}
}
apply(operation);
};
return e;
};
Object.defineProperty(exports, "NODE_TO_INDEX", {
enumerable: true,
get: function () { return slateDom.NODE_TO_INDEX; }
});
Object.defineProperty(exports, "NODE_TO_PARENT", {
enumerable: true,
get: function () { return slateDom.NODE_TO_PARENT; }
});
exports.DefaultElement = DefaultElement;
exports.DefaultLeaf = DefaultLeaf;
exports.DefaultPlaceholder = DefaultPlaceholder;
exports.DefaultText = DefaultText;
exports.Editable = Editable;
exports.ReactEditor = ReactEditor;
exports.Slate = Slate;
exports.defaultScrollSelectionIntoView = defaultScrollSelectionIntoView;
exports.useComposing = useComposing;
exports.useEditor = useEditor;
exports.useElement = useElement;
exports.useElementIf = useElementIf;
exports.useFocused = useFocused;
exports.useReadOnly = useReadOnly;
exports.useSelected = useSelected;
exports.useSlate = useSlate;
exports.useSlateSelection = useSlateSelection;
exports.useSlateSelector = useSlateSelector;
exports.useSlateStatic = useSlateStatic;
exports.useSlateWithV = useSlateWithV;
exports.withReact = withReact;
//# sourceMappingURL=index.js.map